JavaScript 继承的实现攻略
JavaScript 是一种基于原型的语言,这使得继承变得更为简单。通过原型继承,对象可以继承另一个对象的属性和方法。继承的实现方式有多种,包括原型链继承、构造函数继承、组合继承、寄生继承、原型式继承和类继承等。下面将依次介绍这些继承的实现方式。
1. 原型链继承
原型链继承利用 JavaScript 的原型链机制,把子类的原型链指向父类的实例。这种方式实现简单,并且可以实现继承父类的所有属性和方法。但是有一个致命缺陷,就是如果父类的引用类型属性被修改,子类的引用类型属性也会随之改变。示例如下:
function Parent() {
this.name = 'Parent';
this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function Child() {
this.age = 18;
}
Child.prototype = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.colors.push('yellow');
console.log(child1.colors); // ["red", "green", "blue", "yellow"]
console.log(child2.colors); // ["red", "green", "blue", "yellow"]
上面的代码中,父类 Parent 有一个属性 colors,它是一个数组。当我们定义子类 Child 并继承 Parent 时,子类的原型指向父类的实例。如果我们向 child1 的 colors 属性中添加一个新的元素,child2 的 colors 也发生了变化。这是因为 child1 和 child2 的 colors 引用的是同一个数组,从而导致共享一个实例。这样的结果有时会是我们所期望的,但有时却不是。
2. 构造函数继承
构造函数继承通过在子类型的构造函数中调用父类型的构造函数来实现继承。这种方式可以解决在原型链继承中父类引用类型属性共享的问题。但是这种方式无法继承父类原型上的属性和方法。示例如下:
function Parent() {
this.name = 'Parent';
this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function Child() {
Parent.call(this);
this.age = 18;
}
var child = new Child();
console.log(child.name); // "Parent"
console.log(child.colors); // ["red", "green", "blue"]
console.log(child.age); // 18
console.log(child.sayName); // undefined
在上面的示例中,子类 Child 的构造函数中调用了父类 Parent 的构造函数,并且使用 call 方法改变了 this 的指向。这样就只继承了父类的属性和方法,而没有继承原型上的属性和方法。
3. 组合继承
组合继承是指将原型链继承和构造函数继承组合起来使用。这种方式可以继承父类原型上的属性和方法,并且可以避免引用类型属性共享的问题。但是这种方式会调用两次父类的构造函数,一次是在创建子类原型的时候调用,另一次是在子类的构造函数中调用。示例如下:
function Parent() {
this.name = 'Parent';
this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function Child() {
Parent.call(this);
this.age = 18;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child();
var child2 = new Child();
child1.colors.push('yellow');
console.log(child1.name); // "Parent"
console.log(child1.colors); // ["red", "green", "blue", "yellow"]
console.log(child1.age); // 18
console.log(child1.sayName()); // "Parent"
console.log(child2.name); // "Parent"
console.log(child2.colors); // ["red", "green", "blue"]
console.log(child2.age); // 18
console.log(child2.sayName()); // "Parent"
在上面的代码中,首先通过在子类的构造函数中调用父类的构造函数来继承父类的属性。然后将子类的原型设置为一个父类的实例,从而继承父类原型上的属性和方法。需要注意的是,由于将子类的原型设置为父类的实例,所以在创建子类的实例时会调用两次父类的构造函数。
4. 寄生继承
寄生继承是指在原型式继承的基础上,增强对象,然后返回对象。这种方式通过创建一个用于封装继承过程的函数,其中该函数以某种方式来增强对象,最后再返回对象来实现继承。示例如下:
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function create(parent) {
var child = object(parent.prototype);
child.constructor = Child;
return child;
}
function Child() {
Parent.call(this);
this.age = 18;
}
Child.prototype = create(Parent);
var child = new Child();
console.log(child.name); // "Parent"
console.log(child.age); // 18
console.log(child.sayName()); // "Parent"
在上面的代码中,我们使用了一个 helper 函数 object() 来创建一个新的对象,然后将其原型设置为一个父类的实例。最后,我们创建了一个 create() 函数来实现父类的实例化,并将其赋给子类的原型。这样我们就可以在子类的构造函数中调用父类的构造函数来继承属性,同时通过增强对象继承父类原型上的属性和方法。
5. 原型式继承
原型式继承是在不使用构造函数的情况下,实现对象之间的继承。这种方式背后的思想是借助已有的对象来创建新的对象,并且重写其属性和方法。虽然这种方式看起来非常简单,但是由于使用的是引用类型,所以容易出现引用类型属性共享的问题。示例如下:
var parent = {
name: 'parent',
colors: ['red', 'green', 'blue'],
sayName: function () {
console.log(this.name);
}
};
var child1 = Object.create(parent);
var child2 = Object.create(parent);
child1.name = 'child1';
child1.colors.push('yellow');
console.log(child1.name); // "child1"
console.log(child1.colors); // ["red", "green", "blue", "yellow"]
console.log(child1.sayName()); // "child1"
console.log(child2.name); // "parent"
console.log(child2.colors); // ["red", "green", "blue", "yellow"]
console.log(child2.sayName()); // "parent"
在上面的代码中,我们通过 Object.create() 方法来创建了一个新的对象 child,同时将其原型指向了一个父类对象 parent。接着,我们修改了 child1 的 name 属性,并向其 colors 属性中添加了一个新的元素,但是修改操作也影响了 child1 的父对象 parent。因为 parent 对象是一个引用类型,被 child1 和 child2 所共享。
6. 类继承
ES6 中引入了 class 关键字来实现面向对象编程。类继承是指通过继承 class 中的属性和方法来实现继承。类继承相比于上述的继承方式更加直观,易于理解。示例如下:
class Parent {
constructor() {
this.name = 'parent';
this.colors = ['red', 'green', 'blue'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor() {
super();
this.age = 18;
}
}
var child = new Child();
console.log(child.name); // "parent"
console.log(child.age); // 18
console.log(child.sayName()); // "parent"
console.log(child instanceof Parent); // true
console.log(child instanceof Child); // true
在上面的代码中,我们定义了一个父类 Parent 和一个子类 Child,并且通过 extends 关键字让子类继承父类的属性和方法。在子类的构造函数中使用 super() 关键字来调用父类的构造函数。这样就实现了子类的继承。
通过上述 6 种继承方式的比较,我们可以看出,不同的应用场景有不同的继承方式选择。对于继承应该如何实现,要根据实际情况做出权衡和选择。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JavaScript 继承的实现 - Python技术站