原型链继承
// 父类
function Parent(name) {
this.name = name;
}
Parent.prototype.age = 20; // 在父类原型上添加属性
// 子类
function Per() {
this.name = 'Rory';
}
Per.prototype = new Parent();
const per = new Per();
console.log(per)
打印结果如下:
上面定义了一个 Parent
类并传了一个 name
值,而且在 Parent
的原型上添加了一个 age
属性,然后定义了一个子类 Per
,并将子类的原型设置为 Parent
的实例,这种方式是利用原型来实现继承,它就是让新实例的原型等于父类的实例。这种继承方式可继承的属性有:
- 实例的构造函数的属性
- 父类构造函数的属性,比如
Parent
的name
- 父类原型的属性 比如
age
缺点:
- 无法向父类构造函数传参,比如上图中新实例的原型中的
name
为undefined
- 所有新实例都共用父类的属性,如果一个实例修改了原型会影响其它的实例
构造函数继承
// 父类
function Parent(name) {
this.name = name;
}
Parent.prototype.age = 20; // 在父类原型上添加属性
function Per() {
Parent.call(this, 'Rory');
this.sex = '男';
}
const per = new Per();
console.log(per)
打印结果如下
构造函数继承使用了 call()
或 apply()
将父类构造函数引入子类函数,这种继承方式的特点有:
- 只继承父类构造函数的属性,没有继承父类原型上的属性
- 解决了原型链继承的缺点
- 可以继承多个构造函数
- 在子类的实例中可以传入父类的实例参数
缺点:
- 只能继承父类构造函数的属性
- 每次使用都得重新调用,无法复用
- 每个新实例都有父类构造函数的属性,会比较臃肿
组合式继承
// 父类
function Parent(name) {
this.name = name;
}
Parent.prototype.age = 20; // 在父类原型上添加属性
function Per(name) {
Parent.call(this, name);
}
Per.prototype = new Parent();
const per = new Per('Rory');
console.log(per);
打印结果如下:
这种继承方式通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
缺点:
- 调用了两次父类构造函数,所以会比较耗内存
- 子类的构造函数会代替原型上的那个父类构造函数。
原型式继承
// 父类
function Parent(name) {
this.name = name;
}
Parent.prototype.age = 20; // 在父类原型上添加属性
// 封装一个函数
function content(obj) {
function F(){};
F.prototype = obj;
return new F();
}
const p = new Parent('Rory');
const per = content(p);
console.log(per);
console.log(per.name);
console.log(per.age);
打印结果如下:
这种继承方式是先定义一个函数 content
,然后在内部定义一个构造函数 F
,将该构造函数的原型设置为传入的参数,参数是一个对象,然后将 F
实例化后返回。
缺点:
- 所有实例都会继承原型上的属性
- 无法复用,返回的是一个实例,属性要另外添加
寄生式继承
// 父类
function Parent(name) {
this.name = name;
}
Parent.prototype.age = 20; // 在父类原型上添加属性
// 封装一个函数
function content(obj) {
function F(){};
F.prototype = obj;
return new F();
}
const Per = new Parent();
function subObject(obj) {
const per = content(obj);
per.name = 'Rory';
return per;
}
const p = subObject(Per);
console.log(p);
打印结果如下:
这种继承方式相当于给原型式继承外面套了个壳子,可以在 subObject
里新增属性或方法
优点:
- 没有创建自定义类型,因为只是套了个壳子返回对象,这个函数就成了创建的新对象
缺点:
- 没用到原型,无法复用
寄生组合式继承
// 父类
function Parent(name) {
this.name = name;
}
Parent.prototype.age = 20; // 在父类原型上添加属性
function content(obj) {
function F(){}
F.prototype = obj;
return new F();
}
const Per = content(Parent.prototype);
function subObject() {
Parent.call(this, 'Rory');
}
subObject.prototype = Per;
Per.constructor = subObject; // 修复实例
const p = new subObject();
console.log(p)
打印结果如下:
这种继承方式修复了组合继承的问题,在函数内返回对象然后调用,函数的原型等于另一个实例。在函数中用 apply
或者 call
引入另一个构造函数,可传参
特点:
- 可以向父类传参
- 子类实例可以继承到父类原型上的属性
- 可以复用
ES6中的继承
通过 class
定义一个类,再用 extends
进行继承,通过 super
继承父类的属性或方法
class Parent {
constructor(name, age) {
this.name = name;
this.age = age;
}
drink() {
console.log('喝水')
}
}
class Per extends Parent {
constructor(name, age, sex) {
super(name, age);
this.sex = sex;
}
drink() {
super.drink();
}
}
const p = new Per('Rory', 22, '男');
console.log(p)
打印结果如下: