1. 程式人生 > >javascript中prototype、constructor以及__proto__之間的三角關係

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__(其值繼承自父物件)。

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