本文详细解析 JavaScript 在 ES6 之前实现继承的四种主流方式。涵盖构造继承、原型链继承、组合继承以及寄生组合继承的原理与优缺点。重点分析各方案在属性访问、多继承支持、参数传递及原型污染方面的差异,并指出寄生组合继承为最完美方案,也是 Babel 转换 ES6 class 的核心机制。
在非 ES6 语法环境下,实现类继承的主流方案主要包含以下几类:构造继承、原型链继承、结合前两者的组合继承,以及在组合继承基础上优化衍生的其他继承方式。
构造继承 (借助 call 实现)
实现
function Super(age){
this.age = age;
this.say = function(){
console.log(this.age)
}
}
function Child(name,age){
Super.call(this,age)
this.name = name;
}
var child = new Child("min",23)
console.log(child instanceof Super); // false
console.log(child instanceof Child); // true
优点
(1) 支持多继承,可通过多次调用 call 继承多个父类对象 (2) 允许在构造函数中向父级传递参数
缺点
(1) 仅能继承父类实例的属性和方法,无法访问原型上的属性和方法 (2) 生成的实例并非父类的实例,仅是子类的实例
原型链继承 (借助原型链实现)
实现
function Super(){
this.getName = function(){
console.log(this.name)
}
}
function Child(name){
this.name = name;
}
Child.prototype = new Super(); // 这里可以传构造参数
Child.prototype.constructor = Child;
var child = new Child("min");
console.log(child instanceof Super); // true
console.log(child instanceof Child); // true
console.log(child.constructor); // Child
优点 (1) 子类可以访问父类原型上的属性与方法 (2) 实例既是子类的实例,也是父类的实例
缺点 (1) 不支持多继承 (2) 创建子类实例时,无法向父类构造函数传递参数
组合继承 (构造继承+原型链继承)
实现
function Super(age){
this.age = age;
this.getAge = function(){
console.log(this.age);
}
}
function Child(name,age){
Super.call(this,age)
this.name = name;
}
Child.prototype = new Super(1);
Child.prototype.constructor = Child;
var child = new Child("min",23);
console.log(child instanceof Super); // true
console.log(child instanceof Child); // true
console.log(child.constructor); // Child
优点 (1) 结合了构造继承与原型链继承的优势
缺点 (1) 执行 Child.prototype = new Super(); 会多调用一次构造函数,导致原型对象中包含不必要的属性(例如示例中的 age 属性)
寄生组合继承
实现
function Super(age){
this.age = age;
this.getAge = function(){
console.log(this.age)
}
}
function Child(name,age){
Super.call(this,age)
this.name = name;
}
(function(){
function Copy(){}
Copy.prototype = Super.prototype;
Child.prototype = new Copy();
})()
Child.prototype.constructor = Child;
var child = new Child("min",23);
备注 问:为何不直接使用 Child.prototype = Super.prototype; 答:关键代码 Child.prototype.constructor = Child; 若直接赋值,修改 Super.prototype 时也会发生变动(因引用类型指向同一地址)
优点 (1) 这被视为实现继承的最完美方案,ES6 的 extends 关键字在经 Babel 转换后的代码亦通过此方式实现继承。
额外:借助 (Object.create)
实现
function Super(age){
this.age = age;
this.getAge = function(){
console.log(this.age)
}
}
function Child(name,age){
Super.call(this,age)
this.name = name;
}
Child.prototype = Object.create(Super.prototype,{
constructor:{ // 构造函数修复
value: Child
}
})
var child = new Child("min",23);
console.log(child instanceof Super); // true
console.log(child instanceof Child); // true
console.log(child.constructor); // Child
以上就是 JavaScript 实现继承的几种方式的详细内容,更多关于 JavaScript 实现继承的资料请关注本站其它相关文章!