JavaScript继承演进为五阶段:原型链继承暴露共享与传参缺陷;构造函数继承解决属性隔离但丢失方法;组合继承因两次调用父构造函数而冗余;寄生组合继承通过Object.create优化原型链,成为ES5终极方案;ES6 class是其语法糖,本质未变。

原型链继承:最原始但暴露本质的方式
这是 JavaScript 继承的起点,核心是让子类的 prototype 指向父类的一个实例。例如:Child.prototype = new Parent(); 这样一来,Child 实例就能通过 __proto__ 链访问 Parent.prototype 上的方法。
但它有两个硬伤:一是父类中定义的引用类型属性(比如 this.colors = ['red'])会被所有子类实例共享;二是子类创建时无法给父类构造函数传参,因为 new Parent() 是在原型赋值阶段静态执行的,不是每次实例化都调用。
构造函数继承:解决属性隔离,却丢了方法复用
为绕开共享问题,开发者转而使用 call 或 apply 在子类构造函数里调用父类构造函数,例如:Parent.call(this, name)。这样每个子类实例都拥有自己的一份属性副本,引用类型不再互相干扰。
但副作用也很明显:父类原型上的方法(如 Parent.prototype.say())完全没被继承,子类实例无法调用。也就是说,只解决了“实例属性”的继承,没解决“原型方法”的继承。
立即学习“Java免费学习笔记(深入)”;
组合继承:两次调用父构造函数的妥协方案
把前两种方式拼起来就成了组合继承:子类构造函数内 call 父类继承属性,同时又把 Child.prototype = new Parent() 来继承方法。它看起来很完整,但存在一个隐蔽缺陷——父类构造函数被执行了两次。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 第一次在 new Parent() 设置原型时,产生一个无用的父类实例(其属性对子类实例无效)
- 第二次在子类构造函数里 Parent.call(this),才是真正初始化子类实例的属性
这不仅浪费内存,还可能引发副作用(比如父类构造函数中有日志、请求或状态变更)。
寄生组合继承:ES5 时代的终极解法
它修正了组合继承的冗余问题,关键在于用 Object.create(Parent.prototype) 替代 new Parent() 来设置子类原型。这样既建立了正确的原型链(Child.prototype.__proto__ === Parent.prototype),又避免了执行父类构造函数。
再配合 Parent.call(this) 继承实例属性,并手动修复 constructor 指向,就构成了真正干净、高效、可扩展的继承结构。这也是 Babel 编译 class extends 的底层实现逻辑。
ES6 class:语法糖之下仍是原型链
class 和 extends 并没有改变 JavaScript 的继承本质,只是封装了寄生组合继承的繁琐步骤。当你写 super(),引擎实际做的就是 Parent.call(this);当你写 extends Parent,引擎自动设置了 Child.prototype.__proto__ = Parent.prototype。
值得注意的是:class 声明不会被提升,且必须先调用 super() 才能使用 this,这是为了确保原型链和实例属性初始化顺序可控,防止访问未定义的父类上下文。

















