前言:
js有几种经典的继承方式。比如原型链继承、构造函数继承、组合继承、寄生组合继承、ES6继承。让我们一一分析并实现。同时了解每种方案的优缺点。
其实js的继承本质上是通过原型链机制实现的扩展。不管是哪种继承方式,都是通过操作父类原型链和子类原型链形成关联关系实现的。只是不同实现中需要考虑不同的问题。在实际项目开发中,建议尽可能使用ES6的class extends实现。
一、原型链继承
优点: 父类新增原型方法或属性,子类都能访问到,可共用方法
缺点:
- 创建子类实例时,无法向父类构造函数
传参
。引用类型
(比如对象/数组)的属性会被所有实例共享
( 实例使用的是同一个原型对象。它们的内存空间是共享的)
function Parent() {
this.name = "Parent";
this.arr=[]
}
Parent.prototype.sayHello = function () {
this.arr.push(1)
console.log("Hello from " + this.name);
};
function Child() {
this.name = "Child";
}
// 设置Child的原型为Parent的实例
Child.prototype = new Parent();
// 创建Child的实例
let child = new Child();
let child2 = new Child();
child.sayHello(); // 输出: Hello from Child
child.sayHello(); // 输出: Hello from Child
console.log(child.sayHello === child2.sayHello); // true
console.log(child.arr,child2.arr); // [ 1, 1 ] [ 1, 1 ] , child的 arr 修改 导致 child2的arr也被修改了
二、构造函数继承
优点:
修复引用类型被篡改
,解决原型链继承无法传参的问题(通过call传参)。
缺点: 每次创建子类实例时都会创建父类的方法,导致内存浪费
,无法实现函数复用
function Parent(name) {
this.name = name;
this.arr=[]
}
Parent.prototype.sayHello = function () {
this.arr.push(1)
console.log("Hello from " + this.name);
};
function Child(name) {
Parent.call(this, name); // 调用Parent的构造函数
}
Child.prototype = new Parent(); // 继承Parent的原型方法
Child.prototype.constructor = Child; // 修正构造函数指向
var child = new Child('ziyu');
var child1 = new Child('ziyu');
console.log(child.sayHello());
console.log(child.sayHello());
console.log(child.sayHello === child1.sayHello); //false
console.log(child.arr, child1.arr); // 输出: [ 1, 1 ] []
三、组合继承
优点:
可以传参,并且可以复用父类方法,可以避免引用类型属性共享的问题。
缺点:父类构造函数被调用了两次
,一次在设置原型时,一次在构造函数中,可能会导致不必要性能开销
。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child(name, age) {
Parent.call(this, name); // 调用Parent的构造函数 , 第一次调用
this.age = age;
}
// 通过原型链继承Parent的方法
Child.prototype = new Parent() // 第二次调用
Child.prototype.constructor = Child;
Child.prototype.sayBye = function() {
console.log('Bye from ' + this.name);
};
var child = new Child('ziyu_jia', 25);
child.sayHello(); // 输出: Hello from ziyu_jia
child.sayBye(); // 输出: Bye from ziyu_jia
四、寄生组合继承
优点:寄生组合继承其实就是在组合继承的基础上,
解决了父类构造函数调用两次的问题
。
缺点: 无明显缺点
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child(name, age) {
// 创建一个没有实例的Parent对象
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.sayBye = function() {
console.log('Bye from ' + this.name);
};
var child = new Child('ziyu_jia', 25);
child.sayHello(); // 输出: Hello from ziyu_jia
child.sayBye(); // 输出: Bye from ziyu_jia
Object.create()
Object.create()
静态方法以一个现有对象作为原型,创建一个新对象。
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}.`);
},
};
const me = Object.create(person);
me.name='ziyu'
console.log( me.printIntroduction()); // My name is ziyu
五、ES6继承
ES6提供了class语法糖,同时提供了extends用于实现类的继承。这也是项目开发中推荐使用的方式。
优点: 使用class继承很简单,也很直观
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello from ' + this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的构造函数
this.age = age;
}
sayBye() {
console.log('Bye from ' + this.name);
}
}
var child = new Child('ziyu', 25);
child.sayHello(); // 输出: Hello from ziyu
child.sayBye(); // 输出: Bye from ziyu