javascript 面向物件(實現繼承的幾種方式)
1、原型鏈繼承
核心: 將父類的例項作為子類的原型
缺點:父類新增原型方法/原型屬性,子類都能訪問到,父類一變其它的都變了
function Person (name) { this.name = name; }; Person.prototype.getName = function () {//對原型進行擴充套件 return this.name; }; function Parent (age) { this.age = age; }; Parent.prototype = new Person('老明');//這一句是關鍵 //通過構造器函式創建出一個新物件,把老物件的東西都拿過來。 Parent.prototype.getAge = function () { return this.age; }; //Parent.prototype.getName = function () {//可以重寫從父類繼承來的方法,會優先呼叫自己的。 //console.log(222); //}; var result = new Parent(22); console.log(result.getName());//老明 //呼叫了從Person原型中繼承來的方法(繼承到了當前物件的原型中) console.log(result.getAge());//22 //呼叫了從Parent原型中擴充套件來的方法
2、構造繼承
基本思想
借用建構函式的基本思想就是利用call
或者apply
把父類中通過this
指定的屬性和方法複製(借用)到子類建立的例項中。
因為this
物件是在執行時基於函式的執行環境繫結的。也就是說,在全域性中,this
等於window
,而當函式被作為某個物件的方法呼叫時,this
等於那個物件。
call
、apply
方法可將一個函式的物件上下文從初始的上下文改變為由 thisObj 指定的新物件。
所以,這個借用建構函式就是,new
物件的時候(new建立的時候,this
指向建立的這個例項),建立了一個新的例項物件,
並且執行Parent
裡面的程式碼,而Parent
裡面用call
呼叫了Person
,也就是說把this
指向改成了指向新的例項,
所以就會把Person
裡面的this
相關屬性和方法賦值到新的例項上,而不是賦值到Person
上面,
所以所有例項中就擁有了父類定義的這些this
的屬性和方法。
因為屬性是繫結到this
上面的,所以呼叫的時候才賦到相應的例項中,各個例項的值就不會互相影響了。
核心:使用父類的建構函式來增強子類例項,等於是複製父類的例項屬性給子類(沒用到原型)
缺點: 方法都在建構函式中定義, 只能繼承父類的例項屬性和方法,不能繼承原型屬性/方法,無法實現函式複用,每個子類都有父類例項函式的副本,影響效能
function Person (name) { this.name = name; this.friends = ['小李','小紅']; this.getName = function () { return this.name; } }; //Person.prototype.geSex = function () {//對原型進行擴充套件的方法就無法複用了 //console.log("男"); //}; function Parent = (age) { Person.call(this,'老明'); //這一句是核心關鍵 //這樣就會在新parent物件上執行Person建構函式中定義的所有物件初始化程式碼, // 結果parent的每個例項都會具有自己的friends屬性的副本 this.age = age; }; var result = new Parent(23); console.log(result.name); //老明 console.log(result.friends); //["小李", "小紅"] console.log(result.getName()); //老明 console.log(result.age); //23 console.log(result.getSex()); //這個會報錯,呼叫不到父原型上面擴充套件的方法
3、組合繼承
組合繼承(所有的例項都能擁有自己的屬性,並且可以使用相同的方法,組合繼承避免了原型鏈和借用建構函式的缺陷,結合了兩個的優點,是最常用的繼承方式)
核心:通過呼叫父類構造,繼承父類的屬性並保留傳參的優點,然後再通過將父類例項作為子類原型,實現函式複用
缺點:呼叫了兩次父類建構函式,生成了兩份例項(子類例項將子類原型上的那份遮蔽了)
function Person(name) { this.name = name; this.friends = ['小李','小紅']; }; Person.prototype.getName = function () { return this.name; }; function Parent (age) { Person.call(this,'老明'); //這一步很關鍵 this.age = age; }; Parent.prototype = new Person('老明'); //這一步也很關鍵 var result = new Parent(24); console.log(result.name); //老明 result.friends.push("小智"); // console.log(result.friends); //['小李','小紅','小智'] console.log(result.getName()); //老明 console.log(result.age); //24 var result1 = new Parent(25);//通過借用建構函式都有自己的屬性,通過原型享用公共的方法 console.log(result1.name); //老明 console.log(result1.friends); //['小李','小紅']
4、寄生組合繼承
核心:通過寄生方式,砍掉父類的例項屬性,這樣,在呼叫兩次父類的構造的時候,就不會初始化兩次例項方法/屬性,避免的組合繼承的缺點
缺點:堪稱完美,但實現較為複雜
function Person(name) { this.name = name; this.friends = ['小李','小紅']; } Person.prototype.getName = function () { return this.name; }; function Parent(age) { Person.call(this,"老明"); this.age = age; } (function () { var Super = function () {};// 建立一個沒有例項方法的類 Super.prototype = Person.prototype; Parent.prototype = new Super();//將例項作為子類的原型 })(); var result = new Parent(23); console.log(result.name); console.log(result.friends); console.log(result.getName()); console.log(result.age);