JavaScript 的原型鏈

前言
什么是原型

在js中有一句話叫萬物皆對(duì)象,每個(gè)對(duì)象都有原型。我們創(chuàng)建函數(shù),如果采用new的方式調(diào)用,當(dāng)然這種調(diào)用方式有個(gè)名字叫實(shí)例化。
// 創(chuàng)建一個(gè)函數(shù)function B(name) {this.name = name;};// 實(shí)例化var bb = new B('實(shí)例化的b');console.log(bb.name); // 實(shí)例化的b;
如上面的代碼,bb是通過B實(shí)例化之后得到的對(duì)象。在這里B就是一個(gè)構(gòu)造器,他所擁有的名字(this.name)屬性會(huì)帶給bb;這也符合之前杯子的例子,杯子的屬性會(huì)從構(gòu)造器中獲得。
假如我們想要做出來的bb具有一定的功能,那么就需要在原型上下功夫了。根據(jù)上面構(gòu)造器和原型的關(guān)系。我們可以這樣做。
// 創(chuàng)建一個(gè)函數(shù)function B(name) {this.name = name;};// 在原型上添加一個(gè)方法B.prototype.tan = function() {alert('彈出框');}// 實(shí)例化var bb = new B('實(shí)例化的b');console.log(bb.name); // 實(shí)例化的b;bb.tan(); // alert('彈出框');
在上面的代碼中,我們?cè)贐的原型上添加了一個(gè)tan的方法,在實(shí)例化出來的bb也具備了這個(gè)方法。這里我們就簡(jiǎn)單實(shí)現(xiàn)了一個(gè)類。
用下面一張圖,說明一下。實(shí)例對(duì)象(bb), 原型(prototype), 構(gòu)造函數(shù)(constructor)的關(guān)系。

B是我們構(gòu)造的一個(gè)類,這里稱為構(gòu)造函數(shù)。他用prototype指向了自己的原型。而他的原型也通過constructor指向了它。
B.prototype.constructor === B; // true;
bb和B沒有直接的關(guān)聯(lián),雖然B是bb的構(gòu)造函數(shù),這里用虛線表示。bb有一個(gè)__ proto__屬性,指向了B的prototype
bb.__ proto__ === B.prototype; // true;bb.__ proto__.constructor = B; // true;
總之
1,每創(chuàng)建一個(gè)函數(shù)B,就會(huì)為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象;
2,原型對(duì)象會(huì)默認(rèn)去取得constructor屬性,指向構(gòu)造函數(shù)。
3,當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例bb后,該實(shí)例的內(nèi)部將包含一個(gè)指針__ proto__,指向構(gòu)造函數(shù)的原型對(duì)象。
默認(rèn)原型
我們知道,所有引用對(duì)象都默認(rèn)繼承了Object,所有函數(shù)的默認(rèn)原型都是Object的實(shí)例。
之前說過構(gòu)造函數(shù)和原型之間具備對(duì)應(yīng)關(guān)系,如下:

既然函數(shù)的默認(rèn)原型都是Object的實(shí)例,B的原型對(duì)象也應(yīng)該是Object的實(shí)例子,也就是說。B的原型的__ proto__應(yīng)該指向Objct的原型。

Object的原型對(duì)象的原型是最底部了,所以不存在原型,指向NULL;
console.log(Object.prototype.__ proto__); // null;

Function對(duì)象
我們知道,函數(shù)也是對(duì)象,任何函數(shù)都可以看作是由構(gòu)造函數(shù)Function實(shí)例化的對(duì)象,所以Function與其原型對(duì)象之間也存在如下關(guān)系

如果將Foo函數(shù)看作實(shí)例對(duì)象的話,其構(gòu)造函數(shù)就是Function(),原型對(duì)象自然就是Function的原型對(duì)象;同樣Object函數(shù)看作實(shí)例對(duì)象的話,其構(gòu)造函數(shù)就是Function(),原型對(duì)象自然也是Function的原型對(duì)象。

如果Function的原型對(duì)象看作實(shí)例對(duì)象的話,如前所述所有對(duì)象都可看作是Object的實(shí)例化對(duì)象,所以Function的原型對(duì)象的__ proto __指向Object的原型對(duì)象。

到這里prototype,__ proto __, constructor三者之間的關(guān)系我們就說完了。
實(shí)現(xiàn)繼承
function Animal() {this.type = '動(dòng)物';}Animate.prototype.eat = function() {console.log('吃食物');}
上面定義了一個(gè)動(dòng)物類,作為父類。
function Cat(name) {this.name = name || ‘小貓’;}
定義了一個(gè)貓作為子類,這里我們要繼承動(dòng)物類的eat方法和type屬性
function Cat(name){Animal.call(this);this.name = name || '小貓';}
在實(shí)例化Cat時(shí)通過call執(zhí)行了Animal類, 這樣Animal中的this就被修改為當(dāng)前Cat的this。所有的屬性也會(huì)加在Cat上。
(function(){// 創(chuàng)建一個(gè)沒有實(shí)例方法的類var Super = function(){};Super.prototype = Animal.prototype;//將實(shí)例作為子類的原型Cat.prototype = new Super();})();
通過寄生方式,砍掉父類的實(shí)例屬性,這樣,在調(diào)用兩次父類的構(gòu)造的時(shí)候,就不會(huì)初始化實(shí)例方法/屬性。而父類的方法仍舊可以賦值給子類。
Cat.prototype = new Super(); 可以實(shí)現(xiàn)方法的繼承,是因?yàn)椋鶕?jù)前面的知識(shí)我們知道 new Cat()的__ proto __是指向 Cat的原型的。
(new Cat()).__ proto __ === Cat.prototype; // true
new Cat()所有的方法都是從原型上取到的。
我們通過 Cat.prototype = new Super(); 公式變成了。
(new Cat()).__ proto __ = Cat.prototype = new Super();
所以現(xiàn)在(new Cat()).__ proto __ 指向了 Super的prototype。也就是new Cat的方法是繼承自Super.prototype。
Super.prototype又在前一句等于Animal.prototype。所以實(shí)現(xiàn)了Cat繼承Animal。
這里我們就實(shí)現(xiàn)了js屬性和方法的繼承。不過還在最后一個(gè)小問題。
我們知道 prototype 和 constructor 是相互指向的。
Cat.prototype.constructor 應(yīng)該等于 Cat;
但是隨著我們的修改了Cat.prototype = Super.prototype;
現(xiàn)在Cat.prototype.constructor是等于Super的。
所以我們還應(yīng)該糾正這個(gè)問題,一句話搞定。
Cat.prototype.constructor = Cat; // 需要修復(fù)下構(gòu)造函數(shù)
以上就是js的原型繼承,完整代碼如下。
// 創(chuàng)建一個(gè)父類function Animal() {this.type = '動(dòng)物';}// 給父類添加一個(gè)方法Animate.prototype.eat = function() {console.log('吃食物');}// 創(chuàng)建一個(gè)子類function Cat(name){// 繼承Animal的屬性Animal.call(this);this.name = name || '小貓';}// 繼承 Animal 的方法(function(){// 創(chuàng)建一個(gè)沒有實(shí)例方法的類var Super = function(){};Super.prototype = Animal.prototype;//將實(shí)例作為子類的原型Cat.prototype = new Super();})();// 修正構(gòu)造函數(shù)Cat.prototype.constructor = Cat;
好啦,js的繼承原理和prototype,proto, constructor之間的關(guān)系我們就說完了,ES6底層的實(shí)現(xiàn)方式原理基本相同。
學(xué)習(xí)更多技能
請(qǐng)點(diǎn)擊下方公眾號(hào)
![]()

