javaScript之原型

[[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()來構造一個新對象進行關聯。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章