[[prototype]]
JavaScript中的對象有一個特殊的 [[prototype]] 內置屬性,其實就是對於其他對象的引用。幾乎所有的對象在創建時 [[prototype]] 屬性都會被賦予一個非空的值。
Object.create(..)會創建一個對象並把這個對象的 [[prototype]] 關聯到指定的對象。
var obj = {
a: 2
};
var obj1 = Object.create(obj);
console.log(obj1.a); //2
Object.prototype
所有普通的 [[prototype]] 鏈最終都會指向內置的 Object.prototype。
"類" 函數
所有的函數默認都會擁有一個名爲prototype的共有的不可枚舉的屬性,它會指向另一個對象。
function Foo() {
}
Foo.prototype;
這個對象通常被稱爲Foo的原型。
這個對象是在調用 new Foo()時創建的,最後會被(有點武斷地)關聯到這個“Foo.prototype”對象上。
new Foo()這個函數調用實際上並沒有直接創建關聯,這個關聯只是一個意外的副作用。new Foo()只是間接完成了我們的目標:一個關聯到其他對象的新對象。
“構造函數”
最準確的解釋: 所有帶new的函數調用。
函數不是構造函數,但是當且僅當使用new時,函數調用會變成“構造函數調用”。
function Foo() {
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
Foo.prototype默認有一個公有並且不可枚舉的屬性.constructor,這個屬性引用的是對象關聯的函數。
通過“構造函數”調用new Foo()創建的對象也有一個.constructor屬性,指向“創建這個對象的函數”。
實際上,.constructor引用同樣被委託給了Foo.prototype,而Foo.prorotype.constructor默認指向Foo。
.constructor只是通過默認的 [[prototype]] 委託指向Foo。
function Foo() {
}
Foo.prototype = {};
var a = new Foo();
console.log(a.constructor === Foo); // false
console.log(a.constructor === Object); // true
a沒有.constructor屬性,所以它會委託 [[prototype]] 鏈上的Foo.prototype,但這個對象也沒有.constructor屬性;所以它繼續委託,這次委託給委託鏈頂端的Object.prototype,這個對象有.constructor屬性,指向內置的object(..)函數。
(原型)繼承
// ES6之前需要創建一個新對象然後把舊對象拋棄掉,不能直接修改已有的默認對象Bar.prototype
Bar.prototype = Object.create( Foo.prototype);
// ES6開始可以直接修改現有的 Bar.prototype
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
在傳統的面向類環境中,檢查一個實例的繼承祖先(js中的委託關聯)通常被稱爲內省(或反射)。
a instanceof Foo;
instanceof 操作符的左操作數是一個對象,右操作符是一個函數,
回答的是:在a的整條[[prototype]]鏈中是否有指向Foo.prototype的對象。
Foo.prototype.isPrototypeOf(a);
isPrototypeOf 回答的是: 在a的整條 [[prototype]] 鏈中是否出現過Foo.prototype
在ES5中,標準地獲取一個對象的 [[prototype]] 鏈的方法:
Object.getPrototypeOf(a);
絕大多數(不是所有!)瀏覽器也支持一種非標準的方法來訪問內部 [[prototype]] 屬性:
a.__proto__ === Foo.prototype; // true
__proto__(在ES6之前並不是標準!)屬性“神奇地”引用了內部的 [[prototype]] 對象,和其它的常用函數( .toString( ) 、.isPrototypeOf( ) , 等等)一樣,存在於內置的Object.prototype中。(它們是不可枚舉的)。
__proto__看起來很像一個屬性,但實際上它更像一個getter/setter
.__proto__的實現大致上是這樣:
Object.defineProperty(Object.prototype, "__proto__", {
get: function() {
return Object.getPrototypeOf(this);
},
set: functional(o) {
Object.setPrototypeOf(this, o);
return o;
}
})
因此,訪問(獲取值)a.__proto__時,實際上是調用了a.__proto__()(調用getter函數)。雖然getter函數存在於Object.prototype對象中,但是它的this指向對象a,所以和Object.getPrototypeOf(a)結果相同。
對象關聯
[[prototype]] 機制就是存在於對象中的一個內部鏈接,它會引用其他對象。
這個鏈接的作用:如果在對象上沒有找到需要的屬性或者方法引用,引擎會繼續在 [[prototype]] 關聯的對象上繼續查找。 同理,如果在後者中也沒有找到需要的引用就會繼續查找它的 [[prototype]] , 以此類推。 這一系列對象的鏈接被成爲“原型鏈”。
Object.create(..)會創建一個新對象並把它關聯到我們指定的對象,這樣我們就可以充分發揮 [[prototype]] 極致的威力(委託)並且避免不必要的麻煩(比如使用new的構造函數調用會生成.prototype和.constructor引用)
Object.create(null)會創建一個擁有空(null) [[prototype]] 鏈接的對象,這個對象無法進行委託。由於這個對象沒有原型鏈,所以instanceof操作符無法進行判斷,因此總是會返回false。這些特殊的空 [[prototype]] 對象通常被稱作“字典”,它們完全不會受到原型鏈的干擾,因此非常適合用來存儲數據。
Object.create() 的pollyfill代碼
// 部分實現了 Object.create(..)的功能
if(!object.create){
Object.create = function(o) {
function F(){}
F.prototype = o;
return new F();
}
}
這段polyfill代碼使用了一個一次性函數F,我們通過改寫它的 .prototype 屬性使其指向想要關聯的對象,然後再使用new F()來構造一個新對象進行關聯。