原型鏈的2個重要點
-
__proto__ === constructor.prototype
砂心; - 對象查找一個屬性的時候,會首先檢查自身是否有這個屬性夺欲,如果沒有谓罗,則沿著原型鏈(也就是
__proto__
)往上尋找,直到尋找到null
顶别。
繼承
繼承是為了復用對象谷徙。下面是幾種繼承方法:
-
原型鏈繼承
function A(name) { this.name = name; this.city = ['Guangzhou', 'Shenzhen']; }; A.prototype.getName = function () { return this.name }; function B(sex) { this.sex = sex; } B.prototype = new A('Bobo'); var c = new B('male'); c.getName(); // Bobo // 缺陷 c.city.push('Shanghai'); var d = new B('jojo'); d.city; // [ 'Guangzhou', 'Shenzhen', 'Shanghai' ]
解釋:
根據(jù)
__proto__ === constructor.prototype
可以知道,子類實例(也就是c)可以通過__proto__
訪問到子類(也就是B)的prototype
驯绎,由于B.prototype = new A('Bobo')
, 子類的實例能訪問到父類(也就是A)的實例完慧。這樣,子類實例(c)就能訪問到父類的私有方法剩失,然后通過父類實例(也就是new A('Bobo')
或者B.prototype
或者c.__proto__
)的__proto__
(也就是new A('bobo').__ptoto__
或者B.prototype.__proto__
或者c.__proto__.__proto__
) 訪問到父類的原型上的方法屈尼。子類繼承父類的屬性和方法是將父類的私有屬性和公有方法都作為自己的公有屬性的方法册着。如果私有屬性是引用類型的,實例進行修改脾歧,將會導致下一個實例的對應的私有屬性發(fā)生變化甲捏。
優(yōu)勢:可以函數(shù)共享。
缺陷:當上一個實例修改了原型中的
引用類型
的值鞭执,會影響到下一個實例摊鸡。 -
構造函數(shù)繼承
function A (name) { this.city = ['Guangzhou', 'Shenzhen']; this.name = name; } A.prototype.getName = function() { return this.name; }; function B(name, sex) { A.call(this, name); this.sex = sex; } var c = new B('coco', 'male'); // c.getName(); // 無法共享方法 c.name; // coco c.sex; // male c.city.push('Shanghai'); var d = new B('jojo', 'female'); d.city; // [ 'Guangzhou', 'Shenzhen' ]
解釋:
為什么叫構造函數(shù)繼承呢,是因為對于新new對象(比如例子的c)來說蚕冬,B就是他的構造函數(shù)免猾,在B內進行繼承,所以叫構造函數(shù)繼承囤热。
構造函數(shù)繼承猎提,就是在子類函數(shù)里面使用
call
,apply
來改變this
指向,讓它指向父類旁蔼,改變上下文環(huán)境锨苏。由于父類方法是掛載在prototype
所以子類無法獲取到掛載的方法。優(yōu)勢:屬性是實例借助構造函數(shù)自己生成的棺聊,所以各個實例的屬性是各自獨立的伞租。創(chuàng)建子類實例的時候,可以向父類傳遞參數(shù)限佩,可以實現(xiàn)多重繼承(call多個對象)
缺陷:實例是子類實例葵诈,不是父類的實例;無法繼承父類原型的屬性和方法祟同;不能復用作喘,因為每個子類都有父類實例函數(shù)的副本。
-
結合繼承
function A (name) { this.city = ['Guangzhou', 'Shenzhen']; this.name = name; } A.prototype.getName = function() { return this.name; }; function B(name, sex) { A.call(this, name); // 調用了一次父類的構造函數(shù)晕城,會被下次調用屏蔽 this.sex = sex; } B.prototype = new A(); // 又調用了一次父類的構造函數(shù)泞坦, A原型上的屬性其實是沒有必要的 // 可以用 B.prototype = A.prototype來避免2次調用父類構造函數(shù), // 但是這樣砖顷,造成了子類原型鏈上的混亂贰锁,無法知道實例是子類創(chuàng)造的還是父類創(chuàng)造的,它們指向同一個 // 可以用 Object.create(A.prototype) // B.prototype.constructor = B滤蝠;來替代豌熄,不過這種方法可以參照圣杯模式 var c = new B('dodo', 'male'); c.getName(); // dodo c.city.push('Shanghai'); var d = new B('eoeo', 'female'); d.city; // [ 'Guangzhou', 'Shenzhen' ]
顯而易見地,結合繼承結合了原型繼承和構造函數(shù)繼承的優(yōu)點几睛。
缺陷:子類原型上有一份多余的父類實例屬性房轿,父類構造函數(shù)被調用了2次,生成了2份,子類實例上的那一份屏蔽了子類原型上的囱持。
-
寄生結合繼承(圣杯模式)
// 寫法1 function inherit(c, p) { function f() {} f.prototype = p.prototype; c.prototype = new f(); c.prototype.constructor = c; // uber是超類夯接,儲存這個目標是繼承于誰,可寫可不寫 c.prototype.uber = p.prototype; } // 寫法2 var inherit2 = (function(c, p){ var F = function(){}; return function(c, p) { F.prototype = p.prototype; c.prototype = new F(); c.uber = p.prototype; c.prototype.constructor = c; } })(); function A (name) { this.city = ['Guangzhou', 'Shenzhen']; this.name = name; } A.prototype.getName = function() { return this.name; }; function B(name, sex) { this.sex = sex; this.name = name; } inheirt(B, A); var c = new B('fofo', 'male'); c.name; // fofo c.sex; // male // c.city; // 沒有這個屬性 c.getName(); // fofo
創(chuàng)建一個中間對象f纷妆,將父類的
prototype
指向中間對象f的prototype
;然后子類實現(xiàn)f的原型繼承盔几,再矯正子類的constructor
,這樣不會造成原型鏈的混亂掩幢。從而隔開了子類修改原型對父類造成的影響逊拍。但是無法繼承父類的屬性(比如父類的city屬性);關鍵點在于
__proto__===constructor.prototype
的理解际邻。對象會循著__proto__
向上尋找屬性芯丧,如果自身沒有的話。這樣當A.prototype = new B()
的時候世曾,var c = new A()
當c
執(zhí)行一個方法或者查找一個屬性沒有找到的時候缨恒,c.__proto__ -> A.prototype(new B()) -> A.prototype.__proto__ -> B.prototype -> ... -> null
; 所以這樣是可以實現(xiàn)原型鏈查找,這也是繼承的體現(xiàn)之處轮听。