原型链继承
原型继承很简单,就是将子类的原型挂载到父类实例上。
// 一个父类
function Father(){
this.name = "xhc",
this.age = 21,
this.hobbies = [] // 引用类型属性
}
// 父类原型上的属性/方法
Father.prototype.getAge = function(){
console.log(this.age)
}
// 一个子类
function Child(){}
Child.prototype = new Father(); // 原型链继承的方式
let child1 = new Child();
console.log(child1.name); // xhc -- 继承了父类的属性
child1.getAge(); // 21 -- 调用了父类原型的方法
// 测试引用类型共享问题
child1.hobbies.push("ping-pong")
console.log(child1.hobbies); // ["ping-pong"]
let child2 = new Child();
console.log(child2.hobbies); // ["ping-pong"]
这种方法实现继承,有个明显的优点就是简单。另外还有下面这些特点:
- 只是单一继承无法实现多继承。
这一点很好理解,Child.prototype
只能等于一个值,无法同时又等于其他值。
-
新实例无法向父类构造函数传参。
-
来自父类以及父类所有属性会被所有实例共享。
原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!
构造器继承
构造器继承借用了call
等可以改变this指向的函数来实现继承。有个很明显的优点就是可以在父类构造器中传入参数。(这个可以比较一下原型链继承)
// 一个父类
function Father(faName,faAge){
this.name = faName,
this.age = faAge
}
// 父类原型上的属性/方法
Father.prototype.getAge = function(){
console.log(this.age)
}
// 一个子类
function Child(childName, childAge){
Father.call(this, childName, childAge); // 构造器继承
}
let child = new Child('xhc', 21);
console.log(child.name); // xhc -- 构造器可以传参
child.getAge(); // TYPEERROR -- 无法继承父类原型上的属性
特点 | 缺陷 |
---|---|
1.只继承了父类的属性和方法,没有继承父类原型的属性和方法 | 1.只能继承父类构造函数的属性 |
2.可以实现多继承,通过call方法可以实现 | 2.无法实现构造函数的调用,每次用的时候都要调用一次父类构造函数 |
3.可以向父类构造函数传参 | 3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能 |
4.解决了子类实例共享父类引用属性的问题 | 4.实例只是子类的实例,不是父类的实例。(用instanceof可以检测出来) |
组合继承
这种继承方式就是结合了原型链继承和构造函数继承,合二为一,提取各自的特点来实现继承。
// 一个父类
function Father(faName,faAge){
this.name = faName,
this.age = faAge,
this.hobbies = [] // 实例引用属性
}
// 父类原型上的属性
Father.prototype.getAge = function(){
console.log(this.age)
}
// 一个子类
function Child(childName, childAge){
Father.call(this, childName, childAge); // 组合继承核心1
}
Child.prototype = new Father(); // 组合继承核心2
let child1 = new Child('xhc', 21);
console.log(child1.name); // xhc -- 构造器可以传参
child1.getAge(); // xhc -- 继承了父类原型方法
// 测试一下实例引用问题
child1.hobbies.push('ping-pong');
console.log(child1.hobbies); // ['ping-pong']
let child2 = new Child('lyc', 21);
console.log(child2.hobbies); // [] -- 这里说明了不存在引用类型属性共享问题
这样的话,组合继承就是前面两种继承的结合体了:
特点 | 缺陷 |
---|---|
1.可以向父类构造函数传递参数 | 1.调用了两次父类构造函数(耗内存) |
2.继承了父类以及父类原型上的属性和方法 | 2.子类的构造函数会代替原型上的那个父类构造函数 |
3.不存在引用属性共享问题 | |
4.每个新实例引入的构造函数属性是私有的 | |
5.既是父类的实例,也是子类的实例 |
寄生组合继承
寄生组合继承是对组合继承的优化,组合继承的最大缺点就是调用了两次构造函数,针对这个,出现了寄生组合继承。
// 一个父类
function Father(faName,faAge){
this.name = faName,
this.age = faAge,
this.hobbies = [] // 实例引用属性
}
// 父类原型上的属性
Father.prototype.getAge = function(){
console.log(this.age)
}
// 一个子类
function Child(childName, childAge){
Father.call(this, childName, childAge); //
}
// Child.prototype = new Father(); // 下面两行取代了这一行
Child.prototype = Object.create(Father.prototype)
Child.prototype.constructor = Child; // 重新指定constructor
let child1 = new Child('xhc', 21);
console.log(child1.name); // xhc -- 构造器可以传参
child1.getAge(); // xhc -- 继承了父类原型方法
// 测试一下实例引用问题
child1.hobbies.push('ping-pong');
console.log(child1.hobbies); // ['ping-pong']
let child2 = new Child('lyc', 21);
console.log(child2.hobbies); // [] -- 这里说明了不存在引用类型属性共享问题