# JavaScript網(wǎng)道教程-學(xué)習(xí)筆記(重點(diǎn)記錄)
## 導(dǎo)論
各種宿主機(jī)環(huán)境提供額外的API(即只能在該環(huán)境使用的接口),以便JavaScript調(diào)用,以瀏覽器為例,它提供的額外API可以分成三大類:
- 瀏覽器控制類:操作瀏覽器;
- DOM類:操作網(wǎng)頁(yè)文檔的各種元素;
- Web類:實(shí)現(xiàn)互聯(lián)網(wǎng)的各種功能;
JavaScript的核心語(yǔ)法精簡(jiǎn),但是其復(fù)雜性體現(xiàn)在其他兩個(gè)方面:
- 首先,它涉及大量的外部 API。JavaScript 要發(fā)揮作用,必須與其他組件配合,這些外部組件五花八門(mén),數(shù)量極其龐大,幾乎涉及網(wǎng)絡(luò)應(yīng)用的各個(gè)方面,掌握它們絕非易事;
- 其次,JavaScript 語(yǔ)言有一些設(shè)計(jì)缺陷。某些地方相當(dāng)不合理,另一些地方則會(huì)出現(xiàn)怪異的運(yùn)行結(jié)果。學(xué)習(xí) JavaScript,很大一部分時(shí)間是用來(lái)搞清楚哪些地方有陷阱;
JavaScript 的性能優(yōu)勢(shì)體現(xiàn)在以下方面:
- **靈活的語(yǔ)法,表達(dá)力強(qiáng)。**
- **支持編譯運(yùn)行。**
- **事件驅(qū)動(dòng)和非阻塞式設(shè)計(jì)。**
進(jìn)入JavaScript實(shí)驗(yàn)環(huán)境的快捷鍵(Chrome):
- Mac:Option + Command + J
- Win/Linux:Ctrl + Shift + J
進(jìn)入開(kāi)發(fā)者工具快捷鍵(Chrome):
- Mac:Option + Command + I
- Win/Linux:Ctrl + Shift + I
---
## 歷史
---
## 基本語(yǔ)法
### 變量提升
> JavaScript 引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然后再一行一行地運(yùn)行。這造成的結(jié)果,就是所有的變量的聲明語(yǔ)句,都會(huì)被提升到代碼的頭部,這就叫做變量提升(hoisting)。
### 區(qū)塊
> JavaScript 使用大括號(hào),將多個(gè)相關(guān)的語(yǔ)句組合在一起,稱為“區(qū)塊”(block)。
>
> 對(duì)于`var`命令來(lái)說(shuō),JavaScript 的區(qū)塊不構(gòu)成單獨(dú)的作用域(scope)。
### 條件語(yǔ)句
> JavaScript 提供`if`結(jié)構(gòu)和`switch`結(jié)構(gòu),完成條件判斷,即只有滿足預(yù)設(shè)的條件,才會(huì)執(zhí)行相應(yīng)的語(yǔ)句。
#### if
注意,`if`后面的表達(dá)式之中,不要混淆賦值表達(dá)式(`=`)、嚴(yán)格相等運(yùn)算符(`===`)和相等運(yùn)算符(`==`)。尤其是賦值表達(dá)式不具有比較作用。
為了避免將`==`寫(xiě)成`=`的情況,有些開(kāi)發(fā)者習(xí)慣將常量寫(xiě)在運(yùn)算符的左邊,這樣的話,一旦不小心將相等運(yùn)算符寫(xiě)成賦值運(yùn)算符,就會(huì)報(bào)錯(cuò),因?yàn)槌A坎荒鼙毁x值,如下:
```javascript
if (x = 2) { // 不報(bào)錯(cuò)
if (2 = x) { // 報(bào)錯(cuò)
```
多個(gè)判斷分支時(shí),使用`if...else if...else`
#### switch
`switch`語(yǔ)句部分和`case`語(yǔ)句部分,都可以使用表達(dá)式。
> 需要注意的是,`switch`語(yǔ)句后面的表達(dá)式,與`case`語(yǔ)句后面的表示式比較運(yùn)行結(jié)果時(shí),采用的是嚴(yán)格相等運(yùn)算符(`===`),而不是相等運(yùn)算符(`==`),這意味著比較時(shí)不會(huì)發(fā)生類型轉(zhuǎn)換。
#### 三目運(yùn)算符
```shell
(條件) ? 表達(dá)式1 : 表達(dá)式2
```
### 循環(huán)
- while
- for
- do...while
### 標(biāo)簽(label)
> JavaScript 語(yǔ)言允許,語(yǔ)句的前面有標(biāo)簽(label),相當(dāng)于定位符,用于跳轉(zhuǎn)到程序的任意位置,標(biāo)簽的格式如下:
>
> label:
>
>? ? 語(yǔ)句
給的樣例:
```javascript
top:
? for (var i = 0; i < 3; i++){
? ? for (var j = 0; j < 3; j++){
? ? ? if (i === 1 && j === 1) break top;? //當(dāng)break執(zhí)行時(shí),直接跳出雙重循環(huán)
? ? ? console.log('i=' + i + ', j=' + j);
? ? }
? }
```
break、continue都可以結(jié)合label來(lái)使用,可以跳出多重循環(huán),也可以用來(lái)跳出區(qū)塊...
---
## 數(shù)據(jù)類型
### 確定value類型的三種方式
- typeof運(yùn)算符
- instanceof運(yùn)算符
- Object.prototype.toString方法
```javascript
typeof null; //"object"
```
> `null`的類型是`object`,這是由于歷史原因造成的。1995年的 JavaScript 語(yǔ)言第一版,只設(shè)計(jì)了五種數(shù)據(jù)類型(對(duì)象、整數(shù)、浮點(diǎn)數(shù)、字符串和布爾值),沒(méi)考慮`null`,只把它當(dāng)作`object`的一種特殊值。后來(lái)`null`獨(dú)立出來(lái),作為一種單獨(dú)的數(shù)據(jù)類型,為了兼容以前的代碼,`typeof null`返回`object`就沒(méi)法改變了。
### null 與 undefined
> `null`與`undefined`都可以表示“沒(méi)有”,含義非常相似。將一個(gè)變量賦值為`undefined`或`null`,老實(shí)說(shuō),語(yǔ)法效果幾乎沒(méi)區(qū)別。
```javascript
var a = undefined;
// 或者
var a = null;
```
上面代碼中,變量`a`分別被賦值為`undefined`和`null`,這兩種寫(xiě)法的效果幾乎等價(jià)。
在`if`語(yǔ)句中,它們都會(huì)被自動(dòng)轉(zhuǎn)為`false`,相等運(yùn)算符(`==`)甚至直接報(bào)告兩者相等。
用法與含義:
> 對(duì)于`null`和`undefined`,大致可以像下面這樣理解。
>
> `null`表示空值,即該處的值現(xiàn)在為空。調(diào)用函數(shù)時(shí),某個(gè)參數(shù)未設(shè)置任何值,這時(shí)就可以傳入`null`,表示該參數(shù)為空。比如,某個(gè)函數(shù)接受引擎拋出的錯(cuò)誤作為參數(shù),如果運(yùn)行過(guò)程中未出錯(cuò),那么這個(gè)參數(shù)就會(huì)傳入`null`,表示未發(fā)生錯(cuò)誤。
>
> `undefined`表示“未定義”,下面是返回`undefined`的典型場(chǎng)景。
>
> ```javascript
> // 變量聲明了,但沒(méi)有賦值
> var i;
> i // undefined
>
> // 調(diào)用函數(shù)時(shí),應(yīng)該提供的參數(shù)沒(méi)有提供,該參數(shù)等于 undefined
> function f(x) {
>? return x;
> }
> f() // undefined
>
> // 對(duì)象沒(méi)有賦值的屬性
> var? o = new Object();
> o.p // undefined
>
> // 函數(shù)沒(méi)有返回值時(shí),默認(rèn)返回 undefined
> function f() {}
> f() // undefined
> ```
### 布爾值
JavaScript中會(huì)返回布爾值的運(yùn)算符:
- 前置邏輯運(yùn)算符: `!` (Not)
- 相等運(yùn)算符:`===`,`!==`,`==`,`!=`
- 比較運(yùn)算符:`>`,`>=`,`<`,`<=`
如果 JavaScript 預(yù)期某個(gè)位置應(yīng)該是布爾值,會(huì)將該位置上現(xiàn)有的值自動(dòng)轉(zhuǎn)為布爾值。轉(zhuǎn)換規(guī)則是除了下面六個(gè)值被轉(zhuǎn)為`false`,其他值都視為`true`。
- `undefined`
- `null`
- `false`
- `0`
- `NaN`
- `""`或`''`(空字符串)
注意,空數(shù)組(`[]`)和空對(duì)象(`{}`)對(duì)應(yīng)的布爾值,都是`true`。
### 數(shù)值
JavaScript 內(nèi)部,所有數(shù)字都是以64位浮點(diǎn)數(shù)形式儲(chǔ)存,即使整數(shù)也是如此。所以,`1`與`1.0`是相同的,是同一個(gè)數(shù)。
這就是說(shuō),JavaScript 語(yǔ)言的底層根本沒(méi)有整數(shù),所有數(shù)字都是小數(shù)(64位浮點(diǎn)數(shù))。容易造成混淆的是,某些運(yùn)算只有整數(shù)才能完成,此時(shí) JavaScript 會(huì)自動(dòng)把64位浮點(diǎn)數(shù),轉(zhuǎn)成32位整數(shù),然后再進(jìn)行運(yùn)算,參見(jiàn)《運(yùn)算符》一章的“位運(yùn)算”部分。
#### 數(shù)值精度
根據(jù)國(guó)際標(biāo)準(zhǔn) IEEE 754,JavaScript 浮點(diǎn)數(shù)的64個(gè)二進(jìn)制位,從最左邊開(kāi)始,是這樣組成的。
- 第1位:符號(hào)位,`0`表示正數(shù),`1`表示負(fù)數(shù)
- 第2位到第12位(共11位):指數(shù)部分
- 第13位到第64位(共52位):小數(shù)部分(即有效數(shù)字)
精度最多只能到53個(gè)二進(jìn)制位,這意味著,絕對(duì)值小于2的53次方的整數(shù),即-253到253,都可以精確表示。
#### 數(shù)值范圍
也就是說(shuō),64位浮點(diǎn)數(shù)的指數(shù)部分的值最大為2047,分出一半表示負(fù)數(shù),則 JavaScript 能夠表示的數(shù)值范圍為21024到2-1023(開(kāi)區(qū)間),超出這個(gè)范圍的數(shù)無(wú)法表示。
#### 數(shù)值的表示
以下兩種情況,JavaScript 會(huì)自動(dòng)將數(shù)值轉(zhuǎn)為科學(xué)計(jì)數(shù)法表示,其他情況都采用字面形式直接表示:
1. **小數(shù)點(diǎn)前的數(shù)字多于21位**;
2. **小數(shù)點(diǎn)后的零多于5個(gè)**;
#### 數(shù)值的進(jìn)制
使用字面量(literal)直接表示一個(gè)數(shù)值時(shí),JavaScript 對(duì)整數(shù)提供四種進(jìn)制的表示方法:十進(jìn)制、十六進(jìn)制、八進(jìn)制、二進(jìn)制。
- 十進(jìn)制:沒(méi)有前導(dǎo)0的數(shù)值。
- 八進(jìn)制:有前綴`0o`或`0O`的數(shù)值,或者有前導(dǎo)0、且只用到0-7的八個(gè)阿拉伯?dāng)?shù)字的數(shù)值。
- 十六進(jìn)制:有前綴`0x`或`0X`的數(shù)值。
- 二進(jìn)制:有前綴`0b`或`0B`的數(shù)值。
#### 特殊數(shù)值
- 正零、負(fù)零
- NaN
? `NaN`是 JavaScript 的特殊值,表示“非數(shù)字”(Not a Number),主要出現(xiàn)在將字符串解析成數(shù)字出錯(cuò)的場(chǎng)合;
? `NaN`不等于任何值,包括它本身;
? 數(shù)組的`indexOf`方法內(nèi)部使用的是嚴(yán)格相等運(yùn)算符,所以該方法對(duì)`NaN`不成立;
? `NaN`在布爾運(yùn)算時(shí)被當(dāng)作`false`;
? `NaN`與任何數(shù)(包括它自己)的運(yùn)算,得到的都是`NaN`
- Infinity
---
### 字符串
---
### 對(duì)象
> 對(duì)象的每一個(gè)鍵名又稱為“屬性”(property),它的“鍵值”可以是任何數(shù)據(jù)類型。如果一個(gè)屬性的值為函數(shù),通常把這個(gè)屬性稱為“方法”,它可以像函數(shù)那樣調(diào)用。
eg:
```javascript
var obj = {
? p: function (x) {
? ? return 2 * x;
? }
};
obj.p(1) // 2
```
> 如果屬性的值還是一個(gè)對(duì)象,就形成了鏈?zhǔn)揭谩?/p>
eg:
```javascript
var o1 = {};
var o2 = { bar: 'hello' };
o1.foo = o2;
o1.foo.bar // "hello"
```
#### 對(duì)象的引用
如果不同的變量名指向同一個(gè)對(duì)象,那么它們都是這個(gè)對(duì)象的引用,也就是說(shuō)指向同一個(gè)內(nèi)存地址。修改其中一個(gè)變量,會(huì)影響到其他所有變量:
```javascript
var o1 = {};
var o2 = o1;
o1.a = 1;
o2.a // 1
o2.b = 2;
o1.b // 2
```
此時(shí),如果取消某一個(gè)變量對(duì)于原對(duì)象的引用,不會(huì)影響到另一個(gè)變量:
```javascript
var o1 = {};
var o2 = o1;
o1 = 1;
o2 // {}
```
#### 這是表達(dá)式還是語(yǔ)句?
對(duì)象采用大括號(hào)表示,這導(dǎo)致了一個(gè)問(wèn)題:如果行首是一個(gè)大括號(hào),它到底是表達(dá)式還是語(yǔ)句?
```javascript
{ foo: 123 }
```
JavaScript 引擎讀到上面這行代碼,會(huì)發(fā)現(xiàn)可能有兩種含義。第一種可能是,這是一個(gè)表達(dá)式,表示一個(gè)包含`foo`屬性的對(duì)象;第二種可能是,這是一個(gè)語(yǔ)句,表示一個(gè)代碼區(qū)塊,里面有一個(gè)標(biāo)簽`foo`,指向表達(dá)式`123`。
為了避免這種歧義,JavaScript 引擎的做法是,如果遇到這種情況,無(wú)法確定是對(duì)象還是代碼塊,一律解釋為代碼塊。
如果要解釋為對(duì)象,最好在大括號(hào)前加上圓括號(hào)。因?yàn)閳A括號(hào)的里面,只能是表達(dá)式,所以確保大括號(hào)只能解釋為對(duì)象。(但是這樣子有意義嗎?匿名對(duì)象?)
這種差異在使用eval語(yǔ)句時(shí)表現(xiàn)最明顯,可以用eval語(yǔ)句來(lái)聲明賦值一個(gè)變量是對(duì)象還是別的類型:
```javascript
var a = eval('{foo: 123}') // 123
var b = eval('({foo: 123})') // {foo: 123}
a // 123
b // {foo: 123}
```
#### 屬性讀取需要注意的地方
```javascript
var foo = 'bar';
var obj = {
? foo: 1,
? bar: 2
};
obj.foo? // 1
obj[foo]? // 2
```
#### 屬性的刪除
另外,需要注意的是,`delete`命令只能刪除對(duì)象本身的屬性,無(wú)法刪除繼承的屬性;
#### 屬性是否存在:使用`IN`運(yùn)算符
`in`運(yùn)算符用于檢查對(duì)象是否包含某個(gè)屬性(注意,檢查的是鍵名,不是鍵值),如果包含就返回`true`,否則返回`false`。它的左邊是一個(gè)字符串,表示屬性名,右邊是一個(gè)對(duì)象。eg:
```javascript
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
```
`in`運(yùn)算符的一個(gè)問(wèn)題是,它不能識(shí)別哪些屬性是對(duì)象自身的,哪些屬性是繼承的。就像上面代碼中,對(duì)象`obj`本身并沒(méi)有`toString`屬性,但是`in`運(yùn)算符會(huì)返回`true`,因?yàn)檫@個(gè)屬性是繼承的。
這時(shí),可以使用對(duì)象的`hasOwnProperty`方法判斷一下,是否為對(duì)象自身的屬性。eg:
```javascript
var obj = {};
if ('toString' in obj) {
? console.log(obj.hasOwnProperty('toString')) // false
}
```
#### 屬性的遍歷:使用`for...in`循環(huán)
`for...in`循環(huán)用來(lái)遍歷一個(gè)對(duì)象的全部屬性:
```javascript
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
? console.log('鍵名:', i);
? console.log('鍵值:', obj[i]);
}
// 鍵名: a
// 鍵值: 1
// 鍵名: b
// 鍵值: 2
// 鍵名: c
// 鍵值: 3
```
`for...in`循環(huán)有兩個(gè)使用注意點(diǎn)。
- 它遍歷的是對(duì)象所有可遍歷(enumerable)的屬性,會(huì)跳過(guò)不可遍歷的屬性;
- 它不僅遍歷對(duì)象自身的屬性,還遍歷繼承的屬性;
舉例來(lái)說(shuō),對(duì)象都繼承了`toString`屬性,但是`for...in`循環(huán)不會(huì)遍歷到這個(gè)屬性:
```javascript
var obj = {};
// toString 屬性是存在的
obj.toString // toString() { [native code] }
for (var p in obj) {
? console.log(p);
} // 沒(méi)有任何輸出
```
(方法是不可遍歷的?)
#### with語(yǔ)句
`with`語(yǔ)句的格式如下:
```javascript
with (對(duì)象) {
? 語(yǔ)句;
}
```
它的作用是操作同一個(gè)對(duì)象的多個(gè)屬性時(shí),提供一些書(shū)寫(xiě)的方便:
```javascript
// 例一
var obj = {
? p1: 1,
? p2: 2,
};
with (obj) {
? p1 = 4;
? p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
// 例二
with (document.links[0]){
? console.log(href);
? console.log(title);
? console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);
```
注意,如果`with`區(qū)塊內(nèi)部有變量的賦值操作,必須是當(dāng)前對(duì)象已經(jīng)存在的屬性,否則會(huì)創(chuàng)造一個(gè)當(dāng)前作用域的全局變量:
```javascript
var obj = {};
with (obj) {
? p1 = 4;
? p2 = 5;
}
obj.p1 // undefined
p1 // 4
```
弊病在于:`with`區(qū)塊沒(méi)有改變作用域,它的內(nèi)部依然是當(dāng)前作用域。這造成了`with`語(yǔ)句的一個(gè)很大的弊病,就是綁定對(duì)象不明確。eg:
```javascript
with (obj) {
? console.log(x);
}
```
單純從上面的代碼塊,根本無(wú)法判斷`x`到底是全局變量,還是對(duì)象`obj`的一個(gè)屬性。這非常不利于代碼的除錯(cuò)和模塊化,編譯器也無(wú)法對(duì)這段代碼進(jìn)行優(yōu)化,只能留到運(yùn)行時(shí)判斷,這就拖慢了運(yùn)行速度。因此,建議不要使用`with`語(yǔ)句,可以考慮用一個(gè)臨時(shí)變量代替`with`:
```javascript
with(obj1.obj2.obj3) {
? console.log(p1 + p2);
}
// 可以寫(xiě)成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);
```
---
## 函數(shù)
### 函數(shù)的聲明
1. function 命令
2. 函數(shù)表達(dá)式
? `var f = function f() {};`
3. Function構(gòu)造函數(shù)
### 函數(shù)的重復(fù)聲明
> 如果同一個(gè)函數(shù)被多次聲明,后面的聲明就會(huì)覆蓋前面的聲明。
eg:
```javascript
function f() {
? console.log(1);
}
f() // 2
function f() {
? console.log(2);
}
f() // 2
```
上面代碼中,后一次的函數(shù)聲明覆蓋了前面一次。而且,由于函數(shù)名的提升,前一次聲明在任何時(shí)候都是無(wú)效的,這一點(diǎn)要特別注意。
### 函數(shù)是第一等公民
> 函數(shù)只是一個(gè)可以執(zhí)行的值,此外并無(wú)特殊之處。
>
> 由于函數(shù)與其他數(shù)據(jù)類型地位平等,所以在 JavaScript 語(yǔ)言中又稱函數(shù)為第一等公民。
eg:
```javascript
function add(x, y) {
? return x + y;
}
// 將函數(shù)賦值給一個(gè)變量
var operator = add;
// 將函數(shù)作為參數(shù)和返回值
function a(op){
? return op;
}
a(add)(1, 1)? // 第二個(gè)圓括號(hào)是第一個(gè)圓括號(hào)中傳入的函數(shù)所需的參數(shù)
// 2
```
### 函數(shù)名提升
由于函數(shù)是第一等公民,其地位與其他數(shù)據(jù)類型相等,而變量存在"變量"提升,巧了,函數(shù)名也存在"提升";
> JavaScript 引擎將函數(shù)名視同變量名,所以采用`function`命令聲明函數(shù)時(shí),整個(gè)函數(shù)會(huì)像變量聲明一樣,被提升到代碼頭部。所以,下面的代碼不會(huì)報(bào)錯(cuò):
>
> ```javascript
> f();
>
> function f() {}
> ```
但是,如果采用賦值語(yǔ)句定義函數(shù),JavaScript 就會(huì)報(bào)錯(cuò):
```javascript
f();
var f = function (){};
// TypeError: undefined is not a function
```
上面的代碼等同于:
```javascript
var f;
f();
f = function () {};
```
上面代碼第二行,調(diào)用`f`的時(shí)候,`f`只是被聲明了,還沒(méi)有被賦值,等于`undefined`,所以會(huì)報(bào)錯(cuò)。因此,如果同時(shí)采用`function`命令和賦值語(yǔ)句聲明同一個(gè)函數(shù),最后總是采用賦值語(yǔ)句的定義:
```javascript
var f = function () {
? console.log('1');
}
function f() {
? console.log('2');
}
f() // 1
```
### 函數(shù)本身的作用域
函數(shù)本身也是一個(gè)值,也有自己的作用域。它的作用域與變量一樣,就是其聲明時(shí)所在的作用域,與其運(yùn)行時(shí)所在的作用域無(wú)關(guān)。
```javascript
var a = 1;
var x = function () {
? console.log(a);
};
function f() {
? var a = 2;
? x();
}
f() // 1
```
上面代碼中,函數(shù)`x`是在函數(shù)`f`的外部聲明的,所以它的作用域綁定外層,內(nèi)部變量`a`不會(huì)到函數(shù)`f`體內(nèi)取值,所以輸出`1`,而不是`2`。
**總之,函數(shù)執(zhí)行時(shí)所在的作用域,是定義時(shí)的作用域,而不是調(diào)用時(shí)所在的作用域。**
**同樣的,函數(shù)體內(nèi)部聲明的函數(shù),作用域綁定函數(shù)體內(nèi)部。**
```javascript
function foo() {
? var x = 1;
? function bar() {
? ? console.log(x);
? }
? return bar;
}
var x = 2;
var f = foo();
f() // 1
```
上面代碼中,函數(shù)`foo`內(nèi)部聲明了一個(gè)函數(shù)`bar`,`bar`的作用域綁定`foo`。當(dāng)我們?cè)赻foo`外部取出`bar`執(zhí)行時(shí),變量`x`指向的是`foo`內(nèi)部的`x`,而不是`foo`外部的`x`。正是這種機(jī)制,構(gòu)成了“閉包”現(xiàn)象。
### 函數(shù)參數(shù)的傳遞方式
- 函數(shù)參數(shù)如果是原始類型的值(數(shù)值、字符串、布爾值),傳遞方式是傳值傳遞(passes by value)。這意味著,在函數(shù)體內(nèi)修改參數(shù)值,不會(huì)影響到函數(shù)外部。
- 但是,如果函數(shù)參數(shù)是復(fù)合類型的值(數(shù)組、對(duì)象、其他函數(shù)),傳遞方式是傳址傳遞(pass by reference)。也就是說(shuō),傳入函數(shù)的原始值的地址,因此在函數(shù)內(nèi)部修改參數(shù),將會(huì)影響到原始值。
**注意,如果函數(shù)內(nèi)部修改的,不是參數(shù)對(duì)象的某個(gè)屬性,而是替換掉整個(gè)參數(shù),這時(shí)不會(huì)影響到原始值:**
```javascript
var obj = [1, 2, 3];
function f(o) {
? o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
```
上面代碼中,在函數(shù)`f`內(nèi)部,參數(shù)對(duì)象`obj`被整個(gè)替換成另一個(gè)值。這時(shí)不會(huì)影響到原始值。這是因?yàn)椋问絽?shù)(`o`)的值實(shí)際是參數(shù)`obj`的地址,重新對(duì)`o`賦值導(dǎo)致`o`指向另一個(gè)地址,保存在原地址上的值當(dāng)然不受影響。
### 同名參數(shù)
- 如果有同名的參數(shù),則取最后出現(xiàn)的那個(gè)值。
- 即使后面的`a`沒(méi)有值或被省略,也是以其為準(zhǔn)。
```javascript
function f(a, a) {
? console.log(a);
}
f(1) // undefined
```
調(diào)用函數(shù)`f`的時(shí)候,沒(méi)有提供第二個(gè)參數(shù),`a`的取值就變成了`undefined`。這時(shí),如果要獲得第一個(gè)`a`的值,可以使用`arguments`對(duì)象:
```javascript
function f(a, a) {
? console.log(arguments[0]);
}
f(1) // 1
```
### arguments對(duì)象
> 由于 JavaScript 允許函數(shù)有不定數(shù)目的參數(shù),所以需要一種機(jī)制,可以在函數(shù)體內(nèi)部讀取所有參數(shù)。這就是`arguments`對(duì)象的由來(lái)。
>
> `arguments`對(duì)象包含了函數(shù)運(yùn)行時(shí)的所有參數(shù),`arguments[0]`就是第一個(gè)參數(shù),`arguments[1]`就是第二個(gè)參數(shù),以此類推。這個(gè)對(duì)象只有在函數(shù)體內(nèi)部,才可以使用。
正常模式下,`arguments`對(duì)象可以在運(yùn)行時(shí)修改:
```javascript
var f = function(a, b) {
? arguments[0] = 3;
? arguments[1] = 2;
? return a + b;
}
f(1, 1) // 5
```
嚴(yán)格模式下,`arguments`對(duì)象與函數(shù)參數(shù)不具有聯(lián)動(dòng)關(guān)系。也就是說(shuō),修改`arguments`對(duì)象不會(huì)影響到實(shí)際的函數(shù)參數(shù):
```javascript
var f = function(a, b) {
? 'use strict'; // 開(kāi)啟嚴(yán)格模式
? arguments[0] = 3;
? arguments[1] = 2;
? return a + b;
}
f(1, 1) // 2
```
#### 與數(shù)組的關(guān)系
*需要注意的是,雖然`arguments`很像數(shù)組,但它是一個(gè)對(duì)象。數(shù)組專有的方法(比如`slice`和`forEach`),不能在`arguments`對(duì)象上直接使用。*
如果要讓`arguments`對(duì)象使用數(shù)組方法,真正的解決方法是將`arguments`轉(zhuǎn)為真正的數(shù)組。下面是兩種常用的轉(zhuǎn)換方法:`slice`方法和逐一填入新數(shù)組:
```javascript
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
? args.push(arguments[i]);
}
```
#### callee屬性
`arguments`對(duì)象帶有一個(gè)`callee`屬性,返回它所對(duì)應(yīng)的原函數(shù)。
```javascript
var f = function () {
? console.log(arguments.callee === f);
}
f() // true
```
> 可以通過(guò)`arguments.callee`,達(dá)到調(diào)用函數(shù)自身的目的。這個(gè)屬性在嚴(yán)格模式里面是禁用的,因此不建議使用。
### **閉包(closure)**
> 理解閉包,首先必須理解變量作用域。
> 如果出于種種原因,需要得到函數(shù)內(nèi)的局部變量。正常情況下,這是辦不到的,只有通過(guò)變通方法才能實(shí)現(xiàn)。那就是在函數(shù)的內(nèi)部,再定義一個(gè)函數(shù)。
#### 鏈?zhǔn)阶饔糜?/p>
JavaScript 語(yǔ)言特有的"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope),子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量。所以,父對(duì)象的所有變量,對(duì)子對(duì)象都是可見(jiàn)的,反之則不成立。
eg:
```javascript
function f1() {
? var n = 999;
? function f2() {
console.log(n); // 999
? }
}
```
上面代碼中,函數(shù)`f2`就在函數(shù)`f1`內(nèi)部,這時(shí)`f1`內(nèi)部的所有局部變量,對(duì)`f2`都是可見(jiàn)的。但是反過(guò)來(lái)就不行,`f2`內(nèi)部的局部變量,對(duì)`f1`就是不可見(jiàn)的。
#### 閉包的特點(diǎn)與作用
> 閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境,比如`f2`記住了它誕生的環(huán)境`f1`,所以從`f2`可以得到`f1`的內(nèi)部變量。在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。
閉包的最大用處有兩個(gè),一個(gè)是可以讀取函數(shù)內(nèi)部的變量,另一個(gè)就是讓這些變量始終保持在內(nèi)存中,即閉包可以使得它誕生環(huán)境一直存在。請(qǐng)看下面的例子,閉包使得內(nèi)部變量記住上一次調(diào)用時(shí)的運(yùn)算結(jié)果:
```javascript
function createIncrementor(start) {
? return function () {
? ? return start++;
? };
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
```
閉包的另一個(gè)用處,是封裝對(duì)象的私有屬性和私有方法,eg:
```javascript
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
```
上面代碼中,函數(shù)`Person`的內(nèi)部變量`_age`,通過(guò)閉包`getAge`和`setAge`,變成了返回對(duì)象`p1`的私有變量。
**注意,外層函數(shù)每次運(yùn)行,都會(huì)生成一個(gè)新的閉包,而這個(gè)閉包又會(huì)保留外層函數(shù)的內(nèi)部變量,所以內(nèi)存消耗很大。因此不能濫用閉包,否則會(huì)造成網(wǎng)頁(yè)的性能問(wèn)題。**
### 立即調(diào)用的函數(shù)表達(dá)式(IIFE)
> 有時(shí),我們需要在定義函數(shù)之后,立即調(diào)用該函數(shù)。
**通常情況下,只對(duì)匿名函數(shù)使用這種“立即執(zhí)行的函數(shù)表達(dá)式”。它的目的有兩個(gè):一是不必為函數(shù)命名,避免了污染全局變量;二是 IIFE 內(nèi)部形成了一個(gè)單獨(dú)的作用域,可以封裝一些外部無(wú)法讀取的私有變量。**
eg:
```javascript
// 寫(xiě)法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫(xiě)法二
(function () {
? var tmp = newData;
? processData(tmp);
? storeData(tmp);
}());
```
上面代碼中,寫(xiě)法二比寫(xiě)法一更好,因?yàn)橥耆苊饬宋廴救肿兞俊?/p>
### eval命令
`eval`命令接受一個(gè)字符串作為參數(shù),并將這個(gè)字符串當(dāng)作語(yǔ)句執(zhí)行:
```javascript
eval('var a = 1;');
a // 1
```
如果參數(shù)字符串無(wú)法當(dāng)作語(yǔ)句運(yùn)行,那么就會(huì)報(bào)錯(cuò):
```javascript
eval('3x') // Uncaught SyntaxError: Invalid or unexpected token
```
放在`eval`中的字符串,應(yīng)該有獨(dú)自存在的意義,不能用來(lái)與`eval`以外的命令配合使用。舉例來(lái)說(shuō),下面的代碼將會(huì)報(bào)錯(cuò):
```javascript
eval('return;'); // Uncaught SyntaxError: Illegal return statement
```
上面代碼會(huì)報(bào)錯(cuò),因?yàn)閌return`不能單獨(dú)使用,必須在函數(shù)中使用。
如果`eval`的參數(shù)不是字符串,那么會(huì)原樣返回:
```javascript
eval(123) // 123
```
`eval`沒(méi)有自己的作用域,都在當(dāng)前作用域內(nèi)執(zhí)行,因此可能會(huì)修改當(dāng)前作用域的變量的值,造成安全問(wèn)題:
```javascript
var a = 1;
eval('a = 2');
a // 2
```
為了防止這種風(fēng)險(xiǎn),JavaScript 規(guī)定,如果使用嚴(yán)格模式,`eval`內(nèi)部聲明的變量,不會(huì)影響到外部作用域:
```javascript
(function f() {
? 'use strict';
? eval('var foo = 123');
? console.log(foo);? // ReferenceError: foo is not defined
})()
```
> 總之,`eval`的本質(zhì)是在當(dāng)前作用域之中,注入代碼。由于安全風(fēng)險(xiǎn)和不利于 JavaScript 引擎優(yōu)化執(zhí)行速度,所以一般不推薦使用。通常情況下,`eval`最常見(jiàn)的場(chǎng)合是解析 JSON 數(shù)據(jù)的字符串,不過(guò)正確的做法應(yīng)該是使用原生的`JSON.parse`方法。
`eval`的別名調(diào)用的形式五花八門(mén),只要不是直接調(diào)用,都屬于別名調(diào)用,因?yàn)橐嬷荒芊直鎌eval()`這一種形式是直接調(diào)用。
---
## 數(shù)組
### 數(shù)組的本質(zhì)