es5 中实现继承的核心做法是结合构造函数与原型链,通过修改子类的 prototype 对象并调用父类构造函数来达成。具体而言,子类需要将自己的 prototype 指向父类的一个实例,从而共享父类原型上的方法;同时,在子类的构造函数内部使用 call 或 apply 调用父类构造函数,以初始化子类实例自身的属性。这种模式被称为寄生组合式继承,它既保证了每个实例拥有独立的属性副本,又实现了原型链的复用。

如何正确设置原型链以共享方法
要让子类实例能够访问父类原型上的方法,必须将子类的 prototype 属性替换为父类的一个新实例。这样做的直接结果是,子类实例在查找属性时,会沿着原型链向上追溯到父类实例,进而找到父类原型上定义的方法。需要注意的是,如果直接赋值 Parent.prototype,会导致所有子类实例共享同一个原型对象,这在某些情况下可能引发数据污染,因此通常采用创建新实例的方式。此外,修正后的 prototype.constructor 指向也需要手动恢复,以保持类型标识的一致性。

function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('My name is ' + this.name);
};
function Child(name, age) {
// 调用父类构造函数,初始化实例属性
Parent.call(this, name);
this.age = age;
}
// 核心步骤:将子类的原型指向父类的实例
Child.prototype = new Parent();
// 修复 constructor 指向
Child.prototype.constructor = Child;
// 子类特有的方法
Child.prototype.sayAge = function() {
console.log('I am ' + this.age + ' years old');
};
var child = new Child('Alice', 10);
child.sayName(); // 输出: My name is Alice
child.sayAge(); // 输出: I am 10 years old上述代码展示了标准的寄生组合式继承结构。首先,Parent.call(this, name) 确保了 child 实例拥有自己的 name 属性,而不是从原型链获取。其次,Child.prototype = new Parent() 建立了原型链连接,使得 child 可以访问 Parent.prototype 上的 sayName 方法。最后,手动设置 constructor 是为了保证 child.constructor === Child 为真,避免类型判断出错。运行该代码后,控制台将依次打印出名字和年龄,验证了属性和方法的正确继承。
立即学习“Java免费学习笔记(深入)”;
如何处理父类构造函数中的参数传递
在 ES5 继承中,父类构造函数通常接收特定的参数用于初始化实例状态。为了让子类能够正确传递这些参数,必须在子类构造函数内部显式调用父类构造函数,并使用 call 或 apply 改变其执行上下文。这一步骤发生在子类自身属性初始化之前或之后,取决于具体的业务逻辑需求。如果不进行这一步调用,子类实例将无法获得父类定义的实例属性,导致运行时错误或缺失关键数据。通过这种方式,子类可以在初始化的同时,灵活地控制传递给父类的参数值。

function Animal(type) {
this.type = type;
this.legs = 4;
}
Animal.prototype.move = function() {
console.log(this.type + ' moves with ' + this.legs + ' legs');
};
function Dog(type, breed) {
// 传递参数给父类构造函数
Animal.call(this, type);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
var dog = new Dog('Canine', 'Labrador');
dog.move(); // 输出: Canine moves with 4 legs
console.log(dog.breed); // 输出: Labrador在这段代码中,Dog 构造函数接收 type 和 breed 两个参数。其中 type 被传递给 Animal 构造函数,用于设置 this.type。而 breed 则是 Dog 独有的属性。使用 Object.create(Animal.prototype) 是一种更现代且安全的原型赋值方式,它避免了直接调用父类构造函数可能带来的副作用(如执行不必要的初始化逻辑),但在 ES5 环境中,new Parent() 也是常见做法。无论哪种方式,关键在于确保原型链的正确建立以及父类属性的正确初始化。最终创建的 dog 实例既拥有 move 方法,也拥有独立的 breed 属性。
当父类属性包含引用类型时会发生什么
如果父类构造函数中的属性是数组、对象等引用类型,直接使用寄生组合式继承可能会导致所有子类实例共享同一个引用对象。这是因为原型链上的属性是被所有实例共享的。例如,若父类有一个 friends 数组,子类实例修改自己的 friends 可能会意外影响其他实例。为了避免这个问题,除了依赖原型链共享方法外,应确保引用类型的属性在每次创建实例时都被重新初始化。这可以通过在子类构造函数中再次调用父类构造函数来实现,因为 call 会在新的实例上下文中执行父类代码,从而为每个实例创建独立的引用类型属性副本。


















