JavaScript的prototype和class本质是同一套原型机制的两种写法,class是语法糖;prototype手动操作易出错因需显式挂载方法、易覆盖原型链、继承需手动设置且无私有支持;class自动处理constructor、extends、static等,但不提升且混用时易破坏原型链;选择取决于兼容性、控制需求与团队规范。

JavaScript 的 prototype 和 class 本质是同一套原型机制的两种写法,class 是语法糖,不是新模型 —— 所以别指望它带来 Java 那种真正的类继承。
prototype 方式怎么写、为什么容易出错
手动操作 prototype 是在直接暴露和修改函数的原型对象,灵活性高但容错低:
-
function Person(name) { this.name = name; }定义构造函数后,必须显式往Person.prototype上挂方法,否则实例无法访问 - 如果误写成
Person.prototype = { say() { } },会彻底覆盖原有原型(包括constructor指针),导致new Person().__proto__.constructor === Person失败 - 多个构造函数间模拟“继承”需手动设置
Child.prototype = Object.create(Parent.prototype),再补constructor,漏一步就出问题 - 没有内置的私有字段或方法支持,所有属性都可被外部读写
class 语法到底做了什么封装
class 并不改变原型链行为,只是把常见操作打包成更紧凑、更易读的声明式写法:
-
class声明自动设为constructor方法,且该方法不可枚举(Object.keys(Person.prototype)看不到) -
extends关键字背后调用的是Object.setPrototypeOf(Child.prototype, Parent.prototype),还确保子类constructor正确指向自身 -
static方法自动挂到构造函数本身(Person.sayHi),而非原型上 - 但注意:
class声明不会提升(hoisting),必须先定义再使用,否则报ReferenceError
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
static isAnimal(obj) {
return obj instanceof Animal;
}
}
prototype 和 class 混用时的坑
两者可以共存,但混合修改容易引发预期外行为:
立即学习“Java免费学习笔记(深入)”;
- 用
class Dog extends Animal定义后,再执行Dog.prototype.speak = function() { ... }会覆盖 class 中定义的方法,但不会影响父类原型 - 若在
class外部给Dog.prototype赋值整个对象(如Dog.prototype = { ... }),会切断extends建立的原型链,instanceof Animal返回false -
class内部定义的 getter/setter 或方法,其[[Enumerable]]为false;而手动在prototype上添加的普通函数默认为true,这会影响for...in或Object.keys()的结果
该选哪个:看团队、看环境、看需求
现代项目基本统一用 class,但理解 prototype 仍是调试和阅读旧代码的刚需:
- 需要兼容 IE 或极老运行时?只能用
prototype+function(class在 IE 完全不支持) - 写库或框架底层(比如实现自己的
EventEmitter)?直接操作prototype更可控,也更轻量 - 涉及动态方法注入、运行时重写原型逻辑?
class的静态结构反而碍事 - 多人协作或长期维护?
class的语义更清晰,尤其配合 TypeScript 后类型推导更稳
真正关键的不是语法选择,而是意识到:无论用哪种写法,obj.__proto__ === Constructor.prototype 这条链始终没变。忘了这点,任何语法糖都会变成陷阱。











