本篇文章給大家帶來的內容是關於(超經典)javascript中對象繼承方式的總結 ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所協助。
一、原型鏈繼承
重點:利用原型讓一個參考型別繼承另外一個參考型別的屬性和方法。建構函式,原型,執行個體之間的關係:每個建構函式都有一個原型對象,原型對象包含一個指向建構函式的指標,而執行個體都包含一個指向原型對象的內部指標。
function SuperType(){ this.property = true;}SuperType.prototype.getSuperValue = function(){ return this.property;};function SubType(){ this.subproperty = false;}// 繼承自SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function (){ return this.subproperty;};var example = new SubType();alert(example.getSuperValue());//true
使用原型建立對象會存在多個執行個體對參考型別的操作會被篡改的問題,在上面同樣存在這個問題,如下:
function SuperType(){ this.colors = ["red", "blue", "green"];}function SubType(){}//即使沒有寫,也不會影響結果SubType.prototype = new SuperType();var example1 = new SubType();example1.colors.push("black");alert(example1.colors); //"red,blue,green,black"var example2 = new SubType(); alert(example.colors); //"red,blue,green,black"
兩個執行個體對象example1和example2的colors屬性指向相同,改變一個會影響另一個執行個體的屬性。
缺點:
①原型鏈繼承多個執行個體的參考型別屬性指向相同,一個執行個體修改了原型屬性,另一個執行個體的原型屬性也會被修改;
②不能傳遞參數;
③繼承單一。
二、借用建構函式繼承
重點:使用.call()和.apply()將父類建構函式引入子類函數,使用父類的建構函式來增強子類執行個體,等同於複製父類的執行個體給子類。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"];}function SubType(name, age){ // 繼承自SuperType SuperType.call(this, name); this.age = age;}var example1 = new SubType("Mike", 23);example1.colors.push("black");alert(example1.colors);//"red,blue,green,black"var example2 = new SubType();alert(example2.colors);//"red,blue,green"alert(example1.name); // "Mike"alert(example1.age); // 23
借用建構函式繼承的重點就在於SuperType.call(this, name),調用了SuperType建構函式,這樣,SubType的每個執行個體都會將SuperType中的屬性複製一份。
缺點:
①只能繼承父類的執行個體屬性和方法,不能繼承原型屬性/方法;
②無法實現建構函式的複用,每個子類都有父類執行個體函數的副本,影響效能,代碼會臃腫。
三、組合繼承
重點:將原型鏈繼承和建構函式繼承這兩種模式的優點組合在一起,通過調用父類構造,繼承父類的屬性並保留傳參,然後通過將父類執行個體作為子類原型,實現函數複用。
其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用建構函式來實現對執行個體屬性的繼承,這樣,既通過在原型上定義方法實現了函數複用,又能保證每個執行個體都有它自己的屬性。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"];}SuperType.prototype.sayName = function(){ alert(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(){ alert(this.age);};var example1 = new SubType("Mike", 23);example1.colors.push("black");alert(example1.colors); //"red,blue,green,black"example1.sayName(); //"Mike";example1.sayAge(); //23var example2 = new SubType("Jack", 22);alert(example2.colors); //"red,blue,green"example2.sayName(); //"Jack";example2.sayAge(); //22
缺陷:
父類中的執行個體屬性和方法既存在於子類的執行個體中,又存在於子類的原型中,不過僅是記憶體佔用,因此,在使用子類建立執行個體對象時,其原型中會存在兩份相同的屬性/方法。-------這個方法是javascript中最常用的繼承模式。
四、 原型式繼承
重點:用一個函數封裝一個對象,然後返回這個函數的調用,這個函數就變成了個可以隨意增添屬性的執行個體或對象。object.create()就是這個原理,直接將某個對象直接賦值給建構函式的原型。
function object(obj){ function O(){} O.prototype = obj; return new O();}
object()對傳入其中的對象執行了一次淺複製,將O的原型直接指向傳入的對象。
var person = { name: "Mike", friends: ["Jack", "Tom", "Joes"]};var anotherPerson = object(person);anotherPerson.name = "Greg";anotherPerson.friends.push("Peter");var yetAnotherPerson = object(person);yetAnotherPerson.name = "Linda";yetAnotherPerson.friends.push("BoBo");alert(person.friends); //"Jack,Tom,Joes,Peter,BoBo"
ECMAScript5通過新增Object.create()方法正常化了原型式繼承,這個方法接收兩個參數:一個用作新對象原型的對象和一個作為新對象定義額外屬性的對象。
var person = {name:"EvanChen",friends:["Shelby","Court","Van"];};var anotherPerson = Object.create(person);anotherPerson.name = "Greg";anotherPerson.friends.push("Rob");var yetAnotherPerson = Object.create(person);yetAnotherPerson.name = "Linda";yetAnotherPerson.friends.push("Barbie");console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"
缺點:
①原型鏈繼承多個執行個體的參考型別屬性指向相同(所有執行個體都會繼承原型上的屬性),存在篡改的可能;
②無法傳遞參數,無法實現複用。(新執行個體屬性都是後面添加的)。
五、寄生式繼承
重點:建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最後返回建構函式。(就像給原型式繼承外面套了個殼子,然後return出來)
function createAnother(original){ varclone=object(original); // 過調用函數建立一個新對象 clone.sayHi = function(){ // 以某種方式增強這個對象 alert("hi"); }; return clone; // 返回對象}
函數的主要作用是為建構函式新增屬性和方法,以增強函數。
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"]};var anotherPerson = createAnother(person);anotherPerson.sayHi(); //"hi"
缺點:
①原型鏈繼承多個執行個體的參考型別屬性指向相同,存在篡改的可能;
②無法傳遞參數,沒用到原型,無法複用。
六、寄生組合式繼承
重點:通過借用建構函式傳遞參數和寄生模式實現繼承屬性,通過原型鏈的混成形式來繼承方法,在函數中用apply或者call引入另一個建構函式,可傳參。
function inheritPrototype(subType, superType){ var prototype = Object.create(superType.prototype); //Object.create建立對象 prototype.constructor = subType; // 增強對象 subType.prototype = prototype; // 指定對象}// 父類初始化執行個體屬性和原型屬性function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"];}SuperType.prototype.sayName = function(){ alert(this.name);};// 借用建構函式傳遞增強子類執行個體屬性(支援傳參和避免篡改)function SubType(name, age){ SuperType.call(this, name); this.age = age;}// 將父類原型指向子類inheritPrototype(SubType, SuperType);// 新增子類原型屬性SubType.prototype.sayAge = function(){ alert(this.age);}var example1 = new SubType("abc", 21);var example2 = new SubType("def", 22);example1.colors.push("pink"); // ["red", "blue", "green", "pink"]example1.colors.push("black"); // ["red", "blue", "green", "black"]
寄生組合繼承集合了前面幾種繼承優點,幾乎避免了上面繼承方式的所有缺陷,是執行效率最高也是應用面最廣的。
缺點:
實現的過程相對繁瑣。
為什麼要學習這些繼承方式,明明可以直接繼承為什麼還要搞這麼麻煩?主要是為了學習它們的思想,打下更好的基礎,為以後閱讀架構源碼,或自己封裝組件甚至架構大有益處。
時間有點匆忙,沒有加上ES6的extends。