同意你的判断,只补一个手写时几乎必漏的点:静态成员不会跟着原型链走,得自己接上。
class Child extends Parent 实际改了两条链:Child.prototype.__proto__ = Parent.prototype(实例方法继承),同时 Object.setPrototypeOf(Child, Parent)(构造函数本身的 __proto__ 指向父类)。所以 class 里 Parent.staticFn() 子类能直接调用。而 ES5 手写寄生组合式只做了第一条,Child.staticFn 是 undefined——写工具库、写 Class 上挂工厂方法的时候非常容易踩。补上就两行:
Object.setPrototypeOf(Child, Parent); // 静态成员继承
另外你说的第 1 点成立,但可以再省一步:原生 function Foo(){} 的 prototype.constructor 本来就是不可枚举的,直接赋值等于人为降级了描述符。影响面主要限于 for...in,用 Object.keys / getOwnPropertyNames 就不受影响,所以很多团队干脆不做处理——要么统一用 defineProperty,要么统一不做,别一半一半。
还有个和 class 的实质差异值得记一下:class 里的方法默认 不可枚举,ES5 写在 prototype.xxx 上是可枚举的,Object.keys(Child.prototype) 两边结果不同,做序列化或遍历式插件注册时会被这个坑到。
Object.create(null) 那个提醒很到位,再补一刀:这种对象在模板字符串或 + 拼接时直接抛 Cannot convert object to primitive value,因为连 toString 都没有,比“方法调不了”更猝不及防。
结论不变:新代码 class extends + super;真要 ES5 风格,就把 Parent.call(this) + Object.create + setPrototypeOf 三条封成一个 extend 工具函数,别散落在各处手抄。