JavaScript继承
1.原型链
- 核心: 将父类的实例赋值给子类的原型
- 优点:方法复用
- 由于方法定义在父类的原型上,复用了父类构造函数的方法
- 缺点:
- 所有子类实例共享了父类构造函数原型上的引用值
- 子类不能给父类传递参数
function SuperType() {
this.superProperty = true
}
SuperType.prototype.getSuperValue = function () {
return this.superProperty
}
function SubType() {
this.subProperty = false
}
// 核心
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
return this.subProperty
}
let person = new SubType();
person.getSuperValue() // true
person.getSubValue() // false
console.log(person.superProperty) // true
console.log(person) // SubType {subProperty: false}
2.盗用构造函数
- 核心: 在子类构造函数中调用父类构造函数,等于把父类的实例属性复制给子类
- 优点:实例间属性独立
- 子类实例可以向父类构造函数传递参数
- 子类实例不共享父类构造函数的引用属性
- 缺点:
- 由于方法的父类构造函数中定义导致方法不能复用(因为每次创建子类实例的时都会创建一次父类的方法)
- 子类无法继承父类原型上的属性(因为没有用到原型)
function SuperType(name) {
this.name = name
this.colors = ['red','blue','green']
}
function SubType(age) {
// 继承 SuperType
SuperType.call(this,'xiaoming')
this.age = age
}
let person = new SubType(18)
console.log(person.age) // 18
person.colors.push('pink') // ['red','blue','green','pink']
let person1 = new SubType(19)
console.log(person1.age) // 19
console.log(person1.colors) // ['red','blue','green']
3.组合继承
- 核心: 属性通过调用父类构造函数实现继承,方法通过将父类的实例对象赋值给子类的原型来实现复用
- 优点:
- 保留构造函数的优点:可以向父类构造函数传递参数,子类不共享父类的引用属性
- 保留原型链的优点:父类构造函数原型的方法可以被子类复用
- 组合继承保留了 instanceof 操作符 和 isPrototypeOf方法识别对象的能力
- 缺点:
- 调用了2次父类构造函数,会多存在一份多余的父类实例属性
function SuperType(name) {
this.name = name
this.colors = ['red','blue','green']
}
SuperType.prototype.getName = 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.getAge = function() {
console.log(this.age)
}
let instance1 = new SubType('xiaoming',18)
instance1.colors.push('pink')
console.log(instance1.colors) // ['red','blue','green','pink']
instance1.getName() // xiaoming
instance1.getAge() // 18
let instance2 = new SubType('xiaohong', 28)
console.log(instance2.colors) // ['red','blue','green']
instance2.getName() // xiaohong
instance2.getAge() // 28
4.原型式继承
- 核心: 基于已有的对象创建对象,同时不必因此创建自定义类型
- 优点:
- 不需要创建构造函数,想让对象直接属性共享的场合
- 缺点:
- 和原型链一样属性和方法会在各个子类中共享
// 临时创建一个构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时构造函数的实例
function object(o) {
function F() {}
F.prototype = o
return new F()
}
let person = {
name: 'xiaoming',
colors: ['red','blue','green']
}
let anotherPerson = Object.create(person)
anotherPerson.name = 'xiaohong'
anotherPerson.colors.push('black')
let anotherPerson1 = Object.create(person)
anotherPerson1.name = 'jack'
anotherPerson1.colors.push('pink')
console.log(person.colors) // ['red','blue','green','black','pink']
5. 寄生式继承
- 寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
- 缺点: 和构造函数模式一样,函数无法重用
- 使用场景:寄生式继承适合主要关注对象,而不在乎类型和构造函数的场景
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function createAnother(original) {
let clone = object(original) // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
console.log('hi')
}
return clone // 返回这个对象
}
// eg
let person = {
name: 'xiaoming',
friends: ['Jack','Rose','Shelby']
}
let anotherPerson = createAnother(person)
anotherPerson.sayHi() // 'hi'
6.寄生式组合继承(引用类型的最佳继承模式)
- 核心:在子类中调用父类构造函数实现属性继承,通过
Object.create(Parent.prototype)方法创建新对象实现对父类原型方法的继承 - 优点: 完美
- 缺点: 无
function Parent(name) {
this.name = name // 实例基本属性 (该属性,强调私有,不共享)
this.ar = [1] // 实例引用属性 (该属性,强调私有,不共享)
}
Parent.prototype.sayName = function () { // 方法需要复用,共享的方法定义在父类的原型上
console.log(this.name)
}
function Child(name,like) {
Parent.call(this,name) // 核心 继承属性
this.like = like
}
Child.prototype = Object.create(Parent.prototype)
// 修复构造函数指向
Child.prototype.constructor = Child
let boy1 = new Child('xiaoming','apple')
let boy2 = new Child('xiaohong','orange')
let parent = new Parent('baba')
console.log(boy1.constructor) // Child
console.log(boy2.constructor) // child
console.log(parent.constructor) // Parent
7.ES6 继承
- 使用super的注意事项
super只能在派生类构造函数和静态方法中使用。- 不能单独引用
super关键字,要么用它调用构造函数,要么用它引用静态方法。 - 调用
super()会调用父类构造函数,并将返回的实例赋值给this。 super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。- 如果没有定义类构造函数,在实例化派生类时会调用
super(),而且会传入所有传给派生类的参数。 - 在类构造函数中,不能在调用
super()之前引用this。 - 如果在派生类中显式定义了构造函数,则要么必须在其中调用
super(),要么必须在其中返回一个对象。
class Parent {
constructor(name,age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name)
}
}
class Child extends Parent {
constructor(name,age) {
// 核心
super(name,age)
this.friends = ['Jack','Rose']
}
sayAge() {
console.log(this.age)
}
}
let person1 = new Child('小明',18)
let person2 = new Child('小红',19)
console.log(person1 instanceof Child) // true
console.log(person1 instanceof Parent) // true
person1.friends.push('小张')
console.log(person1.friends) // ['Jack','Rose','小张']
console.log(person2.friends) // ['Jack','Rose']
person1.sayName() // 小明
person2.sayName() // 小红