## 2.5 原型
### 2.5.1 [[Prototype]]
JavaScript 中的對象有一個特殊的`[[Prototype]]` 內置屬性,其實就是**對于其他對象的引用**。幾乎所有的對象在創建時[[Prototype]] 屬性都會被賦予一個非空的值。
~~~
var myObject = {
a:2
};
myObject.a; // 2
~~~
[[Prototype]] 引用有什么用呢?
當你試圖引用對象的屬性時會觸發`[[Get]]` 操作,比如`myObject.a`。對于默認的`[[Get]]` 操作來說,第一步是檢查對象本身是否有這個屬性,如果有的話就使用它。
但是如果a 不在myObject 中,就需要使用對象的[[Prototype]] 鏈了。對于默認的[[Get]] 操作來說,如果無法在對象本身找到需要的屬性,就會繼續訪問對象的[[Prototype]] 鏈:
~~~
var anotherObject = {
a:2
};
// 創建一個關聯到anotherObject 的對象
var myObject = Object.create( anotherObject );
myObject.a; // 2
~~~
現在myObject 對象的[[Prototype]] 關聯到了anotherObject。顯然myObject.a 并不存在,但是盡管如此,屬性訪問仍然成功地(在anotherObject 中)找到了值2。
但是,如果anotherObject 中也找不到a 并且[[Prototype]] 鏈不為空的話,就會繼續查找下去。這個過程會持續到找到匹配的屬性名或者查找完整條[[Prototype]] 鏈。如果是后者的話,[[Get]] 操作的返回值是`undefined`。
使用`for..in` 遍歷對象時原理和查找[[Prototype]] 鏈類似,任何可以通過原型鏈訪問到(并且是enumerable)的屬性都會被枚舉。使用`in `操作符來檢查屬性在對象中是否存在時,同樣會查找對象的整條原型鏈(無論屬性是否可枚舉):
~~~
var anotherObject = {
a:2
};
// 創建一個關聯到anotherObject 的對象
var myObject = Object.create( anotherObject );
for (var k in myObject) {
console.log("found: " + k);
}
// found: a
("a" in myObject); // true
~~~
因此,當你通過各種語法進行屬性查找時都會查找[[Prototype]] 鏈,直到**找到屬性或者查找完整條原型鏈**。
**1. Object.prototype**
所有普通的`[[Prototype]] `鏈最終都會指向內置的`Object.prototype`。由于所有的“普通”(內置,不是特定主機的擴展)對象都“源于”(或者說把[[Prototype]] 鏈的頂端設置為)這個`Object.prototype` 對象,所以它包含JavaScript 中許多通用的功能。比如說`.toString()` , `.valueOf()` , ` .hasOwnProperty(..)` 和下面介紹的` .isPrototypeOf(..)`。
**2. 屬性設置和屏蔽**
`2.3`說過,給一個對象設置屬性并不僅僅是添加一個新屬性或者修改已有的屬性值。
~~~
myObject.foo = "bar";
~~~
* 如果myObject 對象中包含名為foo 的普通數據訪問屬性,這條賦值語句只會修改已有的屬性值。
* 如果foo 不是直接存在于myObject 中,[[Prototype]] 鏈就會被遍歷,類似[[Get]] 操作。如果原型鏈上找不到foo,foo 就會被直接添加到myObject 上。(如果foo 存在于原型鏈上層,賦值語句my`Object.foo = "bar" `的行為就會有些不同)
* 如果屬性名foo 既出現在myObject 中也出現在myObject 的[[Prototype]] 鏈上層, 那么就會發生**屏蔽**。myObject 中包含的foo 屬性會屏蔽原型鏈上層的所有foo 屬性,因為myObject.foo 總是會選擇原型鏈中最底層的foo 屬性。
下面分析一下如果foo 不直接存在于myObject 中而是存在于原型鏈上層時myObject.foo = "bar" 會出現的三種情況。
* 1)如果在`[[Prototype]] `鏈上層存在名為`foo `的普通數據訪問屬性并且沒有被標記為只讀(`writable:false`),那就會**直接**在`myObject` 中添加一個名為`foo` 的新屬性,它是**屏蔽**屬性。
* 2)如果在`[[Prototype]]` 鏈上層存在`foo`,但是它被標記為只讀(`writable:false`),那么無法修改已有屬性或者在myObject 上創建屏蔽屬性。如果運行在嚴格模式下,代碼會拋出一個錯誤。否則,這條賦值語句會被忽略。總之,**不會發生屏蔽**。
* 3)如果在`[[Prototype]]` 鏈上層存在`foo` 并且它是一個`setter`,那就一定會調用這個`setter`。`foo `不會被添加到(或者說屏蔽于)`myObject`,也不會重新定義`foo` 這個`setter`。
~~~
如果你希望在第二種和第三種情況下也屏蔽foo,那就不能使用= 操作符來賦值,
而是使用Object.defineProperty(..)來向myObject 添加foo。
如果需要對屏蔽方法進行委托的話就不得不使用丑陋的顯式偽多態。通常來說,使用屏蔽得不償失,所以應當盡量避免使用。
~~~
有些情況下會**隱式產生屏蔽**,一定要當心。思考下面的代碼:
~~~
var anotherObject = {
a:2
};
var myObject = Object.create( anotherObject );
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "a" ); // false
myObject.a++; // 隱式屏蔽!
anotherObject.a; // 2
myObject.a; // 3
myObject.hasOwnProperty( "a" ); // true
~~~
盡管`myObject.a++` 看起來應該(通過委托)查找并增加`anotherObject.a `屬性,但是別忘了++ 操作相當于`myObject.a = myObject.a + 1`。因此++ 操作首先會通過[[Prototype]]查找屬性a 并從anotherObject.a 獲取當前屬性值2,然后給這個值加1,接著用**`[[Put]]`**將值3 賦給myObject 中新建的屏蔽屬性a。
修改委托屬性時一定要小心。如果想讓anotherObject.a 的值增加,**唯一**的辦法是`anotherObject.a++`。
### 2.5.2 “類”
在JavaScript 中,類無法描述對象的行,(因為根本就不存在類!)對象直接定義自己的行為。**JavaScript 中只有對象。**
**1. “類”函數**
JavaScript 中有一種奇怪的行為一直在被無恥地濫用,那就是**模仿類**。“類似類”的行為利用了函數的一種特殊特性:所有的函數默認都會擁有一個名為`prototype` 的公有并且不可枚舉的屬性,它會指向另一個對象:
~~~
function Foo() {
// ...
}
Foo.prototype; // { }
~~~
這個對象通常被稱為`Foo` 的**原型**,因為我們通過名為`Foo.prototype `的屬性引用來訪問它。
`Foo` 的原型這個對象是在調用new Foo()時創建的,最后會被(有點武斷地)關聯到這個“Foo 點prototype”對象上。
~~~
function Foo() {
// ...
}
var a = new Foo();
Object.getPrototypeOf( a ) === Foo.prototype; // true
~~~
調用new Foo() 時會創建a,其中的一步就是給a 一個內部的`[[Prototype]]` 鏈接,關聯到Foo.prototype 指向的那個對象。
在面向類的語言中,類可以被復制(或者說實例化)多次,是因為實例化(或者繼承)一個類就意味著“把類的行為復制到物理對象中”,對于每一個新實例來說都會重復這個過程。但是在JavaScript 中,并**沒有類似的復制機制**。你不能創建一個類的多個實例,只能創建多個對象,它們[[Prototype]] 關聯的是同一個對象。但是在默認情況下并**不會進行復制**,因此這些對象之間并不會完全失去聯系,它們是**互相關聯**的。
new Foo() 會生成一個新對象(我們稱之為a),這個新對象的內部鏈接[[Prototype]] 關聯的是Foo.prototype 對象。最后我們得到了兩個對象,它們之間互相關聯,就是這樣。我們并沒有初始化一個類,實際上我們并沒有從“類”中復制任何行為到一個對象中,只是讓兩個對象互相關聯。
new Foo() 這個函數調用實際上并沒有直接創建關聯,這個關聯只是一個意外的副作用。new Foo() 只是間接完成了我們的目
標:**一個關聯到其他對象的新對象**。
#### 關于名稱
在JavaScript 中,我們并不會將一個對象(“類”)復制到另一個對象(“實例”),只是將它們關聯起來。

