引言
在面滴滴出行的时候,面试官问了我这样一个问题,当时回答得不怎么好,表达得可能不是那么全面,于是我总结了一下
1.原型链继承
下面我们来看一段代码
function Parent() {
this.name = '宇哥'
}
Parent.prototype.age = 18
Child.prototype = new Parent()
function Child() {
}
let child1 = new Child()
console.log(child1.name) // 宇哥
console.log(child1.age) //18
在这里,我们有一个父类 Parent,一个子类 Child,我们在父类的原型上添加了一个 key 为age,value为18 的一个属性,我们通过原型链继承的方式来实现 子类Child继承 父类Parent
第一个打印的结果是 '宇哥'
第二个打印的是18
说明成功的实现了继承
但是面试官可能会问:这种继承的缺点是什么?
function Parent() {
this.arr= [1,2,3,4]
}
Parent.prototype.age = 18
Child.prototype = new Parent()
function Child() {
}
let child1 = new Child()
let child2 = new Child()
child1.arr.push(5)
console.log(child1.arr) //[1,2,3,4,5]
console.log(child2.arr) //[1,2,3,4,5]
在这里,我们创建了两个实例对象child1和child2,我们修改了实例对象child1上的属性(在数组里面插入了一个数字5),
我们发现两个打印都是[1,2,3,4,5],说明我们改变第一个实例对象的时候,第二个实例对象受到了影响
这就是缺点!
用原型链实现继承,当要继承父类的属性是引用类型的时候,会导致属性共享
2.构造函数继承
同样看这段代码
function Parent() {
this.name = '宇哥'
}
Parent.prototype.age = 18
function Child() {
Parent.call(this)
}
let child1 = new Child()
console.log(child1.name) // 宇哥
console.log(child1.age) // undefined
在这里我们将Child()的this指向Parent的this
这个时候,子类可以访问到父类中的属性name
但是不能访问到父类prototype(原型上的属性)age,这就是它的缺点
用构造函数来实现继承的时,子类不能访问到父类prototype(原型上的属性)
3.组合继承(把原型链继承和构造函数结合一下)
function Parent() {
this.arr=[1,2,3,4]
}
Parent.prototype.age = 18
Child.prototype=new Parent()
function Child() {
Parent.call(this)
}
let child1 = new Child()
let child2 =new Child()
child1.arr.push(5)
console.log(child1.arr) // [1,2,3,4,5]
console.log(child2.arr) // [1,2,3,4]
console.log(child1.age) // 18
当我们改变 child1 上的属性时,child2不会受到影响
子类可以访问到父类prototype 上的属性
用组合继承的方法,我们就解决了上面两个问题,一个是属性共享,另外一个是不能访问到父类prototype上的属性
但是组合继承也有缺点
组合继承需要调用两次父类的构造函数,一次是在子类的构造函数中调用 Parent.call (this),一次是通过 Child.prototype = new Parent () 实现原型继承。
这样做既增加了调用次数,也可能导致父类构造函数中的逻辑被执行多次。
总结来说,组合继承是一种常用的继承方式,它既能够继承父类的属性和方法,又能够拥有自身独有的属性和方法。
但它的缺点是在创建子类实例时会重复调用父类的构造函数,可能导致内存占用过大,并且需要额外处理父类构造函数中的逻辑
4.寄生组合继承
function Parent() {
this.arr = [1, 2, 3, 4];
}
Parent.prototype.age = 18;
function Child() {
Parent.call(this); // 继承 Parent 的实例属性
}
// 创建一个继承自 Parent 原型的新对象,但不立即执行 Parent 构造函数
Child.prototype = Object.create(Parent.prototype);
let child1 = new Child();
let child2 = new Child();
child1.arr.push(5);
console.log(child1.arr); // 输出: [1, 2, 3, 4, 5]
console.log(child2.arr); // 输出: [1, 2, 3, 4]
console.log(child1.age); // 输出: 18
- 避免构造函数的重复调用:与直接将
Child.prototype
设置为new Parent()
不同,Object.create(Parent.prototype)
只是设置原型关系,而不会立即调用Parent
构造函数。这样可以避免不必要的初始化操作。
5.ES6 里面的class
class Parent {
constructor() {
this.name = '宇哥';
}
getAge() {
return 18;
}
}
class Child extends Parent {
constructor() {
super(); // 调用父类的构造函数
}
}
let child1 = new Child();
console.log(child1.name); // 输出: 宇哥
console.log(child1.getAge()); // 输出: 18
-
定义父类
Parent
:- 使用
class
关键字定义一个名为Parent
的类。 - 在
Parent
类的构造函数中初始化name
属性。 - 在
Parent
类中定义一个方法getAge
,返回年龄 18。这里使用方法而不是直接在原型上定义属性,是为了更好地展示如何继承方法。
- 使用
-
定义子类
Child
:- 使用
class
关键字定义一个名为Child
的类。 - 使用
extends
关键字指定Child
继承自Parent
。 - 在
Child
类的构造函数中调用super()
,这是调用父类构造函数的语法,确保父类的构造函数被执行,从而初始化name
属性。
- 使用
-
创建
Child
实例:- 使用
new
关键字创建Child
的实例child1
。 - 访问
child1
的name
属性和getAge
方法。
- 使用