前言
- 写这种文章总感觉有点怪怪的,就像是孔乙己在说茴字有几种写法一样。
- 开玩笑的,像这种基础知识还是很重要的
正文
- 首先可以用下面的这一张图来进行总结
- 基于上面这张图,分别总结一下js的六种继承方式
原型链继承
function Parent() {
this.name = 'parent';
this.play = [1, 2, 3]
}
function Child() {
this.type = 'child';
}
Child.prototype = new Parent();
console.log(new Child());
- 根据红宝书第4版p238中所说,ECMA-262把原型链定义为ECMA的主要继承方式
- 基本思想
- 缺点
- 一旦改变了子类中该父类的属性,那么继承了该父类的其余子类的继承属性值也会跟着改变
- 解决方法
- 当然缺点也是很明显的,那就是性能消耗比较大
- 其它原型与继承相关知识点
- 使用instanceOf关键字可以确认原型和实例的关系,如果一个实例的原型链中出现过响应的构造函数,则instanceOf返回true
构造函数继承(盗用构造函数)
function Parent1(){
this.name = 'parent1';
}
function Child1(){
Parent1.call(this);
this.type = 'child1'
}
console.log(new Child1);
- 原理:利用call将父类构造函数引入子类函数
- 优点
- 缺点
- 子类不能访问父类原型上定义的方法
- 必须在构造函数中定义方法,因此函数不能重用
组合继承
- 将上面的两种继承方式组合起来,就成了组合继承的方式
function Parent(value) {
this.val = value
}
Parent.prototype.getValue = function() {
console.log(this.val)
}
function Child(value) {
// 继承属性
Parent.call(this, value)
}
// 继承方法
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
- 以上继承的方式核心是
- 利用 Parent.call(this) 继承父类的属性
- 改变子类的原型为 new Parent() 来继承父类的函数。
- 优点
- 构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数
- 缺点
- 就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费
原型式继承
function object(o){
// 临时构造函数
function F() {}
// 将传入的对象赋值给这个构造函数的原型
F.prototype = o
// 返回临时类型的实例
return new F();
}
- 本质上对传入的对象进行了一次浅拷贝
- 所有实例都继承了原型上的属性
- ECMAScript5增加了object.create()方法将原型式继承的概念规范化了,现在使用原型式继承使用object.create()即可
- 和原型链模式类似,子类实例会共享父类实例的属性,一旦改变了子类中该父类的属性,那么继承了该父类的其余子类的继承属性值也会跟着改变
寄生式继承
- 与原型式继承类似,其实就是给原型式继承套了个壳子
- 思路
- 创建一个实现继承的函数,以某种形式增强对象,最后返回对象
function createAnother (origin){
let clone = object(origin) // 通过调用对象来创建一个新对象
clone.sayHi = function(){ // 继承中新增的方法(以某种形式增强对象)
console.log(\"hi\")
}
return clone; // 返回该对象
}
let person = {
name:\"Nicholas\",
friends:[\"Shelby\",\"Court\",\"Van\"]
}
let anotherPerson = createAnother(person)
anotherPerson.sayHi() // hi
寄生组合继承
- 对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了两次构造函数,一次在创建子类原型时调用,另一次则在子类构造函数中又调用了一次,
- 本质上子类原型最终是要包含超类对象的所有实例属性
- 寄生:
- 组合:
- 函数的原型等于另一个实例
- 在函数中用apply或者call引入另一个构造函数,可传参
function Parent () {
this.name = 'parent';
this.play = [1, 2, 3];
}
function Child() {
Parent.call(this); // 组合式思想利用call引入父类构造函数
this.type = 'child';
}
Child.prototype = Object.create(Parent.prototype); // 寄生式思想返回对象
Child.prototype.constructor = Child; // 将父类的构造函数设置为子类,使其只调用一次子类构造函数,避免创建父类中无用的属性,提高继承效率
// 具体使用
let a = new Child()
console.log(a) // Child { name: 'parent', play: [ 1, 2, 3 ], type: 'child' }
let b = new Child()
b.name = 'child'
console.log(a) // Child { name: 'parent', play: [ 1, 2, 3 ], type: 'child' }
console.log(b) // Child { name: 'child', play: [ 1, 2, 3 ], type: 'child' }
- 优点
- 只调用一次构造函数,提高效率
- 原型链保持不变,instanceof操作符和isPrototypeOf()方法仍然有效
- 子类更改属性不会互相影响
- 可以说寄生组合式继承是引用类型继承的最佳模式
ES6的继承写法
class Parent {
constructor(value) {
this.val = value
}
getValue() {
console.log(this.val)
}
}
class Child extends Parent {
constructor(value) {
super(value)
this.val = value
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
- 相当于语法糖,原理与寄生组合继承模式类似,使其更符合面向对象的写法