说说你对js的继承的了解
什么是继承
js中,继承是一种允许我们在已有类的基础上创建新类的机制;它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
为什么要有继承
提高代码的重用性、较少代码的冗余
继承的相关实现
想要实现继承,就得有个被继承的父类
// 最上层函数
function Animal(name) {
this.name = name || 'Animal'
this.eat = function (food) {
console.log(`${this.name}正在吃${food}`)
}
}
// 原型方法
Animal.prototype.sleep = function (time) {
console.log(`${this.name}睡了多久${time}`)
}
原型链继承
-
核心
将父类的实例作为子类的原型
function Cat() {}
Cat.prototype = new Animal()
// 想要为子类新增属性和方法,必须放在new Animal之后
Cat.prototype.play = function(what) {
console.log(`${this.name}正在玩${what}`)
}
const cat = new Cat('猫猫')
cat.eat('什么啊') // Animal正在吃什么啊
cat.play('什么啊') // Animal正在玩什么啊
优点
- 简单,易于实现
- 父类新增原型的方法或属性,子类都能访问到
缺点
- 想要为子类新增属性和方法,必须要在new Animal()这样的语句之后执行
- 实例1修改父类原型上的引用类型属性,实例2的属性也会被影响到
- 无法向Animal构造函数传参
借用构造函数继承
-
核心
使用父类的构造函数增强子类实例
// 借用构造函数
function Cat(name) {
this.name = name
this.drink = function (what) {
console.log(`${this.name}正在喝${what}`)
}
Animal.call(this,'猫猫啊')
}
Cat.prototype.play = function (what) {
console.log(`${this.name}正在玩${what}`)
}
const cat = new Cat('小猫')
cat.eat('什么啊')
console.log(cat.name)
// cat.sleep("多久啊") 访问不到sleep,会报错
cat.drink("什么啊")
cat.play('什么啊')
// 猫猫啊正在吃什么啊
猫猫啊
猫猫啊正在喝什么啊
猫猫啊正在玩什么啊
优点
- 解决了1中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
缺点
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
组合继承
-
核心
通过调用父类构造,继承父类的属性并保留传参的有点,然后通过父类实例作为子类的原型,实现函数复用
// 组合继承
function Cat(name) {
this.name = name
console.log(this.name, "this.name") // 大猫 this.name
this.drink = function (what) {
console.log(name, this.name, 'aaa') // 大猫 猫猫啊 aaa
console.log(`${this.name}正在喝${what}`)
}
Animal.call(this, "猫猫啊")
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
// 想要为子类新增属性和方法,必须放在new Animal之后
Cat.prototype.play = function (what) {
console.log(`${this.name}正在玩${what}`)
}
const cat = new Cat('大猫')
cat.eat('什么啊') // 猫猫啊正在吃什么啊
console.log(cat.name)// 猫猫啊
cat.sleep("多久啊")// 猫猫啊睡了多久多久啊
cat.drink("什么啊")// 猫猫啊正在喝什么啊
cat.play('什么啊')// 猫猫啊正在玩什么啊
缺点
调用了两次父类构造函数
原型式继承
必须得有一个对象作为另一个对象的基础
function create(objParam) {
const F = function () {}
F.prototype = objParam
return new F()
}
// 一、可以直接创建一个对象
const Person = {
name: '名字1',
friends: ['朋友1','朋友2','朋友3']
}
const people1 = create(Person)
console.log(people1.name) // 名字1
people1.name = '宠物2'
people1.friends.push('朋友4')
const people2 = create(Person)
console.log(people2.name) // 名字1
console.log(people2.friends) // ["朋友1", "朋友2", "朋友3", "朋友4"]
// 二、当然也可以用函数实例
const animal = new Animal('宠物')
const people1 = create(animal)
console.log(people1.name) // 宠物
const people2 = create(animal)
people2.eat('什么') // 宠物正在吃什么
people2.sleep('哇') // 宠物睡了多久哇
// 这个create函数和es5新增的Object.create()一样
**缺点:**和原型链继承一样,包含引用类型的属性始终都会共享相应的值
寄生式继承
也就是在原型式上套了个壳子
function create(objParam) {
const F = function () {}
F.prototype = objParam
return new F()
}
function createAdd(objParam) {
const add = create(objParam)
add.jump = function(high) {
console.log(`跳那么高啊${high}`)
}
return add
}
const animal = new Animal()
const animalT = createAdd(animal)
animalT.jump('五米') // 跳那么高啊五米
缺点: 使用寄生式继承来为对象添加方法,不能做到复用,效率降低,这一点和构造函数相似
寄生组合式继承
子类构造函数复制父类的属性和方法、子类原型只继承父类的原型属性和方法
function inheritPrototype(Child,Parent) {
const prototype = Object.create(Parent.prototype)
prototype.constructor = Child
Child.prototype = prototype
}
function Cat(name) {
this.name = name
console.log(this.name, "this.name") // 大猫 this.name
this.drink = function (what) {
console.log(name, this.name, 'aaa') // 大猫 猫猫啊 aaa
console.log(`${this.name}正在喝${what}`)
}
Animal.call(this, "猫猫啊")
}
inheritPrototype(Cat,Animal)
Cat.prototype.play = function (what) {
console.log(`${this.name}正在玩${what}`)
}
const cat = new Cat('大猫')
cat.eat('什么啊') // 猫猫啊正在吃什么啊
console.log(cat.name)// 猫猫啊
cat.sleep("多久啊")// 猫猫啊睡了多久多久啊
cat.drink("什么啊")// 猫猫啊正在喝什么啊
cat.play('什么啊')// 猫猫啊正在玩什么啊
避免了在Cat上创建不必要的、多余的属性。
参考文章:
高程3、4