談到JavaScript的原型鍊,需要思考的幾個問題:
1,什麼是原型鍊?
2,原型鍊有什麼作用?
在JavaScript中不像其他語言有類的概念,提到JavaScript,想到的都是函數,函數無非兩個用途:
1,像一般函數一樣去調用它。
2,作為函數原型的構造函數去new它。
JavaScript原型
什麼是JavaScript原型?
JavaScript每聲明一個function,都附帶着prototype原型,prototype原型是函數的一個預設屬性,在函數的建立過程中由JavaScript編譯器自動添加。
簡言之:每當建立一個function對象的時候,就有一個原型prototype。
栗子:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TM3YTN0cjM4EDMyUDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
那麼,建立一個函數這個過程到底發生了些什麼呢?
再看一個栗子:
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。
在通路屬性的時候,會先在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
可以看到,構造函數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
可以看出,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;,修正之後的指向:
描述不對的地方,多包涵。