JavaScript作为一门基于原型的面向对象的动态解释性语言,继承、原型、原型链在其中占据了重要的地位。
前言
文中的重写constructor都使用简单的重新赋值,最好是使用Object.defineProperty()
,定义constructor,将其定义为不可配置、不可枚举、不可修改的是比较好的实践
原型链继承
function Person(name){
this.name=name;
}
function Child(name){
}
Child.prototype=new Person()
Child.prototype.constructor=Child;
缺点:
- 没有调用父类构造函数
- 假设父类存在引用类型时,所有实例共享这一引用
function Person(name){
this.arr=[1,2,3]
}
function Child(name){
}
Child.prototype=new Person()
Child.prototype.constructor=Child;
let child1=new Child()
let child2=new Child();
child1.arr.push(5);
console.log(child2.arr);//[1,2,3,5]
构造函数继承
利用call方法调用构造父类构造函数
function Person(name){
this.arr=[1,2,3]
}
function Child(name){
Person.call(this,name)
}
优点:
- 每次实例化,都会新调用父类构造函数,不会存在共享父类引用的问题
缺点:
- 所有属性方法都必须在构造函数中设置
- 不能访问原型属性,不算严格意义上的继承
组合继承
结合原型链继承和构造函数继承的优点,可以得到组合继承
function Person(name){
this.arr=[1,2,3]
}
function Child(name){
Person.call(this,name)
}
Child.prototype=new Person()
Child.prototype.constructor=Child;
let child1=new Child()
优点:
- 比较好的实现了继承,可以达到基本要求
缺点:
- 在调用父类构造函数和设置子类原型时调用了两次父类构造函数,存在内存浪费的情况
原型式继承
function createObject(o){
var Super=function(){};
Super.prototype=o;
return new Super();
}
上述函数等效于Object.create()
返回对象的原型为传入参数
优点:
- 不用提前预知子类构造函数
缺点:
- 子类共享父类引用问题
寄生式继承
在原型继承的基础上添加方法,增强原型继承
function createObject(o){
var Super=Object.create(o);
Super.speak=function(){};
return new Super();
}
寄生组合继承
为了改善组合继承创建两个实例的缺陷,可以利用寄生继承不创建父类实例的优点进行结合
function Person(name){
this.arr=[1,2,3]
}
function Child(name){
Person.call(this,name)
}
Child.prototype=Object.create(Person.prototype);
let child1=new Child()
优点:
- 比较完美
缺点:
- 稍微复杂了点,但是不是问题