《JavaScript語言入門教程》記錄整理:入門和數據類型
- 2020 年 7 月 29 日
- 筆記
- javascript, JavaScript入門教程
本系列基於阮一峰老師的《JavaScrip語言入門教程》或《JavaScript教程》記錄整理,教程採用知識共享 署名-相同方式共享 3.0協議。這幾乎是學習js最好的教程之一(去掉之一都不過分)
最好的教程而阮一峰老師又採用開源方式共享出來,之所以重新記錄一遍,一是強迫自己重新認真讀一遍學一遍;二是對其中知識點有個自己的記錄,加深自己的理解;三是感謝這麼好的教程,希望更多人閱讀了解
入門篇
js介紹
- JavaScript 是一種輕量級的腳本語言和嵌入式(embedded)語言,其只能通過宿主環境(host,瀏覽器或node環境)提供I/O操作
- 語法角度,JS是一種”對象模型”語言。支援函數式編程、”面向對象”編程、過程式編程等
- js核心語法包括:基本的語法構造(比如操作符、控制結構、語句)和標準庫(就是一系列具有各種功能的對象比如
Array
、Date
、Math
等)。然後就是宿主提供的API(比如瀏覽器提供的BOM、DOM和Web互聯網相關的類;Node環境提供文件操作API、網路通訊API等) - JavaScript 的所有值都是對象
- js可以採用事件驅動(
event-driven
)和非阻塞式(non-blocking
)設計,實現高並發、多任務處理
歷史
- 1995年,Brendan Eich 只用了10天,完成了js的第一版,其設計借鑒了C、java、Scheme、Awk、Self、Perl、Python等多種語言,同時也留下了各種設計缺陷(導致常常需要學習各種解決問題的模式)
- JavaScript與Java是兩種不同的語言,兩者的關係僅僅是js的基本語法和對象體系最開始是想要模仿Java,而後又與當時Java的公司Sun有合作,也藉助Java的聲勢,從而後來改名叫JavaScript
- ECMAScript 和 JavaScript 的關係是,前者是後者的規格,後者是前者的一種實現。ECMAScript 只用來標準化 JavaScript 的基本語法結構。但其他標準如 DOM 的標準就是由 W3C組織(
World Wide Web Consortium
)制定的 - 2007年,ECMAScript 4.0版草案發布,但是過於激進,導致後面中止了 ECMAScript 4.0 ,將其中一小部分功能發布為ECMAScript 3.1。之後又將其改名為 ECMAScript 5。
- 2015年6月,ECMAScript 6 正式發布,且更名為「ECMAScript 2015」。之後每年發布一個ECMAScript版本
- 周邊大事記
基本語法
- js執行單位是行(line),一行一行地執行,一般,一行就是一個語句
- 語句(statement)執行某種操作、表達式(expression)用於得到返回值。凡是 JavaScript 語言中預期為值的地方,都可以使用表達式(這一點使js某些使用很靈活)
- 語句以分號結尾,一個分號表示一個語句結束。多個語句可以寫在一行內。分號前無內容,表示空語句。表達式不需要分號結尾
- 變數是對「值」的具名引用,即為”值”取名。變數的名字就是變數名。如下使用var聲明一個變數a,並賦值1
var a=1;
- 只聲明變數而不賦值,則該變數的值是
undefined
,表示”無定義”。同時變數賦值時不寫var
也有效,但不建議。變數必須先聲明再使用,否則會報錯not defined
- 一條var命令可以聲明多個變數。js是動態類型語言,變數類型可以隨時更改。
var a = 1;
a = 'hello';
- 使用
var
重新聲明一個已存在的變數,是無效的,重新聲明時賦值,相當於重新賦值 - 變數提升:js的執行方式是,先解析程式碼,獲取所有被聲明的變數,然後再一行一行地運行,這樣就會導致所有變數的聲明語句,會被提升到程式碼的頭部,這叫做變數提升(
hoisting
) - 標識符(
identifier
)指的是識別各種值的合法名稱。比如變數名、函數名。js標識符大小寫敏感。標識符的命名規則是:只能以字母、下劃線(_
)和美元符號($
)開頭,從第二個字元開始還可以使用數字。 - js標識符中的字母指的是
Unicode
字母。因此中文標識符也可以使用 - js中的保留字不能用於標識符,保留字是指js中用來表示特定含義的字元,如return、class、true、false、function等
- 注釋:注釋用來對程式碼進行解釋,js引擎執行時會忽略注釋部分。
//
表示單行注釋。/* */
表示多行注釋 - js使用大括弧表示”區塊”(block)。對於
var
命令,js的區塊不構成單獨的作用域(scope
) - 條件語句:
-
if
和if...else...
結構,根據條件的布爾值判斷執行。 -
switch
結構判斷表達式的值是否與case
相符並執行,如果都不符則執行最後的default
,case
中break
不能少,否則當前case程式碼塊執行完會接著執行下一個case。switch
語句部分和case
語句部分,都可以使用表達式,這就是js中可以為值的地方,都可以使用表達式的體現,如下:
switch (1 + 3) {
case 2 + 2:
f();
break;
default:
neverHappens();
}
switch語句的值和case語句的值,比較時採用的是嚴格相等運算符(===
)
?:
三元運算符:如下,條件true
,執行”表達式1″,否則執行”表達式2″,然後獲取對應返回值
(條件) ? 表達式1 : 表達式2
- 循環語句:重複執行某個操作
-
while
循環:循環條件和程式碼塊,條件為真,就循環執行程式碼塊 -
for
循環:可以指定循環的起點、終點和終止條件。分為初始化表達式(initialize)、條件表達式(test)、遞增表達式(increment)
for (初始化表達式; 條件; 遞增表達式) {
語句
}
所有for循環,都可以改寫成while循環
for語句的無線循環表示:
for ( ; ; ){
console.log('Hello World');
}
do...while
循環:先執行一次循環體,再判斷條件break
語句用於跳出程式碼塊或循環。continue
語句用於立即終止本輪循環,並開始下一輪循環
- js語句的前面可以添加標籤(label),相當於定位符,用於跳轉到程式的任意位置
label:
語句
數據類型
概述
- JavaScript的數據類型有六種(ES6新增了 Symbol 類型)
- 數值(number):整數和小數(比如1和3.14)
- 字元串(strin):文本(比如”Hello World”)。
- 布爾值(boolean):表示真偽的兩個特殊值,即
true
(真)和false
(假) undefined
:表示「未定義」或不存在,即由於目前沒有定義,所以此處暫時沒有任何值null
:表示空值,即此處的值為空。- 對象(object):各種值組成的集合。
數值、字元串、布爾值稱為原始類型(primitive type
),是最基本的數據類型。對象稱為合成類型(complex type
)。undefined
和null
兩個特殊值。
對象分為:狹義的對象(object)、數組(array)、函數(function)
- 類型判斷
typeof
運算符返回一個值的數據類型:
- 數值、字元串、布爾值分別返回
number
、string
、boolean
- 函數返回
function
,undefined
返回undefined
(可以檢測未聲明的變數),對象返回object
,null
返回object
// 檢測未聲明
if (typeof v === "undefined") {
// ...
}
typeof window // "object"
typeof {} // "object"
typeof [] // "object"
typeof null // "object"
instanceof
可以區分數組和對象
[] instanceof Array // false
[] instanceof Array // true
null 和 undefined
- 兩者含義”類似”,if語句中自動轉為false,相等於運算符(==)兩者比較為true。null表示”空”對象,轉為數值是
0
;undefined
表示”無定義”的原始值,轉為數值是NaN
Number(null) // 0
Number(undefined) // NaN
- 函數沒有返回值時,默認返回 undefined
- 布爾值表示
true
和false
兩個真假狀態。 - 如果 JavaScript 預期某個位置應該是布爾值,則會將該位置上現有的值自動轉為布爾值。
下面六個值會被轉為false
,其他的值都是true
:
- undefined
- null
- false
- 0
- NaN
- “”或”(空字元串)
空數組([])和空對象({})對應的布爾值,都是true
數值
- js中所有數字都是以64位浮點數存儲,整數也是如此。因此
1===1.0
,是同一個數
1 === 1.0 // true
JavaScript 語言的底層根本沒有整數,所有數字都是小數(64位浮點數)
- 浮點數不是精確的值,因此小數的比較和運算要特別注意
0.1 + 0.2 === 0.3 // false
0.3 / 0.1 // 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1) // false
- js浮點數的64個二進位位,從最左邊開始,由如下組成:
- 第1位:符號位,0表示正數,1表示負數。數值正負
- 第2位到第12位(共11位):指數部分。數值的大小
- 第13位到第64位(共52位):小數部分(即有效數字)。數值的精度
- 絕對值小於2的53次方的整數,即$-253$到$253$,都可以精確表示
Math.pow(2, 53); // 9007199254740992
Math.pow(2, 53) + 1; // 9007199254740992
Math.pow(2, 53) + 2; // 9007199254740994
Math.pow(2, 53) + 3; // 9007199254740996
Math.pow(2, 53) + 4; // 9007199254740996
- 最大值和最小值:
Number.MAX_VALUE
和Number.MIN_VALUE
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
- 數值表示法:
50
(十進位)、0xFF
(十六進位)、123e3
或123e-3
(科學計數法) - 當小數點前面的數字多於21位時,或者小數點後的零多於5個時,js會自動將數值轉為科學計數法表示
- 使用字面量(
literal
)表示數值時:
- 十進位:沒有前導0的數值
- 八進位:有前綴
0o
或0O
的數值,或者有前導0
、且只用到0-7的八個阿拉伯數字的數值。 - 十六進位:有前綴
0x
或0X
的數值。 - 二進位:有前綴
0b
或0B
的數值。
js會自動將八進位、十六進位、二進位轉為十進位
- js存在2個
0
:+0
,-0
,兩者是等價的。唯一區別是+0
或-0
當作分母時表達式的返回值不相等。 NaN
表示「非數字」(Not a Number
),比如字元串解析為數字報錯時會返回NaN
。0除以0得到NaN。NaN
只是一個特殊值,類型依舊是Number
NaN
不等於任何值,包括它本身
NaN === NaN // false
NaN
的布爾值為false,NaN
與任何數(包括它自己)的運算,得到的都是NaN
Infinity
表示「無窮」,表示無法表示正無窮(Infinity
)和負無窮(-Infinity
);非0數除以0,得到Infinity
。
Infinity
大於一切數值(除了NaN
),-Infinity
小於一切數值(除了NaN
)
NaN
的比較運算會返回false
parseInt()
方法將字元串轉為整數。字元串轉為整數時,會一個個字元依次轉換,如果遇到不能轉為數字的字元,就不再進行下去,返回已經轉好的部分。轉換失敗返回NaN
。
解析科學計數法的數字時會出現奇怪的結果
第二個參數表示解析的值的進位
parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
parseFloat()
:將一個字元串轉為浮點數,可以對科學計數法字元串正確轉換isNaN()
判斷一個值是否是NaN
。isNaN()
只對數值有效,其他類型值會先轉為數值,再判斷。對於空數組和只有一個數值成員的數組,isNaN()
返回false
isNaN('Hello') // true
// 相當於
isNaN(Number('Hello')) // true
- 判斷NaN最好的方法是:
NaN
是唯一不等於自身的值
function myIsNaN(value) {
return value !== value;
}
如果使用isNaN()
判斷,要同時判斷類型:
function myIsNaN(value) {
return typeof value === 'number' && isNaN(value);
}
isFinite()
判斷是否為正常的數值,判斷是參數也會進行類型轉換
字元串
- 字元串是放在單引號或雙引號中的零個或多個排在一起的字元。字元串中使用相同的引號要用
\
反斜杠轉義 - 每行的尾部使用反斜杠,可以實現多行字元串(
\
後面只能有換行符,否則報錯)
var longString = 'Long \
long \
long \
string';
longString // "Long long long string"
也可使用+
可以將多個字元串列連接
- 反斜杠(
\
)用來表示一些特殊字元,稱為轉義。如\n
換行符;\r
:回車鍵;\t
:製表符。如果在字元串中需要包含反斜杠,需要\
轉義自身。
"你好,反斜杠\\"; // "你好,反斜杠\"
- 字元串可以看做字元數組,但僅能使用數組的方括弧運算符獲取字元,而不能進行其他操作
- length屬性返回字元串長度
'hello'.length // 5
- js使用
Unicode
字符集。不僅以Unicode存儲字元,而且可以只用Unicode碼點,如’\u00A9’表示版權符號
var s = '\u00A9';
s // "©"
每個字元在 JavaScript 內部都是以16位(2個位元組)的 UTF-16
格式儲存。js單位字元長度固定為16位長度
js 對 UTF-16
的支援不完整,只支援兩位元組的字元,無法識別四位元組的字元。比如四位元組字元𝌆
,瀏覽器可以正確識別是一個字元,但js無法識別,認為是兩個字元。需要特別注意
'𝌆'.length // 2
Base64
編碼:對於ASCII 碼0到31的符號無法列印出來,可以使用Base64編碼轉換為可列印的字元;以文本格式傳遞二進位數據,也可以使用Base64編碼Base64
是一種編碼方法,可以將任意值轉成 0~9、A~Z、a-z、+
和/
這64個字元組成的可列印字元。目的是不出現特殊字元,簡化程式處理。
btoa()
:任意值轉為Base64
編碼atob()
:Base64
編碼轉為原來的值
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
btoa()
和atob()
的Base64編碼解碼不使用非ASCII碼的字元,如btoa('你好')
就會報錯。處理辦法是加一個URI轉碼,如下
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
對象
- 對象是一組”鍵值對”(key-value)的集合,是一種無序的複合數據集合。
如下,對象用大括弧,鍵值對又叫成員,分為”鍵名”和”鍵值”,對應”成員的名稱”和”成員的值”,鍵名與鍵值用冒號分割,鍵值對逗號分割。
var obj = {
foo: 'Hello',
bar: 'World'
};
- 對象的所有鍵名都是字元串(ES6中
Symbol
值也可以作為鍵名)。鍵名要麼符合標識名的規則,要麼使用引號包含。鍵名又稱為”屬性”(property
),屬性可以動態創建 - “鍵值”可以是任何數據類型,比如
屬性值可以為函數,這個屬性也可以稱為”方法”,可以像函數一樣調用
屬性的值還是對象,就可以形成鏈式引用 - 對象屬性之間逗號分割,最後一個屬性可以加逗號(
trailing comma
),也可不加 - 不同的變數名指向同一個對象,則它們都是這個對象的引用,即指向同一個記憶體地址。引用只局限於對象,原始類型中,兩個變數就是值的拷貝
- 對象採用大括弧表示,則就有可能和程式碼塊的大括弧產生歧義。比如行首是大括弧,如果看做表達式,則是一個對象;如果是語句,則表示一個程式碼區塊。
{ foo: 123 }
遇到大括弧的歧義時,無法確定是對象還是程式碼塊,JavaScript引擎一律解釋為程式碼塊。
可以將大括弧放入圓括弧中,這樣就是表達式
// eval 對字元串求值
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
- 使用點運算符或方括弧運算符讀取對象的屬性,同時也可以用來賦值。
方括弧運算符的鍵名必須有引號,否則被看做變數
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
數值鍵名不能使用點運算符,使用方括弧運算符時數值鍵名會轉換為字元串
8. Object.keys(obj)
查看一個對象所有屬性或鍵名
9. delete
用於刪除對象本身的屬性,成功後返回true。且刪除不存在的key也返回true。只有屬性存在且不能刪除時,才返回false。不能刪除繼承的屬性
delete obj.p // true
in
運算符檢查對象是否包含某個屬性。繼承時屬性也會返回true。hasOwnProperty(key)
方法片段是否為對象自身的屬性for...in
循環用於遍歷對象的全部屬性
- 遍歷的是對象所有可遍歷(
enumerable
)的屬性,會跳過不可遍歷的屬性。 - 不僅遍歷對象自身的屬性,還遍歷繼承的屬性。
通常都是遍歷對象自身的屬性,因此可以結合hasOwnProperty
方法
var person = { name: '阮一峰老師' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
函數
- 函數是一段可以反覆調用的程式碼塊
- js中聲明函數的三種方法
function
命令——函數的聲明(Function Declaration
)
如下,function 函數名(參數1,...){函數體}
function print(s) {
console.log(s);
}
- 函數表達式(
Function Expression
)
變數賦值的方式,這個匿名函數又稱函數表達式
var print = function(s) {
console.log(s);
};
函數表達式需要在語句的結尾加上分號,表示語句結束。
函數表達式聲明函數時,function
後面不帶有函數名。如果加上函數名也僅在函數體內部有效。
Function
構造函數
如下,使用Function
構造函數時,可以傳遞任意數量的參數,只有最後一個參數會被當做函數體
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同於
function add(x, y) {
return x + y;
}
- 函數的重複聲明:如果同一個函數被多次聲明,後面的就會覆蓋前面的聲明。
- 函數圓括弧在聲明時用於放入參數;非聲明時函數後緊跟圓括弧,表示調用函數並傳入參數。
return
語句表示返回後面表達式的值,並退出當前函數 - 函數調用自身,就是遞歸(
recursion
) - js將函數看作一種值。凡是可以使用值的地方,就能使用函數。函數是一個可以執行的值。因為函數與其他數據類型地位平等,所有在js中又稱函數為第一等公民
- js將函數名視為變數名,所以同樣有函數名的提升,
function
聲明函數時會被提升到程式碼頭部。但是賦值語句定義函數,在賦值前調用會報錯(因為是undefined
) - 函數的屬性和方法
- name屬性返回函數的名字
- length屬性返回函數預期傳入的參數個數,即函數定義之中的參數個數。
length屬性提供了一種機制,判斷定義時和調用時參數的差異,以便實現面向對象編程的「方法重載」(overload) toString()
方法返回字元串,內容是函數的源碼。函數內的注釋也會返回。
原生函數,toString()
方法返回function (){[native code]}
- 函數作用域:作用域(
scope
)指變數存在的範圍。ES5
中,JavaScript只有兩種作用域:全局作用域(變數在整個程式中一直存在);函數作用域(變數只在函數內部存在),函數作用域內同樣存在變數提升。ES6
新增了塊級作用域 - 對於頂層函數(直接在js文件中或
<script>
標籤中),函數外部聲明的變數就是全局變數(global variable
)。函數內部定義的變數,外部無法讀取,稱為”局部變數”(local variable
),且函數內部定義的局部變數,在當前函數作用內會覆蓋同名全局變數 - 使用
var
聲明的變數只有在函數內才是局部變數,其他區塊內聲明仍是全局變數(比如if
、while
等塊內) - 函數本身的作用域:函數本身也是一個值。它的作用域與變數一樣,是其聲明時所在的作用域,與其運行時所在的作用域無關。函數執行時所在的作用域,是定義時的作用域,而不是調用時所在的作用域。
var a = 1;
var x = function () {
console.log(a); //聲明時所在作用域的變數a
};
function f() {
var a = 2;
x();
}
f() // 1
同樣,函數體內部聲明的函數,作用域綁定函數體內部。
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
如上,fon顳部聲明的函數bar,bar的作用域為foo函數內部,當在foo外部取出bar執行時,bar中使用的變數x指向的是foo內部(聲明時所在作用域)的x,而不是foo外部的x。這就是閉包
現象
- 閉包(
closure
)就是能夠讀取其他函數內部變數的函數。而js中,只有函數內部的子函數才能讀取內部變數,因此閉包簡單理解就是”定義在一個函數內部的函數”。本質上,閉包用於將函數內部和函數外部連接起來。
理解閉包,要先理解變數的作用域,函數內部可以讀取全局變數
var e = "我是全局變數";
function f1() {
console.log(e);
}
f1() // "我是全局變數"
但是,函數外部無法讀取函數內部聲明的變數。
function f1() {
var n = "我是函數內部變數";
}
console.log(n) // Uncaught ReferenceError: n is not defined
但是,正常無法得到函數內的局部變數,如果想要實現必須通過變通方法:在函數的內部,再定義一個函數。這樣函數內部的子函數就可以使用函數內的局部變數,但是函數內無法使用其子函數內的局部變數。這就是js特有的 “鏈式作用域”結構(chain scope
),子對象會一級一級地向上尋找所有父對象的變數。
父對象的所有變數,對子對象都是可見的,反之則不成立。
這樣將子函數作為返回值,就可以在函數外部讀取它的內部變數
function f1() {
var n = "我是函數內部變數";
function f2() {
console.log(n); // "我是函數內部變數"
}
return f2;
}
var result=f1();
result(); // "我是函數內部變數"
如上,獲取f1
的返回值f2函數
,而f2
可以讀取f1
的內部變數,這樣就可以在外部獲取f1
內部的變數。閉包就是函數f2
閉包最大的特點,就是它可以”記住”誕生的環境
閉包的用處:
- 讀取函數內部的變數之外
- 讓變數始終保持在記憶體中(即閉包可以使得它的誕生環境一直存在)
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5 // 閉包使得start變數一直在記憶體中
inc() // 6
inc() // 7
閉包使得函數內部環境一直存在,閉包可以看作是函數內部作用域的一個介面
- 封裝對象的私有屬性和私有方法
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('張三');
p1.setAge(25);
p1.getAge(); // 25
p1.name; // "張三"
如上,函數Person
的內部變數_age
,通過閉包getAge
和setAge
,變成了返回對象p1
的私有變數。
比如下面,就是閉包使用的典型例子。通常其在處理循環頁面dom元素並添加事件方法時需要特別注意
var funs=[];
for(var i=0;i<5;i++){
funs[i]=function(){
console.log(i);
}
}
funs.forEach(function(f){
f();
})
// 輸出 5個5,而不是0,1,2,3,4
利用閉包,可實現將變數i的每一個循環值”保存”,供調用時輸出
var funs=[];
for(var i=0;i<5;i++){
(function(i){
funs[i]=function(){
console.log(i);
}
})(i);
}
funs.forEach(function(f){
f();
})
// 輸出 0,1,2,3,4
- 不能濫用閉包,容易產生網頁性能問題。外層函數每次運行,都會生成一個新的閉包,而每個閉包都會保留外層函數的內部變數,記憶體消耗很大。
- 函數的參數,通過圓括弧傳遞外部數據。js在調用時允許省略參數(省略的參數變為
undefined
,但只能省略在後面的參數,靠前的參數不能省略) - 參數傳遞方式:函數參數如果是原始類型的值(數值、字元串、布爾值),參數傳遞就是傳值傳遞(
passes by value
)。函數參數是複合類型的值(數組、對象、其他函數),傳遞方式是傳址傳遞(pass by reference
),此時在函數內部修改參數,會影響原始值 arguments
對象包含了函數運行時的所有參數,arguments[0]
是第一個參數,arguments[1]
是第二個參數,以此類推,arguments
只能在函數體內部使用。
arguments
的length
屬性可以判斷函數調用時的參數個數
arguments
是對象,只是很像數組。如果想使用數組方法,需要將arguments
轉為數組。下面是轉換為數組的兩種方法:
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
- 立即調用的函數表達式(
IIFE
):
圓括弧()
運算符跟在函數名後面,表示調用該函數,如果在定義函數之後,立即調用該函數,如下當function
出現在行首時,js會將其解釋為語句,後面是函數的定義,這是以圓括弧結尾就會報錯。
function(){ /* code */ }();
function
出現在行首就是語句。
// 語句
function f() {}
// 表達式
var f = function f() {}
解決辦法就是不要讓function
出現在行首,讓js引擎解釋為表達式。這樣就可以在定義函數之後立即運行
比如放圓括弧中:
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
這就是”立即調用的函數表達式”(Immediately-Invoked Function Expression
,IIFE
)。
IIFE
最後的分號是必須的。尤其是兩個IIFE
寫在一起時,如果省略分號,連著的兩個IIFE
就出出現問題,可能會報錯如下,兩行之間沒有分號,js將它們連在一起解釋,將第二行解釋為第一行的參數
// 報錯 (function(){ /* code */ }()) (function(){ /* code */ }())
任何讓解釋器以表達式來處理函數定義的方法,都能產生IIFE
。
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
// 甚至
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
通常,只對匿名函數使用”立即執行的函數表達式”。目的:一是不必為函數命名,避免了污染全局變數;二是 IIFE
內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變數。
如下,寫法二完全避免了污染全局變數
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
數組
- 數組(
array
)是按次序排列的一組值,位置編號從0開始,用方括弧表示。比如var arr = ['a', 'b', 'c'];
- 任何類型的數據都可以放入數組
var arr = [
{a: 1},
[1, 2, 3],
function() {return true;}
];
arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}
- 數組本質是一種特殊的對象。
typeof
返回數組的類型是object
數組”對象”的鍵名,是按次序排列的整數。
var arr = ['a', 'b', 'c'];
Object.keys(arr); // ["0", "1", "2"]
js中,對象的鍵名一律為字元串,數組的鍵名也是字元串。對象中數值的鍵名不能使用點結構(object.key
),所以數組元素不能使用.
獲取
- 數組的
length
屬性,返回數組元素的個數,即數組長度
js使用一個32位整數,保存數組的元素個數。即數組長度最多只有 4294967295
個($2^32 – 1$)個
length
屬性是可寫的。減少length
值可用於刪除數組元素。length
設置為0可用於清空數組。length屬性的值等於最大的數字鍵加1
var arr = [ 'a', 'b', 'c' ];
arr.length // 3
arr.length = 2;
arr // ["a", "b"]
- 不推薦使用
for...in
遍曆數組(會遍歷非數字鍵)。可使用for
或while
循環,或forEach
方法遍歷
var a = [1, 2, 3];
// for循環
for(var i = 0; i < a.length; i++) {
console.log(a[i]);
}
// while循環
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
}
var l = a.length;
while (l--) {
console.log(a[l]);
}
// forEach方法
a.forEach(function (item) {
console.log(item);
});
- 數組的某個位置是空元素,即兩個逗號之間沒有任何值,稱該數組存在空位(
hole
)
var a = [1, , 3 , 4];
a.length // 4
a[1] // undefined
delete a[2];
a[2] // undefined
a.length // 4
數組的空位不影響length
屬性。數組最後一個元素後面有逗號,不會產生空位。空位返回undefined
。delete
命令刪除一個數組成員會形成空位,且不會影響length
- 類似數組的對象:如果一個對象的所有鍵名都是正整數或零,並且有
length
屬性,則這個對象語法上稱為”類似數組的對象”(array-like object
)。
“類似數組的對象”並不是數組,它們不具備數組特有的方法。
如下,obj
就是一個類似數組的對象
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function
“類似數組的對象”的根本特徵,就是具有length
屬性。只要有length
屬性,就可以認為這個對象類似於數組。但這種length
屬性不是動態值
典型的”類似數組的對象”是函數的arguments
對象,以及大多數DOM元素集,還有字元串。
// arguments對象
function args() { return arguments }
var arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false
// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false
// 字元串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false
數組的slice
方法可以將”類似數組的對象”變成真正的數組。
var arr = Array.prototype.slice.call(arrayLike);
除了轉為真正的數組,還可以通過call()
把數組的方法放到對象上面,從而讓”類似數組的對象”可以使用數組的方法。
Array.prototype.forEach.call(arrayLike, function (value, index) {
console.log(index + ' : ' + value);
});
這種方法比直接使用數組原生的forEach要慢,所以可以先轉為真正的數組,在調用方法