前言
?在面向?qū)ο缶幊讨衅蚯桑瑢?duì)象的繼承是很重要的一個(gè)概念涨椒。A 對(duì)象通過(guò)繼承 B 對(duì)象,就能直接擁有 B 對(duì)象的所有屬性和方法绽媒。這對(duì)于代碼的復(fù)用是非常有用的蚕冬。
現(xiàn)在梳理下js的繼承
方式一、原型鏈繼承
實(shí)現(xiàn)核心
?這個(gè)繼承方法的核心是:將父類的實(shí)例作為子類的原型
// 父類
function Parent(name) {
this.name = name;
this.play = [1, 2, 3];
this.setName = function (name) { this.name = name };
}
Parent.prototype.getName = function () {
console.log("parent name:", this.name);
}
// 子類
function Child(name) {
this.name = name;
}
// 核心: 將父類的實(shí)例作為子類的原型
Child.prototype = new Parent("father");
Child.prototype.constructor=Child是辕;//需手動(dòng)綁定constructor
var child1 = new Child("son1");
child1.getName();//parent name: son1
child1.setName("ganin");
child1.getName();//parent name: ganin
var child2 = new Child("son2");
child2.getName();//parent name: son2
?這種方法實(shí)際是將子類的原型指向父類的實(shí)例囤热,所以子類還是可以通過(guò)getPrototypeOf訪問(wèn)到Parent的實(shí)例的,這樣就可以訪問(wèn)的父類的私有方法了免糕,然后在通過(guò)getPrototypeOf就可以獲得父類原型上的方法了赢乓。簡(jiǎn)單來(lái)說(shuō)就是子類繼承父類的屬性和方法是將父類的私有屬性和公有方法都作為自己的公有屬性和方法忧侧。這樣也有一個(gè)不足之處石窑,如果說(shuō)父類的私有屬性中有引用類型的屬性,那它被子類繼承的時(shí)候會(huì)作為公有屬性蚓炬,這樣子類1操作這個(gè)屬性的時(shí)候松逊,就會(huì)影響到子類2。見(jiàn)例子:
console.log(child2.play)//[ 1, 2, 3 ]
child1.play.push(4)
console.log(child2.play)//[ 1, 2, 3, 4 ]
特點(diǎn)
??父類新增原型方法/原型屬性肯夏,子類都能訪問(wèn)到
??簡(jiǎn)單经宏,易于實(shí)現(xiàn)
缺點(diǎn)
?無(wú)法實(shí)現(xiàn)多繼承
?不能定義私有屬性方法
?沒(méi)辦法向父類傳遞參數(shù)
?無(wú)論是定義還是繼承都需要手動(dòng)修改 constructor
?要想為子類新增屬性和方法,必須要在Student.prototype = new Person() 之后執(zhí)行驯击,不能放到構(gòu)造器中烁兰。
方式二、構(gòu)造函數(shù)繼承(類式繼承)
實(shí)現(xiàn)核心:在子類型構(gòu)造函數(shù)中使用call()調(diào)用父類型構(gòu)造函數(shù)
// 父類
function Parent(name,age){
this.name=name;
this.age=age;
this.play = [1, 2, 3];
this.setName = function (name) { this.name = name };
}
Parent.prototype.getName = function () {
console.log("parent name:", this.name);
}
function Child(name,age){
Parent.call(this,name,age);
}
var child1=new Child("agam",20);
var child2=new Child("tom",20);
console.log(child1.name)//agam
child1.setName("agamgn")
console.log(child1.name)//agamgn
console.log(child2.name)//tom
child1.getName();//child1.getName is not a function
?這種方式只是實(shí)現(xiàn)部分的繼承徊都,如果父類的原型還有方法和屬性沪斟,子類是拿不到這些方法和屬性的。
特點(diǎn)
??可以定義私有屬性方法暇矫,解決了原型鏈繼承中子類實(shí)例共享父類引用屬性的問(wèn)題
??子類可以向父類傳遞參數(shù)
??可以實(shí)現(xiàn)多繼承(call多個(gè)父類對(duì)象)
缺點(diǎn)
?實(shí)例并不是父類的實(shí)例主之,只是子類的實(shí)例
?只能繼承父類的實(shí)例屬性和方法,不能繼承原型屬性和方法
?無(wú)法實(shí)現(xiàn)函數(shù)復(fù)用李根,每個(gè)子類都有父類實(shí)例函數(shù)的副本槽奕,影響性能
方式三、組合繼承(原型鏈+構(gòu)造函數(shù))
實(shí)現(xiàn)核心:通過(guò)調(diào)用父類構(gòu)造房轿,繼承父類的屬性并保留傳參的優(yōu)點(diǎn)粤攒,然后通過(guò)將父類實(shí)例作為子類原型所森,實(shí)現(xiàn)函數(shù)復(fù)用。
// 父類
function Parent(name,age){
this.name=name;
this.age=age;
this.play = [1, 2, 3];
this.setName = function (name) { this.name = name };
}
Parent.prototype.getName = function () {
console.log("parent name:", this.name);
}
function Child(name,age){
Parent.call(this,name,age);
}
Child.prototype=new Parent();
Child.prototype.constructor=Child;//組合繼承也是需要修復(fù)構(gòu)造函數(shù)指向的
Child.prototype.satHello=function(){console.log("hello")}
var child1=new Child("agam",18);
var child2=new Child("agamgn",18);
console.log(child1)//Child { name: 'agam',
age: 18, play: [ 1, 2, 3 ], setName: [Function] }
child1.setName("Tom")
console.log(child1)//Child { name: 'Tom',
age: 18, play: [ 1, 2, 3 ], setName: [Function] }
console.log(child2)//Child {name: 'agamgn',
age: 18, play: [ 1, 2, 3 ],setName: [Function] }
?這種方式是 JavaScript 中最常用的繼承模式夯接。不過(guò)也存在缺點(diǎn)就是無(wú)論在什么情況下必峰,都會(huì)調(diào)用兩次構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時(shí)候,另一次是在子類型構(gòu)造函數(shù)的內(nèi)部钻蹬,子類型最終會(huì)包含父類型對(duì)象的全部實(shí)例屬性吼蚁,但我們不得不在調(diào)用子類構(gòu)造函數(shù)時(shí)重寫(xiě)這些屬性。
特點(diǎn)
??可以繼承實(shí)例屬性/方法问欠,也可以繼承原型屬性/方法
??公有的寫(xiě)在原型肝匆,私有的寫(xiě)在構(gòu)造函數(shù)
??可以向父類傳遞參數(shù)
??函數(shù)可復(fù)用
缺點(diǎn)
?調(diào)用了兩次父類構(gòu)造函數(shù),生成了兩份實(shí)例(子類實(shí)例將子類原型上的那份屏蔽了)
? 需要手動(dòng)綁定 constructor
方式四顺献、組合繼承優(yōu)化1
實(shí)現(xiàn)核心:通過(guò)父類原型和子類原型指向同一對(duì)象
// 父類
function Parent(name,age){
this.name=name;
this.age=age;
this.play = [1, 2, 3];
this.setName = function (name) { this.name = name };
}
Parent.prototype.getName = function () {
console.log("parent name:", this.name);
}
function Child(name,age){
Parent.call(this,name,age);
}
// 此處父類原型和子類原型指向同一對(duì)象
Child.prototype=Parent.prototype;
Child.prototype.satHello=function(){console.log("hello")}
var child1=new Child("agam",18);
var child2=new Child("agamgn",18);
console.log(child1)//Child { name: 'agam', age: 18, play: [ 1, 2, 3 ], setName: [Function] }
child1.setName("Tom")
console.log(child1)//Child { name: 'Tom', age: 18, play: [ 1, 2, 3 ], setName: [Function] }
console.log(child2)//Child {name: 'agamgn',age: 18, play: [ 1, 2, 3 ],setName: [Function] }
特點(diǎn)
??不會(huì)初始化兩次實(shí)例方法/屬性旗国,避免的組合繼承的缺點(diǎn)
缺點(diǎn)
?沒(méi)辦法辨別是實(shí)例是子類還是父類創(chuàng)造的,子類和父類的構(gòu)造函數(shù)指向是同一個(gè)
方式五注整、原型式繼承
實(shí)現(xiàn)核心:直接使用 ES5 Object.create 方法能曾,該方法的原理是創(chuàng)建一個(gè)構(gòu)造函數(shù),構(gòu)造函數(shù)的原型指向?qū)ο笾坠欤缓笳{(diào)用 new 操作符創(chuàng)建實(shí)例寿冕,并返回這個(gè)實(shí)例,本質(zhì)是一個(gè)淺拷貝椒袍。
// 父類
function Parent(name,age){
this.name=name;
this.age=age;
this.play = [1, 2, 3];
this.setName = function (name) { this.name = name };
}
Parent.prototype.getName = function () {
console.log("parent name:", this.name);
}
function Child(name,age){
Parent.call(this,name,age);
}
// 核心代碼
Child.prototype=Object.create(Parent.prototype);
Child.prototype.constructor=Child;//核心代碼
var child1=new Child("agam",18);
var child2=new Child("agamgn",18);
console.log(child1)//Child { name: 'agam', age: 18, play: [ 1, 2, 3 ], setName: [Function] }
child1.setName("Tom")
console.log(child1)//Child { name: 'Tom', age: 18, play: [ 1, 2, 3 ], setName: [Function] }
console.log(child2)//Child {name: 'agamgn',age: 18, play: [ 1, 2, 3 ],setName: [Function] }
特點(diǎn)
??父類方法可以復(fù)用
缺點(diǎn)
?父類引用屬性全部被共享
?子類不可傳遞參數(shù)給父類
方式六驼唱、ES6 class
實(shí)現(xiàn)核心:ES6中引入了class關(guān)鍵字,是一種語(yǔ)法糖驹暑。
class Parent{
constructor(name){
this.name=name;
}
getName(){
console.log(this.name);
}
}
let p=new Parent("ganin");
console.log(p);//Parent { name: 'ganin' }
class Child extends Parent{
constructor(name,age){
super(name);//通過(guò)supper調(diào)用父類的構(gòu)造方法
this.age=age;
}
get(){
console.log(this.name,this.age)
}
}
let s=new Child("tom",18);
console.log(s)//Child { name: 'tom', age: 18 }
特點(diǎn)
??語(yǔ)法簡(jiǎn)單易懂,操作更方便
缺點(diǎn)
?并不是所有的瀏覽器都支持class關(guān)鍵字
總結(jié)
參考一篇文章理解JS繼承——原型鏈/構(gòu)造函數(shù)/組合/原型式/寄生式/寄生組合/Class extends
本章節(jié)代碼