這個機制通常被稱為**原型繼承**,它常常被視為動態語言版本的類繼承。這個名稱主要是為了對應面向類的世界中“繼承”的意義,但是違背(推翻)了動態腳本中對應的語義。
**繼承**意味著復制操作,JavaScript(默認)并不會復制對象屬性。相反,JavaScript 會在兩個對象之間創建一個關聯,這樣一個對象就可以通過**委托**訪問另一個對象的屬性和函數。**委托**這個術語可以更加準確地描述JavaScript 中對象的關聯機制。
**2. “構造函數”**
~~~
function Foo() {
// ...
}
var a = new Foo();
~~~
到底是什么讓我們認為Foo 是一個“類”呢?
* 關鍵字`new`,在面向類的語言中構造類實例時也會用到它。看起來我們執行了類的構造函數方法,Foo() 的調用方式很像初始化類時類構造函數的調用方式。
* 屬性`.constructor`
~~~
function Foo() {
// ...
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
~~~
Foo.prototype 默認(在代碼中第一行聲明時!)有一個公有并且不可枚舉的屬性`.constructor`,這個屬性引用的是對象關聯的函數(本例中是Foo)。此外,我們可以看到通過“構造函數”調用`new Foo() `創建的對象也有一個`.constructor `屬性,指向“創建這個對象的函數”。
~~~
實際上a 本身并沒有.constructor 屬性。而且,雖然a.constructor 確實指向Foo 函數,但是這個屬性并不是表示a 由Foo“構造”。
~~~
#### 構造函數還是調用
實際上,Foo 和程序中的其他函數沒有任何區別。函數本身并不是構造函數,然而,當在普通的函數調用前面加上new 關鍵字之后,就會把這個函數調用變成一個“構造函數調用”。實際上,new 會劫持所有普通函數并用構造對象的形式來調用它。
舉例來說:
~~~
function NothingSpecial() {
console.log( "Don't mind me!" );
}
var a = new NothingSpecial();
// "Don't mind me!"
a; // {}
~~~
`NothingSpecial `只是一個普通的函數,但是使用new 調用時,它就會構造一個對象并賦值給a,這看起來像是new 的一個副作用(無論如何都會構造一個對象)。這個調用是一個構造函數調用,但是NothingSpecial 本身并不是一個構造函數。換句話說,在JavaScript 中對于“構造函數”最準確的解釋是,所有帶new 的函數調用。函數不是構造函數,但是當且僅當使用new 時,函數調用會變成“**構造函數調用**”。
**3. 技術**
~~~
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
var a = new Foo( "a" );
var b = new Foo( "b" );
a.myName(); // "a"
b.myName(); // "b"
~~~
這段代碼展示了另外兩種“面向類”的技巧:
* 1)` this.name = name `給每個對象(也就是a 和b)都添加了.name 屬性,有點像類實例封裝的數據值。
* 2)`Foo.prototype.myName = ... `,它會給Foo.prototype 對象添加一個屬性(函數)。
在這段代碼中,看起來似乎創建a 和b 時會把Foo.prototype 對象復制到這兩個對象中,然而事實并不是這樣。在創建的過程中,a 和b 的內部[[Prototype]] 都會關聯到Foo.prototype 上。當a和b 中無法找到myName 時,它會(通過委托)在Foo.prototype 上找到。
#### 回顧“構造函數”
之前討論`.constructor` 屬性時,看起來`a.constructor === Foo `為真意味著a 確實有一個指向`Foo 的.constructor` 屬性,但是事實不是這樣。實際上,`.constructor` 引用同樣被**委托**給了`Foo.prototype`,而`Foo.prototype.constructor` 默認指向Foo。
舉例來說,`Foo.prototype` 的`.constructor` 屬性只是Foo 函數在聲明時的默認屬性。如果你創建了一個新對象并替換了函數默認的.prototype 對象引用,那么新對象并不會自動獲得.constructor 屬性。
思考下面的代碼:
~~~
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 創建一個新原型對象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
~~~
a1 并沒有`.constructor` 屬性,所以它會委托[[Prototype]] 鏈上的`Foo.prototype`。但是這個對象也沒有.constructor 屬性(不過默認的Foo.prototype 對象有這個屬性!),所以它會繼續委托,這次會委托給委托鏈頂端的`Object.prototype`。這個對象有`.constructor `屬性,指向內置的`Object(..) `函數。
當然,你可以給`Foo.prototype` 添加一個`.constructor` 屬性,不過這需要手動添加一個符合正常行為的不可枚舉屬性。
舉例來說:
~~~
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 創建一個新原型對象
// 需要在Foo.prototype 上“修復”丟失的.constructor 屬性
// 新對象屬性起到Foo.prototype 的作用
// 關于defineProperty(..),參見第3 章
Object.defineProperty( Foo.prototype, "constructor" , {
enumerable: false,
writable: true,
configurable: true,
value: Foo // 讓.constructor 指向Foo
} );
~~~
**修復.constructor 需要很多手動操作。所有這些工作都是源于把“constructor”錯誤地理解為“由……構造”!**
### 2.5.3(原型)繼承
~~~
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name,label) {
Foo.call( this, name );
this.label = label;
}
// 我們創建了一個新的Bar.prototype 對象并關聯到Foo.prototype
Bar.prototype = Object.create( Foo.prototype );
// 注意!現在沒有Bar.prototype.constructor 了
// 如果你需要這個屬性的話可能需要手動修復一下它
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
~~~
這段代碼的核心部分就是語句`Bar.prototype = Object.create( Foo.prototype )`。調用`Object.create(..)` 會憑空創建一個“新”對象并把新對象內部的`[[Prototype]]` 關聯到你指定的對象(本例中是Foo.prototype)。即**創建一個新的Bar.prototype 對象并把它關聯到Foo.prototype”**。
聲明function Bar() { .. } 時,和其他函數一樣,Bar 會有一個.prototype 關聯到默認的對象,但是這個對象并不是我們想要的Foo.prototype。因此我們創建了一個新對象并把它關聯到我們希望的對象上,直接把原始的關聯對象拋棄掉。
注意,下面這兩種方式是常見的錯誤做法,實際上它們都存在一些問題:
~~~
// 和你想要的機制不一樣!
Bar.prototype = Foo.prototype;
// 基本上滿足你的需求,但是可能會產生一些副作用 :(
Bar.prototype = new Foo();
~~~
* Bar.prototype = Foo.prototype 并不會創建一個關聯到Bar.prototype 的新對象,它只是讓Bar.prototype 直接引用Foo.prototype 對象。因此當你執行類似`Bar.prototype.myLabel = ...` 的賦值語句時會直接修改`Foo.prototype` 對象本身。顯然這不是你想要的結果,否則你根本不需要Bar 對象,直接使用Foo 就可以了,這樣代碼也會更簡單一些。
* `Bar.prototype = new Foo()` 的確會創建一個關聯到`Bar.prototype` 的新對象。但是它使用了`Foo(..) `的“構造函數調用”,如果函數Foo 有一些副作用(比如寫日志、修改狀態、注冊到其他對象、給this 添加數據屬性,等等)的話,就會影響到Bar() 的“后代”,后果不堪設想。
ES6 添加了輔助函數Object.setPrototypeOf(..),可以用標準并且可靠的方法來修改關聯。
我們來對比一下兩種把Bar.prototype 關聯到Foo.prototype 的方法:
~~~
// ES6 之前需要拋棄默認的Bar.prototype
Bar.ptototype = Object.create( Foo.prototype );
// ES6 開始可以直接修改現有的Bar.prototype
Object.setPrototypeOf( Bar.prototype, Foo.prototype );
~~~
如果忽略掉Object.create(..) 方法帶來的輕微性能損失(拋棄的對象需要進行垃圾回收),它實際上比ES6 及其之后的方法更短而且可讀性更高。不過無論如何,這是兩種完全不同的語法。
#### 檢查“類”關系
假設有對象a,如何尋找對象a 委托的對象(如果存在的話)呢?在傳統的面向類環境中,檢查一個實例(JavaScript 中的對象)的繼承祖先(JavaScript 中的委托關聯)通常被稱為**內省(或者反射)**。
~~~
function Foo() {
// ...
}
Foo.prototype.blah = ...;
var a = new Foo();
~~~
如何通過內省找出a 的“祖先”(委托關聯)呢?
* 第一種方法,站在“類”的角度來判斷:
~~~
a instanceof Foo; // true
~~~
`instanceof `操作符的左操作數是一個普通的對象,右操作數是一個函數。instanceof 回答的問題是:在a 的整條[[Prototype]] 鏈中是否有指向Foo.prototype 的對象?
可惜,這個方法只能處理對象(a)和函數(帶.prototype 引用的Foo)之間的關系。如果你想判斷兩個對象(比如a 和b)之間是否通過[[Prototype]] 鏈關聯,只用instanceof無法實現。
* 第二種方法
~~~
Foo.prototype.isPrototypeOf( a ); // true
~~~
在本例中,我們實際上并不關心(甚至不需要)Foo,我們只需要一個可以用來判斷的對象(本例中是Foo.prototype)就行。isPrototypeOf(..) 回答的問題是:在a 的整條[[Prototype]] 鏈中是否出現過Foo.prototype ?
第二種方法中并不需要間接引用函數(Foo),它的`.prototype` 屬性會被自動訪問。我們只需要兩個對象就可以判斷它們之間的關系。舉例來說:
~~~
// 非常簡單:b 是否出現在c 的[[Prototype]] 鏈中?
b.isPrototypeOf( c );
~~~
注意,這個方法并不需要使用函數(“類”),它直接使用b 和c 之間的對象引用來判斷它們的關系。
我們也可以直接獲取一個對象的[[Prototype]] 鏈。在ES5 中,標準的方法是:
~~~
Object.getPrototypeOf( a );
-------------------------------------------------------
Object.getPrototypeOf( a ) === Foo.prototype; // true
~~~
絕大多數瀏覽器也支持一種非標準的方法來訪問內部[[Prototype]] 屬性:
~~~
a.__proto__ === Foo.prototype; // true
~~~
`.__proto__`( 在ES6 之前并不是標準) 屬性引用了內部的`[[Prototype]]` 對象,如果你想直接查找(甚至可以通過`.__proto__.__ptoto__... `來遍歷)原型鏈的話,這個方法非常有用。和之前說過的`.constructor` 一樣,`.__proto__` 實際上并不存在于你正在使用的對象中(本例中是a)。實際上,它和其他的常用函數(.toString()、.isPrototypeOf(..),等等)一樣,存在于內置的Object.prototype 中。(而且是不可枚舉的)
`.__proto__ `看起來很像一個屬性,但是實際上它更像一個`getter/setter`:
~~~
Object.defineProperty( Object.prototype, "__proto__", {
get: function() {
return Object.getPrototypeOf( this );
},
set: function(o) {
// ES6 中的setPrototypeOf(..)
Object.setPrototypeOf( this, o );
return o;
}
} );
~~~
因此,訪問(獲取值)`a.__proto__ `時,實際上是調用了`a.__proto__()`(調用getter 函數)。雖然`getter` 函數存在于`Object.prototype` 對象中,但是它的this 指向對象a,所以和`Object.getPrototypeOf( a )` 結果相同。
### 2.5.4 對象關聯
`[[Prototype]]` 機制就是存在于對象中的一個內部鏈接,它會引用其他對象。
通常來說,這個鏈接的作用是:如果在對象上沒有找到需要的屬性或者方法引用,引擎就會繼續在[[Prototype]] 關聯的對象上進行查找。同理,如果在后者中也沒有找到需要的引用就會繼續查找它的[[Prototype]],以此類推。這一系列對象的鏈接被稱為**“原型鏈”**。
**1. 創建關聯**
~~~
var foo = {
something: function() {
console.log( "Tell me something good..." );
}
};
var bar = Object.create( foo );
bar.something(); // Tell me something good...
~~~
`Object.create(..) `會創建一個新對象(bar)并把它關聯到我們指定的對象(foo),這樣我們就可以充分發揮`[[Prototype]]` 機制的威力(委托)并且避免不必要的麻煩(比如使用new 的構造函數調用會生成.prototype 和.constructor 引用)。
**Object.create()的polyfill代碼**
Object.create(..) 是在ES5 中新增的函數,所以在ES5 之前的環境中(比如舊IE)如果要支持這個功能的話就需要使用一段簡單的polyfill 代碼, 它部分實現了Object.create(..) 的功能:
~~~
if (!Object.create) {
Object.create = function(o) {
function F(){}
F.prototype = o;
return new F();
};
}
~~~
這段polyfill 代碼使用了一個一次性函數F,我們通過改寫它的.prototype 屬性使其指向想要關聯的對象,然后再使用new F() 來構造一個新對象進行關聯。
標準ES5 中內置的Object.create(..) 函數還提供了一系列附加功能:
~~~
var anotherObject = {
a:2
};
var myObject = Object.create( anotherObject, {
b: {
enumerable: false,
writable: true,
configurable: false,
value: 3
},
c: {
enumerable: true,
writable: false,
configurable: false,
value: 4
}
});
myObject.hasOwnProperty( "a" ); // false
myObject.hasOwnProperty( "b" ); // true
myObject.hasOwnProperty( "c" ); // true
myObject.a; // 2
myObject.b; // 3
myObject.c; // 4
~~~
Object.create(..) 的第二個參數指定了需要添加到新對象中的屬性名以及這些屬性的屬性描述符。因為ES5 之前的版本無法模擬屬性操作符,所以polyfill 代碼無法實現這個附加功能。
**2. 關聯關系是備用**
看起來對象之間的關聯關系是處理“缺失”屬性或者方法時的一種備用選項。但這種說法不是`[[Prototype]] `的本質。
~~~
var anotherObject = {
cool: function() {
console.log( "cool!" );
}
};
var myObject = Object.create( anotherObject );
myObject.cool(); // "cool!"
~~~
由于存在[[Prototype]] 機制,這段代碼可以正常工作。但是如果你這樣寫只是為了讓myObject 在無法處理屬性或者方法時可以使用備用的anotherObject,那么你的軟件就會變得有點“神奇”,而且很難理解和維護。
~~~
var anotherObject = {
cool: function() {
console.log( "cool!" );
}
};
var myObject = Object.create( anotherObject );
myObject.doCool = function() {
this.cool(); // 內部委托!
};
myObject.doCool(); // "cool!"
~~~
這里我們調用的myObject.doCool() 是實際存在于myObject 中的,這可以讓我們的API 設計更加清晰(不那么“神奇”)。從內部來說,我們的實現遵循的是**委托設計模式**,通過`[[Prototype]] `委托到`anotherObject.cool()。`
換句話說,內部委托比起直接委托可以讓API 接口設計更加清晰。
- 前言
- 第一章 JavaScript簡介
- 第三章 基本概念
- 3.1-3.3 語法、關鍵字和變量
- 3.4 數據類型
- 3.5-3.6 操作符、流控制語句(暫略)
- 3.7函數
- 第四章 變量的值、作用域與內存問題
- 第五章 引用類型
- 5.1 Object類型
- 5.2 Array類型
- 5.3 Date類型
- 5.4 基本包裝類型
- 5.5 單體內置對象
- 第六章 面向對象的程序設計
- 6.1 理解對象
- 6.2 創建對象
- 6.3 繼承
- 第七章 函數
- 7.1 函數概述
- 7.2 閉包
- 7.3 私有變量
- 第八章 BOM
- 8.1 window對象
- 8.2 location對象
- 8.3 navigator、screen與history對象
- 第九章 DOM
- 9.1 節點層次
- 9.2 DOM操作技術
- 9.3 DOM擴展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件處理程序
- 10.3 事件對象
- 10.4 事件類型
- 第十一章 JSON
- 11.1-11.2 語法與序列化選項
- 第十二章 正則表達式
- 12.1 創建正則表達式
- 12.2-12.3 模式匹配與RegExp對象
- 第十三章 Ajax
- 13.1 XMLHttpRequest對象
- 你不知道的JavaScript
- 一、作用域與閉包
- 1.1 作用域
- 1.2 詞法作用域
- 1.3 函數作用域與塊作用域
- 1.4 提升
- 1.5 作用域閉包
- 二、this與對象原型
- 2.1 關于this
- 2.2 全面解析this
- 2.3 對象
- 2.4 混合對象“類”
- 2.5 原型
- 2.6 行為委托
- 三、類型與語法
- 3.1 類型
- 3.2 值
- 3.3 原生函數