天天看點

JavaScript原型與原型鍊以及執行個體

談到JavaScript的原型鍊,需要思考的幾個問題:

1,什麼是原型鍊?

2,原型鍊有什麼作用?

在JavaScript中不像其他語言有類的概念,提到JavaScript,想到的都是函數,函數無非兩個用途:

1,像一般函數一樣去調用它。

2,作為函數原型的構造函數去new它。

JavaScript原型

什麼是JavaScript原型?

JavaScript每聲明一個function,都附帶着prototype原型,prototype原型是函數的一個預設屬性,在函數的建立過程中由JavaScript編譯器自動添加。

簡言之:每當建立一個function對象的時候,就有一個原型prototype。

栗子:

JavaScript原型與原型鍊以及執行個體

那麼,建立一個函數這個過程到底發生了些什麼呢?

再看一個栗子:

function a(){
    this.name="a";
}
           

過程:

1、它會建立一個函數對象 也就是a 本身(一個構造函數)。

2、它會建立一個原型對象A(用A來表示)。

3、函數對象會有一個prototype指針,它指向了對應的原型對象,這裡就指向了A。

4、原型對象A中有一個construtor指針,指向它的構造函數,這裡就指向了函數a。

這個構造函數a有什麼作用呢?或者說怎麼去使用它呢?

答案就是 —– new。

比如:

function a(){  
    this.name = 'a';  
}  

var a1 = new a();  
           

這裡,a1就是調用原型對象(通過prototype指針)裡面構造函數(constructor)建立一個新的對象執行個體。

如果去修改原型對象中的屬性,那麼以它為“蒙版”執行個體化出來的a1又會怎麼樣呢?

看一個栗子:

function a(){  
    this.name = 'a';  
}  

var a1 = new a();  
a.prototype.age = ;
console.log(a1.age);       // 18
           

為什麼a1對象裡明明沒有定義age屬性,确得到了18這個值呢?

原因是新執行個體化的對象a1内部有一個看不見的 _proto_ 指針,指向原型對象A。

JavaScript原型與原型鍊以及執行個體

在通路屬性的時候,會先在a1對象内部中尋找,如果沒有,就會順着 _proto_ 指向的對象裡面去尋找,這裡會到原型對象A中尋找,找到就傳回值,沒有找到就傳回undefined,反正就是逐級向上查找。到這裡,就可以看到 原型鍊 的影子了。

附上另外說明:

1、一個沒有繼承操作的函數的 _proto_ 都會指向Object.prototype,而Object.prototype都會指向null。

2、是以,也可以很明顯知道,為何null是原型鍊的終端。

再來看個栗子:

function a(){
    this.name="a";
}

a.prototype.age = ;

function b(){
    this.apple="5s";
}

b.prototype = new a();    // b 繼承 a
b.prototype.foot = "fish";

function c(){
    this.computer = "MAC";
}

c.prototype = new b();   // c 繼承 b , b 繼承 a  

var d = new c(); 

console.log(d.apple);    //  5s
console.log(d.name);     //  a
console.log(d.foot);     //  fish
console.log(d.age);      //  18
           

從這個栗子就能很形象的看出來“一層層往上找”的思想了吧!c繼承b , b繼承a ,無形之中就連成了一條“線”,這條線就是“原型鍊”。

代碼延伸一下:

function b(){
    this.apple="5s";
}

b.prototype.foot = "fish";

function c(){
    this.computer = "MAC";
}

c.prototype.c1=function(){
    console.log("c1存在");
}
c.prototype = new b();   // c 繼承 b

c.prototype.c2=function(){
    console.log("c2存在");
}

var d = new c(); 

console.log(d.apple);          //  5s
console.log(d.foot);           //  fish
console.log(d.computer);       //  MAC
d.c2();                        //  "c2存在"
d.c1();             //  Uncaught TypeError: d.c1 is not a function
           
JavaScript原型與原型鍊以及執行個體

可以看到,構造函數c的執行個體d調用 c1() 報錯了,很明顯 c1() 已經不存在于原型對象C中了。這是為什麼呢?

原因是 “c.prototype = new b(); // c 繼承 b” ,這使得原型對象C的指向發生了變化,原來裝有 c1() 的原型對象被抛棄了。

繼續更新,探究一下c繼承b之後還發生了哪些變化:

栗子:

function b(){
    this.apple="5s";
}

b.prototype.foot = "fish";

function c(){
    this.computer = "MAC";
}

c.prototype.c1=function(){
    console.log("c1存在");
}
c.prototype = new b();   // c 繼承 b

c.prototype.c2=function(){
    console.log("c2存在");
}

console.log(c.prototype.constructor);     //  新增代碼 
c.prototype.constructor = c;              //  新增代碼 
console.log(c.prototype.constructor);     //  新增代碼 

var d = new c(); 

console.log(d.apple);          //  5s
console.log(d.foot);           //  fish
console.log(d.computer);       //  MAC
d.c2();                        //  "c2存在"
d.c1();             //  Uncaught TypeError: d.c1 is not a function
           
JavaScript原型與原型鍊以及執行個體

可以看出,c繼承了原型對象B的執行個體化之後,原型對象C 的構造函數指針指向了 構造函數b 。

這是不是說 var d = new c(); 在構造c的執行個體化d 的時候指向的是 構造函數b呢?答案是否定的。因為d.computer列印出來的值依然是”MAC”,是以仍然是調用的構造函數b進行的執行個體化。雖然不影響d的執行個體化,但是影響到了d.constructor的指向,d.constructor也指向了構造函數b。

是以,建議在c繼承b之後,對原型對象C 的構造函數指針進行修正操作:c.prototype.constructor = c;,修正之後的指向:

JavaScript原型與原型鍊以及執行個體
描述不對的地方,多包涵。

繼續閱讀