原型链与 class 的底层关系
JavaScript 没有传统意义上的类,继承靠的是原型链。ES6 的 class 让它写起来像类,但运行时机制没变。
三个概念
- 每个函数都有
prototype属性,指向一个对象; - 每个对象都有内部属性
[[Prototype]],可通过Object.getPrototypeOf访问; - 用
new调用函数时,新对象的[[Prototype]]指向函数的prototype。
function Person(name) { this.name = name; }
Person.prototype.hi = function () { return 'hi ' + this.name; };
const p = new Person('a');
Object.getPrototypeOf(p) === Person.prototype; // true
p.hi(); // 'hi a'
属性查找沿着 p → Person.prototype → Object.prototype → null 逐级向上,找到就停。这就是原型链。
instanceof 的原理
p instanceof Person
// 等价于:Person.prototype 是否出现在 p 的原型链上
所以修改 Person.prototype 会让已经创建的实例判断失败——实例仍然指向旧的原型对象。
class 做了什么
class Person {
constructor(name) { this.name = name; }
hi() { return 'hi ' + this.name; }
static create(n) { return new Person(n); }
}
编译后本质仍是构造函数加原型方法,但有三点区别:
class声明不会提升(存在暂时性死区);- 类内部默认严格模式;
- 必须用
new调用,否则报错。
继承的差异
class Child extends Parent 会同时设置两条链:Child.prototype.__proto__ = Parent.prototype,以及 Child.__proto__ = Parent(静态方法继承)。用 ES5 手写继承时,第二条经常被忽略,导致父类的静态方法拿不到。
class Animal { static kind() { return 'animal'; } }
class Dog extends Animal {}
Dog.kind(); // 'animal'
实践建议
业务代码优先用 class,可读性和错误提示都更好。只有在需要混入(mixin)、动态修改原型、或与老库交互时才直接操作原型。判断一个对象是不是数组,用 Array.isArray 而不是 instanceof——跨 iframe 时后者会失效。