深入探究 JavaScript 中的继承方式
引言:
在 JavaScript 的世界里,继承是构建复杂应用程序的重要基石。它允许我们在已有代码的基础上进行扩展和复用,从而提高开发效率。今天,就让我们一同深入探讨 JavaScript 中多种继承方式的奥秘。
在 JavaScript 中,继承的方式多种多样,每种方式都有其独特的优势和适用场景。我们先来看一种常见的方式:原型传递。
function Game (name, type) {
this.name = name;
this.type = type;
}
Game.prototype.play = function() {
console.log(`${this.name} ${this.type} 游戏正在玩`);
}
// 现在有一个对象 LOL
function LOL() {}
// 他需要继承 Game 的属性和方法
// 原型传递
LOL.prototype = new Game('LOL', 'MOBA');
LOL.prototype.constructor = LOL;
const lol = new LOL();
lol.play(); // LOL MOBA 游戏正在玩
这种方式简单直接,通过将父类的实例赋给子类的原型,实现了属性和方法的继承。但它也存在一些问题。
重写原型法存在的问题:
父类属性一旦赋值给子类的原型属性,此时就属于子类的共享属性,会导致继承者之间实例的篡改。
function Game (name, type) {
this.name = name;
this.type = type;
this.skin = ['史诗'];
}
Game.prototype.play = function() {
console.log(`${this.name} ${this.type} 游戏正在玩`);
}
// 现在有一个对象 LOL
function LOL() {}
// 他需要继承 Game 的属性和方法
// 原型传递
LOL.prototype = new Game('LOL', 'MOBA');
LOL.prototype.constructor = LOL;
const lol = new LOL();
const lol2 = new LOL();
lol.play(); // LOL MOBA 游戏正在玩
lol2.play(); // LOL MOBA 游戏正在玩
lol.skin.push('至臻');
console.log(lol.skin); // ['史诗', '至臻']
console.log(lol2.skin); // ['史诗', '至臻']
而且在实例化时无法向父类传递参数,即无法创造一个不同的 LOL。
为了解决这些问题,我们引出了构造函数继承(经典继承)。
构造函数继承(经典继承):
通过在子类的构造函数中调用父类的构造函数,解决了实例的独立性。
function Game (name, type) {
this.name = name;
this.type = type;
this.skin = ['史诗'];
}
Game.prototype.play = function() {
console.log(`${this.name} ${this.type} 游戏正在玩`);
}
// 现在有一个对象 LOL
function LOL(name, type) {
Game.call(this, name, type);
}
const lol = new LOL('LOL', 'MOBA');
const wzry = new LOL('wzry', 'MOBA');
lol.skin.push('至臻');
console.log(lol.skin); // ['史诗', '至臻']
console.log(wzry.skin); // ['史诗']
// 构造函数继承解决了实例的独立性
lol.play(); // lol.play is not a function
wzry.play(); // wzry.play is not a function
然而,这种方式又带来了新的问题,即继承了父类的属性和方法,但没有原型传递,无法使用原型共享。
接着,我们来看原型链继承(组合继承)。
原型链继承(组合继承):
结合了原型传递和构造函数继承的优点。
function Game (name, type) {
this.name = name;
this.type = type;
this.skin = ['史诗'];
}
Game.prototype.play = function() {
console.log(`${this.name} ${this.type} 游戏正在玩`);
}
// 现在有一个对象 LOL
function LOL(name, type) {
// 第一次执行
Game.call(this, name, type);
}
// 第二次执行
LOL.prototype = new Game();
const lol = new LOL('LOL', 'MOBA');
const wzry = new LOL('wzry', 'MOBA');
lol.skin.push('至臻');
console.log(lol.skin); // ['史诗', '至臻']
console.log(wzry.skin); // ['史诗']
// 构造函数继承解决了实例的独立性
lol.play(); // LOL MOBA 游戏正在玩
wzry.play(); // wzry MOBA 游戏正在玩
但它又存在一个问题,父类的构造函数会被执行两边。
最后,我们介绍寄生组合继承来解决这个问题。
寄生组合继承:
通过使用 Object.create()
方法,避免了父类构造函数的重复执行。
function Game (name, type) {
this.name = name;
this.type = type;
this.skin = ['史诗'];
}
Game.prototype.play = function() {
console.log(`${this.name} ${this.type} 游戏正在玩`);
}
// 现在有一个对象 LOL
function LOL(name, type) {
Game.call(this, name, type);
}
LOL.prototype = Object.create(Game.prototype);
const lol = new LOL('LOL', 'MOBA');
const wzry = new LOL('wzry', 'MOBA');
lol.skin.push('至臻');
console.log(lol.skin); // ['史诗', '至臻']
console.log(wzry.skin); // ['史诗']
// 构造函数继承解决了实例的独立性
lol.play(); // LOL MOBA 游戏正在玩
wzry.play(); // wzry MOBA 游戏正在玩
结尾:
通过对 JavaScript 中各种继承方式的深入探讨和代码示例,我们可以看到每种方式都有其独特的优势和适用场景。在实际开发中,我们需要根据具体需求选择合适的继承方式,以构建高效、可维护的 JavaScript 应用程序。希望本文能为你在 JavaScript 继承的探索之路上提供有益的参考。