javascript構造函數和原型對象

首先明確一點,標準和實現是兩個概念。ECMAScript是標準,而不同瀏覽器廠商(vendor)可以根據此標準有不同的實現,這也是客戶端瀏覽器兼容性問題的原因之一。

__proto__與 prototype

在標準中,幾乎每個對象都有一個[[prototype]]屬性,且該屬性為隱藏屬性,并不對外暴露,指向這個對象的原型(即obj.prototype)。

但為了訪問需要,很多瀏覽器給對象提供了__proto__這一屬性,用以訪問上面提到的[[prototype]],因為瀏覽器實現并不一致,不建議直接使用__proto__。好在ES5(ECMAScript5.1版本)中增加了Object.getPrototypeOf()方法,用于獲得一個對象的[[prototype]],可用于替代__proto__。ES6中,則增加Object.setPrototypeOf(),可直接修改一個對象的[[prototype]]。

那問題來了,我們常說的prototype是什么呢?

 //構造函數的prototype
 console.log(Object.prototype);   //Object {}
 console.log(Array.prototype);    //[Symbol(Symbol.unscopables): Object]
 console.log(Function.prototype); //function () {}
 console.log(Number.prototype);   //Number {[[PrimitiveValue]]: 0}
 console.log(RegExp.prototype);   //Object {}
 console.log(String.prototype);   //String {length: 0, [[PrimitiveValue]]: ""}
 console.log(Boolean.prototype);  //Boolean {[[PrimitiveValue]]: false}
 //實例的prototype
 console.log(({name: 'yunmo'}).prototype);  //undefined
 console.log(([1,2]).prototype);   //undefined
 console.log((function(){}).prototype);   //Object {}
 console.log((1).prototype);  //undefined
 console.log(('yunmo').prototype);   //undefined
 console.log((true).prototype);    //undefined
 //實例的__proto__
 console.log(({name: 'yunmo'}).__proto__);  //Object {}
 console.log(([1,2]).__proto__);   //[Symbol(Symbol.unscopables): Object]
 console.log((function(){}).__proto__);   //function () {}
 console.log((1).__proto__);  //Number {[[PrimitiveValue]]: 0}
 console.log(('yunmo').__proto__);   //String {length: 0, [[PrimitiveValue]]: ""}
 console.log((true).__proto__);    //Boolean {[[PrimitiveValue]]: false}

如上代碼可以看出:

除了函數以外,實例對象是沒有prototype屬性的。只有函數才有prototype屬性。實例對象通過__proto__屬性([[prototype]])串起一個原型鏈,以此在原型鏈上查找屬性。即一個對象具有屬性__proto__,稱為隱式原型,該原型指向構造該對象的構造函數的原型,這保證了實例能夠訪問在構造函數原型中定義的屬性和方法。

每個函數都有一個屬性prototype。prototype的屬性值是一個對象,即屬性的集合,默認只有一個constructor屬性,指向這個函數本身。

如下圖:Super是一個構造函數,右側的方框就是它的原型。

constructor and prototype
function Yunmo() {
    console.log('I AM YUNMO');
}
var yunmo = new Yunmo();
console.log(yunmo.constructor === Yunmo);  //true 

原型作為對象,不只擁有默認的constructor屬性,還允許自定義屬性。如Object構造函數的prototype上,擁有幾個我們熟悉的屬性。

Object.prototype

一般來說有以下等式:

obj.__proto__ === obj.constructor.prototype

如果一個對象obj是通過Object.create函數構造出來的,那么obj.__proto__就不一定是obj.constructor.prototype。

var presetObject = {
    name: 'yun',
    age: 18
};
presetObject.prototype = Object.prototype;
var yunmo = Object.create(presetObject);
console.log(yunmo.__proto__ === yunmo.constructor.prototype);  //false

constructor和prototype

看一下官方定義:

constructor

The value of a constructor's prototype property is a prototype object that is used to implement inheritance and shared properties.

prototype

object that provides shared properties for other objects
When a constructor creates an object, that object implicitly references the constructor's prototype property for the purpose of resolving property references.
Alternatively, a new object may be created with an explicitly specified prototype by using the Object.create built-in function.

譯:構造函數的原型的值是個原型對象,用來實現繼承和屬性共享。
原型是用來為其他對象提供共享屬性的對象。
當構造函數創建了一個對象,該對象將隱性地引用構造函數原型屬性,為了解決屬性引用問題。
或者,還可以通過內置的Object.create函數,指定一個明確的原型來創建一個新的對象。

簡單來說:prototype使得我們有能力向對象添加屬性和方法。
var Coder = function (name, age) {
    this.name = name;
    this.age = age;
    this.learnCoding = function () {
        console.log(this.name + ' is learning coding while he is ' + this.age);
    }
};
var yun = new Coder('yun', 18);
var mo = new Coder('mo', 12);
yun.learnCoding();
mo.learnCoding();
console.log(yun.learnCoding === mo.learnCoding);   //false

如上:一個構造函數Yun生成了yun和mo兩個實例對象,分別擁有name、age屬性和learnCoding方法。然而yun.learnCoding !== mo.learnCoding,說明每次通過new實例化一個對象的時候,都會去創建一個learnCoding方法。但是learnCoding方法做的其實是同樣的事情,卻不能被兩個實例所共享,就造成了內存資源的浪費。

為了避免上述問題,我們需要借助構造函數的原型對象來實現繼承和屬性共享。這時候,prototype就相當于一個公共容器,存放一些公用的東西。

var Coder = function (name, age) {
    this.name = name;
    this.age = age;
};
Coder.prototype.learnCoding = function () {
    console.log(this.name + ' is learning coding while he is ' + this.age);
};
var yun = new Coder('yun', 18);
var mo = new Coder('mo', 12);
yun.learnCoding();
mo.learnCoding();
console.log(yun.learnCoding === mo.learnCoding);   //true

原型鏈

對象在調用一個方法時會先在自身尋找是否有該方法,若沒有,則通過內部的__proto__屬性去自己的構造函數原型上尋找,如果還找不到,繼續向上追溯,直到Object.prototype.__proto__ = null為止,這種依靠原型查找形成的查詢鏈條,即原型鏈。

原型鏈的查找是根據什么進行的?prototype?__proto__?還是constructor?

理論上來說,誰都不是。因為prototype、__proto__ 和constructor這三個屬性是由js引擎對外暴露的API。如本文一開始所說的,對象內部存在一個隱藏屬性[[prototype]],外部是無法直接訪問的,js引擎通過提供__proto__屬性使得代碼上能夠訪問[[prototype]]。因此,從實際可操作的層面來說,是根據__proto__屬性進行查找的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容