————————————————————————————————————————————————————————— 數組
繼承 - ECMAScript只支持實現繼承(依靠原型鏈),不支持接口繼承(函數沒有簽名)函數
原型鏈 this
原型搜索機制:當讀取模式訪問一個實例屬性時,首先會在實例中搜索該屬性,若是沒有找到該屬性則沿着原型鏈向上查找 spa
在例子<Demo-1>中,調用instance.getSuperValue(),先搜索實例instance,再搜索SubType.prototype,再搜索SuperType.protorype,最後一步才找到該方法。prototype
默認的原型:全部的引用類型默認都繼承了Object,因此默認原型的指針都會指向Object.prototype,完整的原型鏈以下: 指針
instance → SubType.prototype → SuperType.prototype → Object.prototypecode
必須替換掉實例的原型後才能給實例添加方法 對象
不能使用對象字面量建立原型方法,這樣作會重寫原型鏈,如<Demo-3>blog
包含引用類型值(Function Object Array)的原型屬性會被全部實例共享,在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例,因此原先的實例屬性就變成了如今的原型屬性了。<Demo-4>繼承
在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。
// "use strict"; // Demo - 1 // SuperType 擁有一個屬性和一個方法 // SubType 擁有一個屬性和一個方法,又從SuperType那裏繼承了一個屬性一個方法 function SuperType(){ this.property = "111"; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = "222"; } // p.s.new操做以前,SubType.prototype指向的是function,不容許爲function()定義.getSubValue方法,因此要將添加方法放在修改原型指向以後 // 操做以後SubType.prototype指向SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ // 必須在SubType替換原型以後才能定義 return this.subproperty; } var instance = new SubType(); console.log(instance.property); // 111 console.log(instance.getSuperValue()); // 111 console.log(instance.subproperty); // 222 console.log(instance.getSubValue()); // 222 console.log(instance.constructor); // f SuperType(){} 本來SubType中的constructor屬性被重寫 // 重寫SuperType.getSuperValue() // 若是要重寫這個方法,會屏蔽原來的方法 // 換句話說,當經過SubType的實例調用getSuperValue時調用的就是這個從新定義的方法,但經過SuperType的實例調用時還會繼續調用原來的方法 var beforeReWrite = new SuperType(); SuperType.prototype.getSuperValue = function(){ console.log("rewrite"); } console.log(instance.getSuperValue()); // rewrite,this.property = undefined console.log(SuperType.prototype.getSuperValue()); // rewrite,this.property = undefined console.log(beforeReWrite.getSuperValue()); // Demo - 2 // 確認原型和實例的關係 console.log(instance instanceof Object); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof SubType); // true // 另外一種方法 console.log(Object.prototype.isPrototypeOf(instance)); // true console.log(SuperType.prototype.isPrototypeOf(instance)); // true console.log(SubType.prototype.isPrototypeOf(instance)); // true // Demo - 3 function SuperType2(){ this.property = "1111"; } SuperType2.prototype.getSuperValue = function(){ return this.property; } function SubType2(){ this.subproperty = "2222"; } SubType2.prototype = new SuperType2(); SubType2.prototype = { getSubValue:function(){ return this.subproperty; }, someOtherMethod:function(){ return false; } } var instance2 = new SubType2(); console.log(instance2 instanceof Object); // true console.log(instance2 instanceof SuperType2); // false,原型鏈被切斷 console.log(instance2 instanceof SubType2); // true // console.log(instance2.getSuperValue()); // error // Demo - 4 function SuperType3(){ this.colors = ["red","blue","green"]; } function SubType3(){} SubType3.prototype = new SuperType3(); var instance3 = new SubType3(); instance3.colors.push("black"); console.log(instance3.colors); // ["red", "blue", "green", "black"] var instance4 = new SubType3(); console.log(instance4.colors); // ["red", "blue", "green", "black"]
借用構造函數(僞造對象 / 經典繼承)
解決了單獨使用原型鏈共享引用類型值屬性的問題
能夠在子類型構造函數中向超類型構造函數傳遞參數
沒法避免構造函數模式存在的問題:方法都在構造函數中定義,沒法實現函數複用
// "use strict"; function SuperType(name) { this.name = name; this.colors = ["111", "222", "333"]; } function SubType() { SuperType.call(this, "name1"); this.age = 20; } var instance = new SubType(); instance.colors.push("444"); console.log(instance.colors); // ["111", "222", "333", "444"] console.log(instance.name); // name1 console.log(instance.age); // 20 var instance2 = new SubType(); console.log(instance2.colors); // ["111", "222", "333"]
組合繼承(僞經典繼承)
// "use strict"; function SuperType(name) { this.name = name; this.colors = ["111", "222", "333"]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); // 繼承屬性 this.age = age; } SubType.prototype = new SuperType(); // 繼承方法 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); } var instance1 = new SubType("hugh", 20); instance1.colors.push("444"); console.log(instance1.colors); // ["111", "222", "333", "444"] instance1.sayName(); // hugh instance1.sayAge(); // 20 var instance2 = new SubType("dong", 21); console.log(instance2.colors); // ["111", "222", "333"] instance2.sayName(); // dong instance2.sayAge(); // 21
原型式繼承
在不想建立構造函數,只想讓一個對象與另外一個對象保持相似的狀況下,原型式繼承徹底能夠勝任
包含引用類型值的屬性始終都會共享,就像原型模式同樣
// "use strict"; function object(o){ function F(){} // 建立臨時性構造函數 F.prototype = o; // 將傳入的對象做爲構造函數的原型 return new F(); // 返回臨時類型的一個新實例 } var person = { name:"hugh", friends:["111",'222','333'] }; var anotherPerson = object(person); anotherPerson.name = "dong"; anotherPerson.friends.push("444"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "hehe"; yetAnotherPerson.friends.push("555"); console.log(person.friends); // ["111", "222", "333", "444", "555"] console.log(person.name); // hugh console.log(anotherPerson.friends); // ["111", "222", "333", "444", "555"] console.log(anotherPerson.name); // dong console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555"] console.log(yetAnotherPerson.name); // hehe // 使用Object.create()規範化原型式繼承 // 以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性 var otherPerson1 = Object.create(person); otherPerson1.friends.push("666"); console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555", "666"] var otherPerson2 = Object.create(person,{ name:{ value:"test" } }); console.log(otherPerson2.name);
寄生式繼承
// "use strict"; function object(o) { function F() {} // 建立臨時性構造函數 F.prototype = o; // 將傳入的對象做爲構造函數的原型 return new F(); // 返回臨時類型的一個新實例 } function createAnother(original) { // 接收的函數做爲新對象基礎的對象 var clone = object(original); clone.sayHi = function() { // 添加新方法 console.log('hi'); }; return clone; } var person = { name: "hugh", friends: ['111', '222', '333'] }; var person1 = createAnother(person); person1.sayHi(); console.log(person1.name); console.log(person1.friends);
寄生組合式繼承
最理想的繼承範式
解決組合繼承重寫屬性的問題,只調用了一次SuperType構造函數
避免了在SubType.prototype上建立沒必要要的屬性
原型鏈保持不變
可以正常使用instanceof和isPrototypeOf()
"use strict"; function object(o) { function F() {} F.prototype = o; return new F(); } // 1.建立超類型原型的一個副本 // 2.爲建立的副本添加constructor屬性,彌補因重寫原型而失去的屬性 // 3.將新建立的對象(即副本)賦值給子類型的原型 function inheritProtoType(subType,superType){ var prototype = object(superType.prototype); // 建立對象 prototype.constructor = subType; // 加強對象 subType.prototype = prototype; // 指定對象 } function SuperType(name){ this.name = name; this.colors= [1,2,3,4]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age = age; } inheritProtoType(SubType,SuperType); SubType.prototype.sayAge = function(){ console.log(this.age); }