首先要明白兩點:
1、非方法屬性每一個子類實例須要獨立
2、方法屬性每一個子類實例須要共享
爲何?
若是非方法屬性爲引用類型,且非方法屬性共享,在一個實例中改變,其餘實例中就會作出改變,這樣每一個實例就會相互影響,而方法屬性通常是不須要進行改變的,只是對方法調用。chrome
方法跟屬性分別能夠定義在構造函數內部跟prototype上。函數
繼承的目的是子類繼承父類的方法跟屬性,換句話說一些類的相同的方法屬性須要共享,將這些須要共享的方法屬性抽取到一個地方,這就是父類。this
代碼主要來自於紅寶書4spa
每一個函數都有個prototype
屬性,每一個對象都有__proto__
屬性(在chrome中表現如此,prototype也是如此) 如圖,屬性的查找會從當前層級依次向原型鏈上查找,直到查找到原型鏈的頂端null,具體可參考js proto
既然屬性的查找是按照原型鏈向上查找的,且繼承就是繼承父類的屬性跟方法,那麼就能夠利用這個特性,進行繼承。prototype
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; }; function SubType() { this.subproperty = false; } // 繼承SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; }; let instance = new SubType(); console.log(instance.getSuperValue()); // true 能夠正確調用父類的方法,拿到父類的屬性
原型雖然實現了繼承,可是仍是有缺點的
劣勢:code
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() {} // 繼承SuperType SubType.prototype = new SuperType(); let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black"; let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green,black";
爲何非方法屬性不寫在prototype上?對象
由於prototype上的屬性的共享的,在一個實例上改了該屬性,其餘實例的該屬性也會被改掉。blog
爲何方法不寫在構造函數內部?繼承
方法寫在子類內部:每次實例化構造函數,方法都是新的;方法只是用來調用,不須要修改,因此實例共享就好了。
方法寫在父類內部:不一樣的子類繼承父類都須要實例化父類;方法只是用來調用,不須要作修改,因此實例共享就好了,包括子類實例。若是子類須要修改父類方法,直接在子類中定義相同方法名,進行覆蓋就好了。ip
爲了解決父類中屬性爲引用類型致使子類實例化後,引用屬性共享的問題,跟父類構造函數沒法傳參的問題。引入了「盜用構造函數「方式實現繼承。思路是在子類構造函數中調用父類構造函數。
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { // 繼承SuperType SuperType.call(this); } let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black"; let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green";
instance1 instance2兩個實例就不會相互影響。
function SuperType(name) { this.name = name; } function SubType(name) { // 繼承SuperType並傳參 SuperType.call(this, name); // 實例屬性 this.age = 29; } let instance = new SubType("geek"); console.log(instance.name); // "geek"; console.log(instance.age); // 29
動態傳遞參數到父類構造函數
劣勢:
function SuperType(name) { this.name = name; } SuperType.prototype.say = function () { console.info("hello"); }; function SubType(name) { // 繼承SuperType並傳參 SuperType.call(this, name); // 實例屬性 this.age = 29; } let instance = new SubType("geek"); console.log(instance.name); // "geek"; console.log(instance.age); // 29 instance.say() // 獲取不到該函數
經過 new 實例化後,實例才能拿到prototype上的方法,a.__proto__===Animal.prototype
,因此instance.say()
不存在
每次實例化子類,都會調用父類構造函數,其內部定義的方法都是新的,佔用了沒必要要的內存,沒有實現方法的共享。
組合繼承兼顧原型鏈繼承跟盜用構造函數的優勢,這樣既能夠把方法定義在原型上以實現重用,又能夠看讓每一個實力都有本身的屬性。
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType(name, age) { // 繼承屬性,綁定上下文爲SubType的實例 SuperType.call(this, name); this.age = age; } // 繼承方法 SubType.prototype = new SuperType(); SubType.prototype.sayAge = function () { console.log(this.age); }; let instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "Nicholas"; instance1.sayAge(); // 29 let instance2 = new SubType("Greg", 27); console.log(instance2.colors); // "red,blue,green"; instance2.sayName(); // "Greg"; instance2.sayAge(); // 27
能夠傳遞參數到父類構造函數
兩個實例中的引用類型不會相互影響
實例能夠調用父類的方法,且實現方法的共享
組合繼承也保留了 instanceof 操做符和isPrototypeOf() 方法識別合成對象的能力。
劣勢:
SuperType會被調用兩次,SubType實例跟原型鏈上都有name跟colors屬性。
不定義構造函數經過原型實現對象以前的繼承。
function object(o) { function F() {} F.prototype = o; return new F(); }
返回新對象,讓其原型指向O
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"], }; let anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); let yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie";
父對象中的引用屬性會在子對象中共享,致使相互污染。
ES5引入了Object.create()
規範了原型式繼承。
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"], }; let anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); let yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); // "Shelby,Court,Van,Rob,Barbi
使用Object.create後anotherPerson.__proto__===person
成立,因此anotherPerson能夠拿到person的屬性,可是一樣存在父對象屬性共享的問題,改了父對象的屬性,其餘的子對象都跟着改變。
劣勢:
父對象的引用類型會在實例中共享,這樣就會相互污染。
寄生式繼承跟原型式繼承很相似,用工廠函數在對返回的新對象包一層,給新對象賦值一些屬性
工廠函數的定義:
function createAnother(original) { let clone = object(original); // 經過調用函數建立一個新對象; clone.sayHi = function () { // 以某種方式加強這個對象; console.log("hi"); }; return clone; // 返回這個對象 }
使用:
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"], }; let anotherPerson = createAnother(person); anotherPerson.sayHi(); // "hi"
定義在新對象上的sayHi方法,每次調用新對象都是新的,沒法實現共享。
劣勢:
父對象的引用類型會在實例中共享,這樣就會相互污染。
方法沒法實現共享
上面提到,組合繼承的缺點就是父類構造函數會被調用兩次,一次是在子類的構造函數中,另外一次在建立子類原型時調用。繼承就是要繼承父類的屬性跟方法,組合繼承實現了這個目標,可是怎麼避免重複調用父類構造函數。
先看下組合繼承:
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType(name, age) { SuperType.call(this, name); // 第二次調用,將父類的屬性綁定到子類的實例中 SuperType(); this.age = age; } SubType.prototype = new SuperType(); // 第一次調用SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); };
由圖可見s的原型鏈上依然有name跟colors屬性。這也是不須要的。怎麼解決這兩個問題?
父類的屬性是須要的,父類的原型上的方法是須要的,重複的父類屬性不須要,由上圖可見重複的父類屬性是因爲實例化父類給子類原型形成的,咱們不去實例化父類,而是將父類的原型傳遞給子類的原型就好了,結合原型式繼承特色能夠作到
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType(name, age) { SuperType.call(this, name); // 將父類的屬性綁定到SubType實例中 this.age = age; } SubType.prototype = Object.create(SuperType.prototype); // 將子類的prototype關聯到父類的prototype上 SubType.prototype.sayAge = function () { console.log(this.age); };
使用Object.create解決了父類構造函數調用兩次,父類屬性重複的問題,可是子類constructor並無出如今原型鏈中
下面作出改造:
SubType.prototype = Object.create(SuperType.prototype, { constructor: { value: SubType, // 修正 constructor 指向 writable: true, configurable: true, }, });
SuperType的constructor出現了,其實constructor並沒什麼用,只是個約定罷了,參考賀老的解釋JavaScript 中對象的 constructor 屬性的做用是什麼?instanceof操做符和 isPrototypeOf() 方法正常有效。寄生式組合繼承能夠算是引用類型繼承的最佳模式