了解JavaScript原型鍊
(一)__proto__的預設指向
定義
雖然JavaScript裡一切皆對象,但為了了解原型鍊系統,我們需要将JavaScript的對象分為對象和函數兩大類。在此基礎上,JavaScript的原型鍊邏輯遵從以下通用規則:
通用規則
- 對象有__proto__屬性,函數有prototype屬性;
- 對象由函數生成;
- 生成對象時,對象的__proto__屬性指向函數的prototype屬性。
- 在沒有手動修改__proto__屬性的指向時,以上三條便是JavaScript預設原型鍊指向邏輯。
為了讓原型鍊有終點,在原型鍊的最頂端,JavaScript規定了Object.prototype.proto === null。
逐漸深入的詳細解釋一下:
1.一般情況
建立空對象時,實際上我們是用Object函數來生成對象的:
>var o = {}
>o.__proto__ === Object.prototype
true
我們也可以顯式的使用Object函數來建立對象:
>var o = Object()
o.__proto__ === Object.prototype
true
當我們使用函數來建立自定義的對象時,上面的規則同樣适用:
>function MyObj(){}
>typeof MyObj
"function"
>var mo = new MyObj()
>mo.__proto__ === MyObj.prototype
true
2.函數對象
既然JavaScript裡“一切皆對象”,那函數自然也是對象的一種。對于函數作為對象來說,上面的規則同樣适用:
函數對象都是由Function函數生成的:
>function fn(){}
>fn.__proto__ === Function.prototype
true
可以看到,把函數當做對象時,生成它的函數就是 Function函數。那Function函數本身呢?同樣适用!
Function函數本身作為對象時,生成它的函數是他自身!
>Function.__proto__ === Function.prototype
true
同樣我們知道,Object函數也是一個函數對象,那麼它是否符合上面的規則呢?當然!
Object函數既然是函數,那生成它的函數自然是Function函數咯:
>Object.__proto__ === Function.prototype
true
3.prototype是誰?
好了,現在我們知道,對象的__proto__屬性是從生成它的函數的prototype那裡得來的,那我們不禁要問,函數的prototype又是誰?
一般函數預設的prototype是系統自動生成的一個對象:
>function fn(){}
>typeof fn.prototype
"object"
>fn.prototype
{constructor: ƒ}
constructor: ƒ fn()
__proto__: Object
>fn.prototype.constructor === fn
true
>fn.prototype.__proto__ === Object.prototype
true
一般函數預設的prototype是一個類型為"object"的對象,它有兩個屬性:constructor和 proto。其中constructor屬性指向這個函數自身,__proto__屬性指向Object.prototype,這說明一般函數的prototype屬性是由Object函數生成的。
4.特殊情況
前面我們特别強調了是一般函數,那不一般的函數是誰呢?當然是Object函數和Function函數!
先來看Object.prototype:
>typeof Object.prototype
"object"
>Object.prototype
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
可以看到Object函數的prototype屬性也是一個類型為"object"的對象,但和一般函數的預設prototype屬性不一樣的是,它多了一大堆方法,這些方法都是JavaScript對象的系統預設方法。
再仔細看,好像少了什麼,對了,Object函數的prototype屬性裡沒有__proto__屬性,我們試着把它的__proto__屬性打出來看看:
>Object.prototype.__proto__
null
這就是Object函數特殊情況了:Object.prototype.proto === null,我們知道,這就是JavaScript原型鍊的終點了。