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); // male

  而Person.prototype = {...}后Person.prototype引用了一個新的對象,如上圖3操作后Person.prototype引用了對象2,但是實例a還是引用在原來的對象1上。

總結

  javascript中每個對象除了本身的屬性外,還有一個__proto__屬性,繼承了父對象的方法和屬性(形成原型鏈);而每個函數有個prototype屬性,該屬性值是個對象,該對象函數自定義的一些屬性方法外,還有兩個屬性,constructor(其值一般為函數本身)和__proto__(其值繼承自父對象)。

  其實樓主對於以上了解的也很淺顯,歡迎指導拍磚~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM