首先定义一个父类
function Person(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
}
Person.prototype.sex = '男'
Person.prototype.saySex = function() {
console.log(this.sex)
}
1. 原型链继承:将父类的实例作为子类的原型
function Coder(name) {
this.name = name
}
Coder.prototype = new Person()
let c = new Coder('yiyiyi')
console.log(c.name) // yiyiyi
c.sayName() // yiyiyi
c.saySex() // 男
console.log(c instanceof Coder) // true
console.log(c instanceof Person) // true
- 优点
- 纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法、原型属性,子类都能访问到
- 简单、易于实现
- 缺点
- 可以在Coder构造函数中,为Coder实例增加实例属性。新增的属性和方法必须放在new Person()语句之后执行
- 无法实现多继承,因为原型一次只能被一个实例更改
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父构造函数传参
2. 构造继承:复制父类的实例属性给子类
function Coder(name) {
Person.call(this)
this.name = name
}
let c = new Coder('yiyiyi')
console.log(c.name) // yiyiyi
c.sayName() // yiyiyi
c.saySex() // Uncaught TypeError: c.saySex is not a function
console.log(c.sex) // undefined
console.log(c instanceof Person) // false
console.log(c instanceof Coder) // true
- 优点
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
- 缺点
- 实例并不是父类实例,只是子类的实例
- 只能继承父类实例的属性和方法,不能继承其原型上的属性和方法
- 无法实现函数复用,每个子类都有父类实例的副本,影响性能
3. 实例继承:为父类实例添加新特征,作为子类实例返回
function Coder(name) {
let p = new Person()
p.name = name
return p
}
let c = new Coder('yiyiyi')
console.log(c.name) // yiyiyi
c.sayName() // yiyiyi
c.saySex() // 男
console.log(c.sex) // 男
console.log(c instanceof Person) // true
console.log(c instanceof Coder) // false
- 优点
- 不限制调用方法,不管是new子类还是new父类,返回的对象具有相同的效果
- 缺点
- 实例是父类的实例,不是子类的实例
- 不支持多继承
4. 拷贝继承:对父类实例中的方法和属性拷贝给子类的原型
function Coder(name) {
let p = new Person()
for(let k in p) {
Coder.prototype[k] = p[k]
}
Coder.prototype.name = name
}
let c = new Coder('yiyiyi')
console.log(c.name) // yiyiyi
c.sayName() // yiyiyi
c.saySex() // 男
console.log(c.sex) // 男
console.log(c instanceof Person) // false
console.log(c instanceof Coder) // true
- 优点
- 支持多继承
- 缺点
- 效率低、性能差、内存占用高(因为需要拷贝父类属性)
- 无法获取父类不可枚举的方法(不可枚举的方法,不能通过for in访问到)
5. 组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Coder(name) {
Person.call(this)
this.name = name
}
Coder.prototype = new Person()
// 修复构造函数的指向
Coder.prototype.constructor = Coder
let c = new Coder('yiyiyi')
console.log(c.name) // yiyiyi
c.sayName() // yiyiyi
c.saySex() // 男
console.log(c.sex) // 男
console.log(c instanceof Person) // true
console.log(c instanceof Coder) // true
- 优点
- 弥补了构造继承的缺点,现在既可以继承实例的属性和方法,也可以继承原型的属性和方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可以复用
- 缺点
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
6. 寄生组合继承:通过寄生方式,砍掉父类的实例属性,避免了组合继承生成两份实例的缺点
function Coder(name) {
Person.call(this)
this.name = name
}
(function() {
// 创建一个没有实例方法的类
let None = function() {}
None.prototype = Person.prototype
// 将实例作为子类的原型
Coder.prototype = new None()
// 修复构造函数指向
Coder.prototype.constructor = Coder
})()
let c = new Coder('yiyiyi')
console.log(c.name) // yiyiyi
c.sayName() // yiyiyi
c.saySex() // 男
console.log(c.sex) // 男
console.log(c instanceof Person) // true
console.log(c instanceof Coder) // true
- 优点
- 完美
- 缺点
- 实现起来复杂
7. Class继承:使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super
class Coder extends Person {
constructor(name) {
super(name)
this.name = name
}
}
let c = new Coder('yiyiyi')
console.log(c.name) // yiyiyi
c.sayName() // yiyiyi
c.saySex() // 男
console.log(c.sex) // 男
console.log(c instanceof Person) // true
console.log(c instanceof Coder) // true