「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」
前言
之前在面试过程中一直被问到继承😭,自己也总是答的不好,所以有时候因为这里会吃个大亏,因此此次来做一次总结,总结一下js的继承。
准备工作
首先是准备一个简单的父类
function Father(name){
// 属性
this.name = name || 'father'
// 实例方法
this.getName = function(){
console.log('实例方法名,打印名字为:'+this.name);
}
// 实例引用属性
this.arr = [1,2,3]
}
Father.prototype.hasName = function(){
console.log('原型链方法名'+this.name);
}
Father.prototype.age = 10
原型链继承
他的核心原理就是将子类的原型指向父类的实例
// 原型链继承
function Child1 (name){
// this.name = name || 'child1'
}
// Child1.prototype.play = function() { // 放在前面抛出错误,因为后面的prototype已经是new Father了, 放后面才能正常进行
// console.log(this.name + '正在玩!');
// };
Child1.prototype = new Father()
Child1.name = 'child1'
Child1.prototype.play = function() {
console.log(this.name + '正在玩!');
};
// 测试代码
let child11 = new Child1('child11') // 参数无效
let child12 = new Child1('child12')
Child1.name = 'C1'
child11.arr.push(4)
console.log(child11 instanceof Child1); // true
console.log(child11 instanceof Father); // true
child11.play() // play方法在前面定义抛出错误,后面则不会
child11.getName() // 实例方法名,打印名字为:father
child11.hasName() // 原型链方法名father
console.log(child11.name); // father
console.log(child12.name); // father
console.log(child11.arr); // [ 1, 2, 3, 4 ]
console.log(child12.arr); // [ 1, 2, 3, 4 ]
优点:
- 写法简单
缺点:
- 不能实现多继承,因为子类的原型只能指向一个父类实例
- 无法向父类传参
- 父类上的引用数据类型为所用子类共享,一个子类实例改变,父类的引用属性也会变,其他子类的该属性也会改变
- 如果要为子类新增属性或者方法,只能在
new Father()之后,并不能放在构造函数中,如上的代码示例,如果新增的方法放在改变子类原型的指向之前,改变指向之后新增的方法自然就没用了。
构造函数实现继承 (又叫借用构造函数继承)
其核心是通过
call或者apply函数在字类中调用父类函数,并将this指向子类
// 借用构造函数继承
function Child (name){
Father.call(this,name)
}
Child.prototype.play = function() {
console.log(this.name + '正在玩!');
};
// 测试代码
let child = new Child('child')
console.log(child.name); // child
console.log(child instanceof Father); // false
console.log(child instanceof Child); // true
child.play() // child正在玩!
child.getName() // 实例方法名,打印名字为:child
// child.hasName() // 不能访问父类原型的方法
优点:
- 可以实现多继承,只需在子类中多次调用构造函数即可
- 可以向父类传参,调用时传参即可
- 不同的对象实例拥有不同一份属性和方法,不会互相影响
缺点:
- 实例并不是父类的实例,由
child instanceof Father为false可知 - 只能获取到父类上的属性和方法,父类原型上的属性和方法不能获取到
- 每次新建一个子类,都会调用一次父类,无法复用,性能较低
组合继承 (结合原型链继承和借用构造继承)
要点就是结合前面两种方式,这样子类实例也可以获取到父类原型上的方法和属性
function Child(name) {
Father.call(this, name)
}
Child.prototype = new Father()
Child.prototype.construtor = Child
Child.prototype.play = function () {
console.log(this.name + '正在玩!')
}
let child = new Child('child')
console.log(child.name) // child
child.hasName() // 原型链方法名child
console.log(child instanceof Father) // true
优点:
- 兼顾急用构造函数继承的优点
- 子类实例也是父类的实例,也能够访问到父类原型上的属性和方法
缺点:
- 调用了两次父类,子类拥有父类上相同的两套属性和方法
- 每次新建子类实例都会调用父类函数的缺点还未解决
原型式继承
用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
function creat(o){
function F(){}
F.prototype = o
return new F()
}
// 测试代码
let sub = new Father()
let child = creat(sub)
child.name = 'child'
child.arr.push(4)
console.log(child instanceof Father); // false
child.getName()
child.hasName()
console.log(sub.arr); // [1,2,3,4]
优点:
- 可以实现在拥有一个对象的情况,再在他的基础上创建一个新的对象
- 可以访问父类对象上的属性和方法
缺点:
- 与原型链继承一样,对父类的引用属性做修改时,其他该父类下的实例的该属性也会被修改
- 创建出来的实例并不是父类的实例
- 无法实现复用,属性和方法的添加只能在实例被创建出来后添加
寄生继承
通过原型继承的函数,再套一层来创建对象
function creat(o){
function F(){}
F.prototype = o
return new F()
}
function creatobj(o){
let obj = creat(o)
// obj.name = 'child'
return obj
}
// 测试代码
let sub = new Father()
let child = creatobj(sub)
child.name = 'child'
child.arr.push(4)
console.log(child instanceof Father); // false
child.getName() // 实例方法名,打印名字为:child
child.hasName() // 原型链方法名child
console.log(sub.arr); // [1,2,3,4]
优点:
与原型继承相似
缺点:
与原型继承相似
寄生组合继承
组合继承最大的缺点就是父类函数会被调用两次,所以会导致实例会有两组一模一样的属性和方法,通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点。
function Child(name) {
Father.call(this, name)
}
(function () {
function Super() {}
Super.prototype = Father.prototype
Child.prototype = new Super()
})()
let child = new Child('child')
let child2 = new Child('chil2')
console.log(child.name) // child
child.getName() // 实例方法名,打印名字为:child
child.hasName() // 原型链方法名child
child.arr.push(4)
console.log(child2.arr) // [1,2,3]
console.log(child instanceof Father) // true
console.log(child instanceof Child); //true
这种算是比较完美的方案了,通过Super函数避免了重复调用两次父类的情况
后言
我们要掌握这么多种继承方法,光靠背肯定式不行的,重点是要理解js实现继承的思想
参考资料:
- 《JavaScript高级程序设计第四版》