这篇文章称为笔记更为适合一些,内容来源于《JavaScript 高级程序设计 (第三版)》第六章 6.3 继承
JavaScript 的几种继承形式
- 原型链继承
- 借助构造函数继承(经典继承)
- 组合继承:原型链 + 借用构造函数(最罕用)
- 原型式继承(Object.create)
- 寄生式继承
-
寄生组合式继承(最现实)
- ES6 中的继承
1. 原型链继承
子类型的原型为父类型的一个实例对象
function Parent() {
this.name = 'bigStar';
this.colors = ['red', 'blue', 'yellow'];
}
Parent.prototype.getName = function() {console.log(this.name)
}
function Child() {this.subName = 'litterStar';}
// 外围代码: 子类型的原型为父类型的一个实例对象
Child.prototype = new Parent();
let child1 = new Child();
let child2 = new Child();
child1.getName(); // bigStar
child1.colors.push('pink');
// 批改 child1.colors 会影响 child2.colors
console.log(child1.colors); // ['red', 'blue', 'yellow', 'pink']
console.log(child2.colors); // ['red', 'blue', 'yellow', 'pink']
留神外围代码:
Child.prototype = new Parent();
特点:
- 父类新增在构造函数下面的办法,子类都能拜访到
毛病:
- 来自原型对象的所有属性被所有实例共享,child1 批改 colors 会影响 child2 的 colors
- 创立子类实例时,无奈向父类的构造函数传参
2. 借助构造函数继承(经典继承)
在子类的构造函数中应用 call() 或者 apply() 调用父类型构造函数
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
Parent.prototype.getName = function() {console.log(this.name)
}
function Child(name, age) {
// 外围代码:“借调”父类型的构造函数
Parent.call(this, name);
this.age = age;
}
let child1 = new Child('litterStar');
let child2 = new Child('luckyStar');
console.log(child1.name); // litterStar
console.log(child2.name); // luckyStar
// 这种形式只是实现局部的继承,如果父类的原型还有办法和属性,子类是拿不到这些办法和属性的。child1.getName(); // TypeError: child1.getName is not a function
留神外围代码:
Parent.call(this, name);
特点:
- 防止援用类型的属性被所有实例共享
- 创立子类实例时,能够向父类传递参数
毛病
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和办法,不能继承原型属性和办法
- 无奈实现函数复用,每次创立实例都会创立一遍办法,影响性能
3. 组合继承:原型链 + 借用构造函数(最罕用)
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
Parent.prototype.getName = function() {console.log(this.name)
}
function Child(name, age) {
// 外围代码①
Parent.call(this, name);
this.age = age;
}
// 外围代码②: 子类型的原型为父类型的一个实例对象
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 能够通过子类给父类的构造函数传参
let child1 = new Child('litterStar');
let child2 = new Child('luckyStar');
child1.getName(); // litterStar
child2.getName(); // luckyStar
child1.colors.push('pink');
// 批改 child1.colors 不会影响 child2.colors
console.log(child1.colors); // ['red', 'blue', 'yellow', 'pink']
console.log(child2.colors); // ['red', 'blue', 'yellow']
留神外围代码:
Parent.call(this, name);
和Child.prototype = new Parent();
特点
-
交融了原型链继承和借用构造函数的长处,称为 JavaScript 中最罕用的继承模式。
毛病
-
调用了两次父类构造函数,生成了两份实例
- 一次是设置子类型实例的原型的时候
Child.prototype = new Parent();
- 一次是创立子类型实例的时候
let child1 = new Child('litterStar');
, 调用 new 会执行Parent.call(this, name);
, 此时会再次调用一次Parent
构造函数
- 一次是设置子类型实例的原型的时候
4. 原型式继承(Object.create)
借助原型能够基于现有办法来创建对象,var B = Object.create(A) 以 A 对象为原型,生成 A 对象,B 继承了 A 的所有属性和办法。
const person = {
name: 'star',
colors: ['red', 'blue'],
}
// 外围代码:Object.create
const person1 = Object.create(person);
const person2= Object.create(person);
person1.name = 'litterstar';
person2.name = 'luckystar';
person1.colors.push('yellow');
console.log(person1.colors); // ['red', 'blue', 'yellow']
console.log(person2.colors); // ['red', 'blue', 'yellow']
留神外围代码:
const person1 = Object.create(person);
特点
-
没有严格意义上的构造函数,借助原型能够基于已有对象创立新对象
毛病
- 来自原型对象的所有属性被所有实例共享,person1 批改 colors 会影响 person2 的 colors,这点跟原型链继承一样。
5. 寄生式继承
创立一个用于封装继承过程的函数,该函数在外部以某种形式来加强对象
function createObj (original) {
// 通过调用函数翻新一个新对象
var clone = Object.create(original);
// 以某种形式来加强这个对象
clone.sayName = function () {console.log('hi');
}
// 返回这个对象
return clone;
}
毛病:每次创建对象都会创立一遍办法,跟借助构造函数模式一样
6. 寄生组合式继承(最现实的)
咱们能够先回顾一下 JavaScript 最罕用的继承模式:组合继承(原型链 + 借用构造函数),它的最大毛病是会调用两次父构造函数(Child.prototype = new Parent();
和 let child1 = new Child('litterStar');
)。
咱们是否能够想方法是调用一次?能够让 Child.prototype 拜访到 Parent.prototype。
咱们不能间接应用 Child.prototype = Parent.prototype
来实现,因为会呈现一些副作用,你可能在批改 Child.prototype
的时候会批改 Parent.prototype
。
能够应用 Object.create(...)
来实现
Object.create
MDN 上的解释:它会创立一个新对象,应用现有的对象来提供新创建的对象的__proto__
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
Parent.prototype.getName = function() {console.log(this.name)
}
function Child(name, age) {
// 外围代码①
Parent.call(this, name);
this.age = age;
}
// 外围代码②
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
留神外围代码:
Parent.call(this, name);
和Child.prototype = Object.create(Parent.prototype);
寄生组合式继承,集寄生式继承和组合式继承的长处,是援用类型最现实的继承范式。
7. ES6 中 class 的继承
ES6 中引入了 class 关键字,能够通过 extends 关键字实现继承。
class Parent {}
class Child extends Parent {constructor(name, age, color) {// 调用父类的 constructor(name, age)
super(name, age);
this.color = color;
}
toString() {return this.color + ' ' + super.toString(); // 调用父类的 toString()}
}
class 关键字只是原型的语法糖,JavaScript 继承依然是基于原型实现的。
参考
- JavaScript 常见的六种继承形式
- JS 继承的几种形式
- JavaScript 深刻之继承的多种形式和优缺点
- ECMAScript6 入门之 Class 的继承