一般来说,像 total 和 distance 这样的属性应该在 subSection 对象的每个实例中被初始化为完全相同的值,而应该在 prototype 而不是构造函数中定义。
对象的prototype 基本上是一个“模板”,该对象的每个实例都基于该模板。每个实例都有在prototype 上定义的每个属性和方法。
因此三个“类”(JavaScript 没有像 Java 等经典继承语言中定义的类,但有时习惯这些语言的人更容易使用该术语)应声明如下:
function subSection(pattern) {
this.pattern = pattern;
}
function enhancer(pattern) {
this.pattern = pattern;
}
function silencer(pattern) {
this.pattern = pattern;
}
将total 和distance 移出subSection 构造函数并移入subSection 原型:
subSection.prototype.total = 0;
subSection.prototype.distance = 0;
下一步是在subSection、enhancer 和silencer 之间设置原型继承,使用无操作函数作为它们之间的一种代理:
function fn() {}
fn.prototype = subSection.prototype;
enhancer.prototype = new fn();
silencer.prototype = new fn();
最后,将enhancer 和silencer 原型的constructor 属性设置为正确的对象,以便实例的constructor 属性引用正确的构造函数。如果我们不采取这一步,那么enhancer 或silencer 的任何实例都将错误地引用subSection 构造函数。
enhancer.prototype.constructor = enhancer;
silencer.prototype.constructor = silencer;
现在我们可以实例化enhancer 和silencer 对象,生成的实例将具有total 和distance 属性以及引用相应对象的constructor 属性。
var a, b;
a = new enhancer('aaaa');
b = new silencer('bbbb');
console.log(a.total + ', ' + a.distance); // 0, 0
console.log(b.total + ', ' + b.distance); // 0, 0
console.log(a.constructor); // (string representation of enhancer constructor)
我们在继承过程中使用fn 函数作为代理的原因是因为一个对象应该能够从另一个不一定具有相同的构造函数参数集的对象继承。
假设我们有以下两个构造函数:
function Person(name, age, hometown) {
this.name = name;
this.age = age;
this.hometown = hometown;
}
function Employee(name, company, title) {
this.name = name;
this.company = company;
this.title = title;
}
Employee 应该能够从Person 继承,即使它们不共享同一组构造函数参数。如果我们简单地将Employee.prototype 设置为等于Person 的新实例,则Employee 原型将具有三个未定义的属性name、age 和hometown。
Employee.prototype = new Person; // name, age and hometown are undefined
var e = new Employee('John Smith', 'New York Times', 'editor'); // e's prototype has undefined properties name, age and hometown
通过使用无操作函数fn(我们几乎可以将它命名为任何我们想要的名称),我们最终为每个实例都提供了一个干净的原型,因为fn 作为构造函数不需要任何参数。
这不是必需的,但它是设置原型继承的一种更简洁的方法:没有太多理由将带有未定义属性的原型弄乱。