javascript传统oop继承通过构造函数继承(保证实例属性私有可传参)与原型继承(复用父类方法)组合实现,即组合继承:子类构造中call父类获取独立属性,原型链上object.create父类原型并修复constructor,兼顾属性隔离与方法复用。
javascript 中实现传统 oop 的继承设计,并不依赖 class 关键字(尽管 es6 后它更直观),而是围绕构造函数、原型对象和 this 绑定展开。核心在于:让子类既能拥有独立的实例属性,又能复用父类的方法,同时避免共享引用类型带来的副作用。
构造函数继承:确保实例属性私有且可传参
通过 call 或 apply 在子类构造函数中调用父类构造函数,把父类的实例属性“复制”到当前子类实例上。
- 每个子类实例都拥有自己的一份属性副本,不会相互影响
- 支持向父类构造函数传递参数,比如 name、age 等初始化值
- 缺点是无法继承父类原型上的方法(如 sayName),只能拿到构造函数内定义的属性和方法
原型继承:复用父类方法,节省内存
将子类的 prototype 指向父类的一个实例(Sub.prototype = new Super()),使子类实例能沿原型链访问父类原型上的方法。
- 方法被所有子类实例共享,避免重复创建函数对象
- 但会带来两个典型问题:父类构造函数被调用两次(一次在 prototype 赋值时,一次在子类构造中);若父类有引用类型属性(如数组),所有子类实例会共用同一份
- 需手动修复
constructor指针,否则new Sub().constructor会指向Super
组合继承:兼顾属性私有性与方法复用性
把构造函数继承和原型继承结合起来——在子类构造函数中用 call 继承实例属性,在子类原型上用 new Super() 继承原型方法。
- 解决了原型链继承中引用属性共享的问题
- 也弥补了纯构造函数继承无法使用父类原型方法的缺陷
- 但存在一个冗余:父类构造函数被调用了两次(一次在
Super.call(this),一次在Sub.prototype = new Super()) - 这是最常用、最易理解的继承模式,适合大多数中等复杂度场景
实际写法示例(精简可运行)
父类:
function Animal(name) {
this.name = name;
this.habits = ['eat', 'sleep']; // 引用类型
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a sound');
};
子类(组合继承):
function Dog(name, breed) {
Animal.call(this, name); // 构造函数继承:获取独立 name 和 habits
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 替代 new Animal(),避免多余调用
Dog.prototype.constructor = Dog; // 修复 constructor
Dog.prototype.bark = function() {
console.log(this.name + ' barks loudly');
};
这样既保证 dog1.habits.push('play') 不会影响 dog2.habits,又能调用 dog1.speak() 和 dog1.bark()。










