本文共 3164 字,大约阅读时间需要 10 分钟。
Class 在 JavaScript 中基于原型继承,这与传统的类继承有所不同。每当你使用 class
关键字在 JavaScript 中,继承另一个 class 时,这实际上是通过原型链来实现的。这种方式与原型继承机制一致,是 JavaScript 的一个重要特性。
要想让一个 class 继承自另一个 class,可以在 class 的声明语法中指定 extends
和父对象。例如,下面的代码展示了 Rabbit 类继承自 Animal 类:
class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stopped.`); }}class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); }}
在上述代码中,Rabbit 类不仅继承了 Animal 类的 constructor 和方法 run()、stop(),还添加了自己的方法 hide()。这里需要注意的是,每当 class 向上查找方法或属性时,它会沿着原型链一直查找到 Object.prototype。
类的 extends
之后不仅可以是另一个 class,还可以是一个表达式。这意味着 class 可以继承的是一个构造函数返回的值,非常适合动态生成父类的情况。例如:
function f(phrase) { return class { sayHi() { alert(phrase); } };}class User extends f("Hello") {}new User().sayHi(); // Hello
在这个例子中,User 类继承自由 f("Hello")
生成的类,类中具有 sayHi() 方法,能够显示 "Hello"。
如果我们想要重写 Rabbit 类的 stop 方法,而不是完全替代,我们可以使用 super.stop()
来调用父类的方法。例如:
class Rabbit extends Animal { stop() { super.stop(); // 调用父类的 stop 方法 this.hide(); // 然后隐藏 }}
这样,stop 方法会先执行父类的 stop 方法,再执行 Rabbit 的隐藏操作。
在构造函数中,如果没有显式调用 super,JavaScript会自动为派生类生成一个 constructor,直接调用父类的 constructor。但这并非理想,因为如果 parent 类有自己的逻辑,直接调用会导致问题。因此,我们需要在 constructor 中显式调用 super(...args)来调用父类的构造函数。
例如:
class Rabbit extends Animal { constructor(name, earLength) { super(name); this.earLength = earLength; }}
这样,Rabbit 的构造函数会首先调用父类的构造函数,这样 name 和 earLength 都会被正确初始化。
在深入理解 super 的实现中,发现当调用 super.method()
时,JavaScript 会从当前对象的原型链中查找 method,并传递 this 给方法。然而,这会涉及到一些底层实现,包括 [[HomeObject]] 属性,它用于确保函数在这种情况下正确调用。
在面对长链原型的情况时,[[HomeObject]] 属性非常有用,可以防止无限递归调用。例如,当一个对象的原型链远远延伸时,[[HomeObject]] 会确保最终调用的是正确的原型链中的方法。
class 语法还支持静态属性和方法的继承。例如:
class Animal { constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } static compare(animalA, animalB) { return animalA.speed - animalB.speed; }}class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); }}let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5)];rabbits.sort(Rabbit.compare);rabbits[0].run(); // Black Rabbit runs with speed 5.
这样,Rabbit 类可以使用继承的 Animal.compare
方法对 rabbit 数组进行排序。
JavaScript 的内置对象如 Array、Date 等可以通过原型链进行继承,尽管它们并不是使用 class 语法定义的。这意味着它们有自己的原型链,而这些原型链是原始的对象原型链的延伸。例如:
console.log(Date.__proto__ === Object.prototype);console.log(Array.__proto__ === Object.prototype);
它们的静态方法和属性的继承遵循标准的原型链机制,而不是类继承。
在大型应用中,尽量使用 class 来定义可扩展的对象,避免复杂的复合继承模式。通过 class
语法可以清晰地表达继承关系,提高代码的可维护性和可读性。
使用 class
时,最好遵循以下原则:
Object.mixin()
来复制属性到对象中。super
时,确保 this 被正确绑定,避免在方法中无法正确获取到对象的原型链。static Symbol.species
方法,返回具体的构造函数类,以确保结果对象的构造函数是正确的。通过遵循这些原则,开发者可以在 JavaScript 中实现更高效和可维护的代码结构。这样,继承机制就不再是问题,而是成为代码结构的一部分。
转载地址:http://nijtz.baihongyu.com/