三者曖昧關系簡單整理
在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); // male
而Person.prototype = {...}后Person.prototype引用了一個新的對象,如上圖3操作后Person.prototype引用了對象2,但是實例a還是引用在原來的對象1上。
總結
javascript中每個對象除了本身的屬性外,還有一個__proto__屬性,繼承了父對象的方法和屬性(形成原型鏈);而每個函數有個prototype屬性,該屬性值是個對象,該對象函數自定義的一些屬性方法外,還有兩個屬性,constructor(其值一般為函數本身)和__proto__(其值繼承自父對象)。
其實樓主對於以上了解的也很淺顯,歡迎指導拍磚~