JavaScript網(wǎng)道教程-學(xué)習(xí)筆記

# 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ì)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容