javascript中prototype、constructor以及__proto__之間的三角關係
三者曖昧關係簡單整理
在javascript中,prototype、constructor以及__proto__之間有著“著名”的剪不斷理還亂的三角關係,樓主就著自己對它們的淺顯認識,來粗略地理理以備忘,有不對之處還望斧正。
樓主的一句話理解就是:某個物件的constructor屬性返回該物件建構函式,其__proto__屬性是個物件,值和其建構函式的prototype屬性值一致。
先來說說prototype。prototype的解釋是“原型”,js的所有函式都有一個prototype屬性,其屬性值是個物件,原型物件初始值是空的(其實還有constructor屬性和__proto__屬性,只是不能被列舉,可以參考最下面留言),程式碼驗證:
function Person() {
this.name = 'hanzichi';
this.age = 10;
}
var num = 0;
for(var i in Person.prototype)
num++;
console.log(num); // 0
prorotype的主要作用簡單來說就是“便於方法或者屬性的重用”,可以利用prototype新增擴充套件屬性和方法,舉個簡單的例子:
function Person() { this.name = 'hanzichi'; this.age = 10; } Person.prototype.show = function() { console.log(this.name); }; Person.prototype.sex = 'male'; var a = new Person(); console.log(a.sex); // male a.show(); // hanzichi console.log(a.__proto__ === Person.prototype); // true 物件a的__proto__值取自建構函式Person的prototype值 console.log(a.constructor === Person); // true 物件a的建構函式是Person console.log(Person.prototype.constructor === Person); // true
如上,所有用Person函式構造的物件都可以使用sex值,呼叫show方法。
那麼問題來了,以上程式碼Person函式的prototype值是什麼?我們嘗試列印(console.log(Person.prototype)):
我們發現Person.prototype值確實是個物件,本身有兩個屬性(show和sex),這是自定義的,還有兩個屬性(constructor和__proto__ 可以通過hasOwnProperty驗證),一個是constructor,值為函式本身,另一個就是__proto__(據說ie不支援 樓主沒測試),其值為父函式的prototype屬性值。因為Person繼承自Object,故其__proto__值為Object的prototype屬性值,也就是說Person繼承了Object本身的所有方法,我們可以展開來細看:
說完prototype,我們再來看看物件例項有哪些屬性,我們也將它打印出來(console.log(a)):
我們看到例項物件a除了本身自帶屬性外,也有個屬性__proto__。
我們通過a.show()呼叫了show方法,但是show方法並不顯示在物件本身屬性裡(可通過hasOwnProperty驗證),為何能用?又是__proto__!所有的例項物件都有個__proto__屬性,我們看到它的值跟Person.prototype一致,也就是說例項a繼承了Person類的屬性方法。本身的屬性裡找不到show方法,自動去__proto__中尋找。
再說說constructor,其值返回該物件的建構函式:
console.log('string'.constructor); // function String() { [native code] }
console.log(new String('string').constructor); // function String() { [native code] }
console.log(/hello/.constructor); // function RegExp() { [native code] }
console.log([1, 2, 3].constructor); // function Array() { [native code] }
function A() {};
var a = new A();
console.log(a.constructor); // function A() {}
我們依舊看最前面的程式碼,a.constructor返回的是a的建構函式,也就是Person,其實例項物件a本身並沒有constructor屬性,但是a中的__proto__擁有constructor屬性,沒錯,a本身沒有,就會從它的__proto__屬性中尋找constructor方法,如果還沒有,就繼續從__proto__屬性的__proto__屬性中尋找... 這樣就構成了一個原型鏈。
我們似乎已經習慣了用new的方式來構造物件,其實new方式的核心實現要分為三個步驟,如下:
function Person() {
this.name = 'hanzichi';
this.age = 10;
}
Person.prototype.show = function() {
console.log(this.name);
};
Person.prototype.sex = 'male';
var a = {}; // 1
a.__proto__ = Person.prototype; // 2
Person.call(a); // 3
console.log(a.sex); // male
a.show(); // hanzichi
以上程式碼一目瞭然。先初始化一個空物件,然後空物件繼承建構函式的prototype值,最後call建構函式初始化。
再來看一段稍微複雜一點的程式碼:
function Person() {
this.name = 'hanzichi';
this.age = 10;
}
Person.prototype.show = function() {
console.log(this.name);
};
Person.prototype.sex = 'male';
function Child() {};
Child.prototype = new Person();
var a = new Child();
console.log(a);
這是一種簡單的繼承程式碼,先不管程式碼對錯,我們看看程式碼執行中發生了什麼。
Person函式前面已經分析了,我們又構造了一個Child函式,我們把一個例項化的Person物件(new Person())賦值給了Child的prototype屬性,也就是說Child繼承了Person的所有方法屬性,可以可以嘗試列印Child.prototype看看(其值其實也就是上圖中的a.__proto__),這樣Child的例項就能使用Person的屬性方法了。而以上例項物件a如果要呼叫show函式需經過兩個的__proto__原型鏈傳遞:
學以致用
試著來做道題看看有沒有理解:
function t1(name) {
if(name) this.name = name;
}
function t2(name) {
this.name = name;
}
function t3(name) {
this.name = name || "test";
}
t1.prototype.name = "hanzichi";
t2.prototype.name = "hanzichi";
t2.prototype.name = "hanzichi";
console.log(new t1().name, new t2().name, new t3().name);
答案:hanzichi undefined test
其實也就是本身有name屬性就用,沒有就從原型鏈中尋找。2和3的話都是本身已經擁有,而1是本身沒有name屬性。ps:沒有傳入實參而在函式中使用形參的話會被解釋成undefined。
恩,再看一道:
function Person() {
this.name = 'hanzichi';
this.age = 10;
}
Person.prototype.sex = 'female';
var a = new Person();
console.log(a.sex);
Person.prototype.sex = 'male';
console.log(a.sex);
Person.prototype = {
sex: 'female'
};
console.log(a.sex)
答案:female male male
為什麼會這樣?
一開始Person.prototype指向一個物件,如上圖1所示指向物件1,而初始化一個例項後,該例項的__proto__屬性同時指向了Person.prototype,即指向了Person.prototype指向的物件1,如上圖2,這時a.sex就會返回物件1中sex的值,而因為Person.prototype和a.__proto__引用同一個物件,所以都能改變該物件的值,如下程式碼也可以同時驗證:
function Person() {
this.name = 'hanzichi';
this.age = 10;
}
Person.prototype.sex = 'female';
var a = new Person();
a.__proto__.sex = 'male';
console.log(Person.prototype.sex); // male
console.log(a.sex); // mal
而Person.prototype = {...}後Person.prototype引用了一個新的物件,如上圖3操作後Person.prototype引用了物件2,但是例項a還是引用在原來的物件1上。
總結
javascript中每個物件除了本身的屬性外,還有一個__proto__屬性,繼承了父物件的方法和屬性(形成原型鏈);而每個函式有個prototype屬性,該屬性值是個物件,該物件函式自定義的一些屬性方法外,還有兩個屬性,constructor(其值一般為函式本身)和__proto__(其值繼承自父物件)。
其實樓主對於以上了解的也很淺顯,歡迎指導拍磚~