ES5中沒有繼承的,但是可以通過對象的原型
和修改構(gòu)造函數(shù)的指向
來達(dá)到繼承的效果舷胜。
1. 原型鏈繼承
子類構(gòu)造函數(shù)的原型指向父類構(gòu)造函數(shù)的實例娩践,這樣,子類也擁有了父類的屬性和方法
基本實現(xiàn)
function Father() {
this.name = '我是爸爸'
}
Father.prototype.say = function () {
console.log(this.name);
}
function Son () {
}
Son.prototype = new Father()
var son = new Son()
son.say() // 我是爸爸
缺點
子類實例會共享父類中相應(yīng)的值,當(dāng)一個子類修改了父類的屬性和方法(引用類型)欺矫,其他子類也相應(yīng)的父類屬性和方法也會被修改
function Father () {
this.sons = ['大明','二明','三明']
}
function Son () {
}
Son.prototype = new Father()
var son1 = new Son()
var son2 = new Son()
// 當(dāng)son1改變了父類的屬性
son1.sons.push('小明')
console.log('son1', son1.sons) // ["大明", "二明", "三明", "小明"]
console.log('son2', son2.sons) // ["大明", "二明", "三明", "小明"]
上面實例可以看出纱新,father有一個數(shù)組對象,當(dāng)子類構(gòu)造函數(shù)的prototype指向父類對象后穆趴,新建的子類對象就共享了父類father中的屬性和方法脸爱。當(dāng)son1對father中的引用類型進(jìn)行修改時,son2中的值也會對象的被修改
2. 構(gòu)造函數(shù)繼承
使用父類的構(gòu)造函數(shù)來增強(qiáng)子類實例未妹,等同于復(fù)制父類的實例給子類
核心代碼是Father.call(this)簿废,創(chuàng)建子類實例時調(diào)用Father構(gòu)造函數(shù),于是Son的每個實例都會將Father中的屬性和方法復(fù)制一份
基本實現(xiàn)
function Father() {
this.colors = ['red', 'black', 'green']
}
Father.prototype.name = 'Father'
Father.prototype.say = function () {
console.log('我是Father');
}
function Son() {
Father.call(this)
}
var son = new Son()
console.log(son);
可以看出络它,在子類構(gòu)造函數(shù)中調(diào)用父類的方法族檬,可以將父類中的this指向子類實例,這樣子類實例就擁有了父類的屬性和方法化戳。
缺點:無法復(fù)制Father構(gòu)造函數(shù)原型的屬性和方法
3. 組合繼承
將
方法1
和方法2
結(jié)合起來单料。組合繼承就是將原型鏈繼承和構(gòu)造函數(shù)繼承結(jié)合起來
- 使用原型鏈繼承解決 父類構(gòu)造函數(shù)原型的屬性和方法繼承問題
- 使用構(gòu)造函數(shù)繼承解決 父類實例中的屬性被共享的問題
基本實現(xiàn)
function Father() {
this.colors = ['red', 'black', 'green']
}
Father.prototype.name = 'Father'
Father.prototype.say = function() {
console.log('我是Father')
}
function Son () {
Father.call(this)
}
Son.prototype = new Father()
var son1 = new Son()
var son2 = new Son()
// 修改一下son1
son1.colors.push('pink')
console.log('son1',son1);
// 調(diào)用父類的原型方法
son1.say()
console.log('son2',son2);
// 調(diào)用父類的原型方法
son2.say()
- 解決了修改父類實例中的變量其他子類實例也會被修改的問題
- 解決了prototype繼承的問題
缺點:可以看到,子類實例和子類的prototype對象中會有重復(fù)的部分
4. 原型式繼承
利用一個空對象作為中介点楼,將某個對象直接賦值給空對象構(gòu)造函數(shù)的原型
基本實現(xiàn)
function object(obj) {
function F() {}
F.prototype = obj
return new F()
}
var person = {
name: '劉德華',
songs: ['冰雨','中國人','恭喜發(fā)財']
}
var son1 = object(person)
son1.name = '張學(xué)友'
son1.songs.push('李香蘭')
var son2 = object(person)
son2.name = '王菲'
son2.songs.push('人間')
console.log('son1',son1);
console.log('son2',son2);
上述代碼可以看到扫尖,son1和son2修改的是同一個songs對象
缺點:原型鏈繼承多個實例的引用類型屬性指向相同,存在篡改的可能
5. 寄生式繼承
在原型式繼承的基礎(chǔ)上掠廓,增強(qiáng)對象换怖,返回構(gòu)造函數(shù)
缺點:同原型式繼承相同
基本實現(xiàn)
function object(obj) {
function F() { }
F.prototype = obj
return new F()
}
function createAnother(original) {
var clone = object(original) // 通過調(diào)用object()函數(shù)創(chuàng)建一個新對象
clone.sayHi = function () { // 以某種方式來增強(qiáng)對象
alert('hi')
}
return clone // 返回這個對象
}
// 函數(shù)的主要作用是為構(gòu)造函數(shù)新增屬性和方法。以增強(qiáng)函數(shù)
var person = {
name: '劉德華',
songs: ['冰雨']
}
var son1 = createAnother(person)
var son2 = createAnother(person)
son1.sayHi()
son1.songs.push('李香蘭')
son2.songs.push('人間')
console.log('son1', son1);
console.log('son2', son2);
6. 寄生組合式繼承
一種比較完美的解決方案蟀瞧,解決了以上繼承的所有缺點
- 子類prototype指向父類原型對象
- 子類原型對象的contructor指向子類構(gòu)造函數(shù)
- 子類構(gòu)造函數(shù)中調(diào)用父類函數(shù)沉颂,復(fù)制父類函數(shù)的屬性和方法
基本實現(xiàn)
function inheritPrototype(son, father) {
// 復(fù)制一份父類的原型
var prototype = Object.create(father.prototype)
// 將原型對象上的構(gòu)造函數(shù)指向子類構(gòu)造函數(shù)
prototype.constructor = son
// 子類的原型對象指向父類原型對象
son.prototype = prototype
}
function Father(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Father.prototype.tag = 'Father'
Father.prototype.say = function () {
alert('我是' + this.tag)
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype.sayAge = function () {
alert('我今年' + this.age + '歲了')
}
inheritPrototype(Son, Father)
var son1 = new Son('大明','23')
var son2 = new Son('小明','20')
son1.colors.push('black')
console.log(son1);
console.log(son2);
7. 混入方式繼承多個對象
原理同寄生組合式繼承:將多個父類對象的prototype的屬性和方法復(fù)制到子類對象的prototype上
在構(gòu)造函數(shù)中,使用call方法悦污,調(diào)用父類對象铸屉,將多個父類對象的屬性和方法復(fù)制到子類對象上
基本實現(xiàn)
function Father1() {
}
function Father2 () {
}
function Son () {
Father1.call(this)
Father2.call(this)
}
Son.prototype = Object.create(Father1.prototype)
Object.assign(Son.prototype, Father2.prototype)
8. ES6 Class extends
核心: ES6繼承的結(jié)果和寄生組合繼承相似,本質(zhì)上塞关,ES6繼承是一種語法糖抬探。但是子巾,寄生組合繼承是先創(chuàng)建子類實例this對象帆赢,然后再對其增強(qiáng);而ES6先將父類實例對象的屬性和方法线梗,加到this上面(所以必須先調(diào)用super方法)椰于,然后再用子類的構(gòu)造函數(shù)修改this。
class A {}
class B extends A {
constructor() {
super();
}
}
ES6實現(xiàn)繼承的具體原理:
class A {
}
class B {
}
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
// B 的實例繼承 A 的實例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 繼承 A 的靜態(tài)屬性
Object.setPrototypeOf(B, A);
ES6繼承與ES5繼承的異同:
- 相同點:本質(zhì)上ES6繼承是ES5繼承的語法糖
- 不同點:
- ES5的繼承實質(zhì)上是先創(chuàng)建子類的實例對象仪搔,然后再將父類的方法添加到this上(Parent.call(this))
- ES6的繼承有所不同瘾婿,實質(zhì)上是先創(chuàng)建父類的實例對象this,然后再用子類的構(gòu)造函數(shù)修改this。因為子類沒有自己的this對象偏陪,所以必須先調(diào)用父類的super()方法抢呆,否則新建實例報錯。