借用构造函数的核心目的是让子类实例独立拥有父类构造函数定义的实例属性,避免原型共享副作用;必须用call/apply以子类实例为this立即执行父类构造函数,确保属性精准复制。

借用构造函数(通过 call 或 apply)的核心目的,是让子类实例在初始化时,**独立拥有父类构造函数中定义的实例属性(尤其是引用类型或每次需重新生成的值)**,避免原型共享带来的副作用。它不解决方法复用问题,专注“特异性属性”的精准复制。
为什么必须用 call/apply 借用构造函数?
仅靠原型链继承(如 Sub.prototype = new Super())会导致所有子类实例共享父类构造函数中创建的引用类型属性(如数组、对象),一个实例修改会影响其他实例。而 call 或 apply 能在子类构造函数执行时,**以当前子类实例为 this,立即调用父类构造函数**,确保每个实例都拥有自己独立的一份属性副本。
call 与 apply 的选择逻辑
两者功能一致,区别仅在于参数传递方式:
-
用
call:当父类构造函数参数个数固定且已知 —— 直接逐个传入,清晰高效。
例:Super.call(this, name, age); -
用
apply:当参数来自数组或类数组(如arguments),或参数数量不确定 —— 把参数“展开”传入。
例:Super.apply(this, arguments);或Super.apply(this, [name, age, tags]);
精准复刻的关键操作细节
真正实现“精准”,需注意三点:
-
必须在子类构造函数首行调用 —— 确保父类初始化逻辑在子类自定义逻辑前执行,避免
this属性被覆盖或遗漏。 -
显式传入
this作为第一个参数 —— 这是绑定执行上下文的核心,否则父类构造函数内部的this指向错误,属性不会挂载到子类实例上。 -
只处理“实例属性”,不替代原型方法继承 —— 构造函数中用
this.xxx = ...定义的才是特异性属性;方法仍应定义在父类原型上,并通过原型链继承(如Sub.prototype = Object.create(Super.prototype)),否则方法会重复创建、浪费内存。
典型安全写法示例
结合借用构造函数 + 原型链继承,兼顾属性独立性与方法复用:
function Super(name, hobbies) {
this.name = name;
this.hobbies = hobbies || []; // 引用类型,必须每实例独有
this.id = Date.now() + Math.random(); // 特异性值,每次新建不同
}
Super.prototype.sayHi = function() {
return `Hi, I'm ${this.name}`;
};
function Sub(name, hobbies, job) {
// ✅ 精准复刻:借用构造函数,保证 name/hobbies/id 独立
Super.call(this, name, hobbies);
// ✅ 子类特有属性
this.job = job;
}
// ✅ 方法复用:设置正确原型链
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
const a = new Sub('Alice', ['reading'], 'Engineer');
const b = new Sub('Bob', ['gaming'], 'Designer');
console.log(a.hobbies === b.hobbies); // false —— 精准独立
console.log(a.sayHi === b.sayHi); // true —— 方法共享











