原型链继承是javascript最原始的继承方式,核心是将子类prototype设为父类实例,虽可复用父类原型方法,但所有子类实例共享父类构造函数中的引用类型属性,且无法向父类传参、constructor指向错误;借用构造函数通过call/apply解决属性独立与传参问题,却无法继承原型方法;组合继承融合二者优势但导致父类构造函数被调用两次;寄生组合式继承(object.create替代new)避免重复调用,成为当前最优实践;es6 class本质仍是原型链,super()等价于parent.call(this);继承适用于“is-a”关系,功能复用应优先选择组合。

原型链继承:共享引用是最大雷区
把子类的 prototype 直接设为父类实例,是最原始也最容易踩坑的方式。它能让子类复用父类原型上的方法,但所有子类实例会共用父类构造函数中创建的引用类型属性(比如数组、对象、日期等)。
- 一个实例修改
this.colors = ['red'],push 新颜色后,其他实例看到的也是改过的数组 - 子类无法在实例化时向父类构造函数传参,
new Child('Lucky')中的 'Lucky' 根本进不去Parent的初始化逻辑 - 子类原型的
constructor会指向父类,必须手动修正:Child.prototype.constructor = Child
借用构造函数:解决共享但丢掉方法复用
在子类构造函数里用 Parent.call(this, name),能确保每个子类实例拥有独立的属性副本,参数也能顺利传递。
- 引用类型不再互相污染,
child1.hobbies.push('gaming')不影响child2 - 但父类定义在 prototype 上的方法(如
sayName)无法被继承,子类实例调用会报undefined is not a function - 每次新建实例都执行一遍父类构造函数,存在冗余开销
组合继承:折中方案,两次调用父构造函数
把前两种方式合起来——既用 Parent.call(this) 初始化实例属性,又用 Child.prototype = new Parent() 继承原型方法。
- 解决了属性隔离和方法复用两个核心问题,长期被视为“黄金搭档”
- 但父类构造函数会在设置原型时执行一次,在子类构造中又执行一次,造成不必要的重复初始化(比如重复创建相同数组、发起无意义请求)
- 需手动修复
constructor指向,否则instance.constructor会错乱
寄生组合式继承:当前最优实践
用 Object.create(Parent.prototype) 替代 new Parent() 来设置子类原型,避免父构造函数重复执行。
- 只调用一次父类构造函数,且完整保留原型链关系与方法复用能力
- 主流库(如 Babel 转译
class)底层正是采用此模式 - 封装成工具函数更安全:
function inherit(Child, Parent) { Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; }
ES6 Class:语法糖之下仍是原型链
class 和 extends 看似简化了继承,但本质没变。它强制要求子类构造函数中必须先调用 super(),否则访问 this 会报错。
-
super()内部仍等价于Parent.call(this, ...args),只是语法更紧凑 - 跨 iframe 或不同 JS 上下文时,
instanceof可能失效(因构造函数不是同一个内存地址) - Babel 编译后的代码可读性差,调试时建议直接看源码或启用 source map
什么时候不该用继承?优先考虑组合
继承表达的是“is-a”关系(狗是一种动物),不是“有某个功能”。若只是为了复用逻辑,组合更灵活、耦合更低。
- 工具类之间不要继承,抽成独立函数或 ES Module 导出即可
- React 组件中,用高阶组件(HOC)或自定义 Hook 替代 class 继承,更符合现代实践
- 状态管理器扩展(如 Redux store 增强)应通过中间件或增强器实现,而非继承 Store 类











