JavaScript 原型链详解:无类的继承机制
JavaScript 的继承模型与 Java 等经典面向对象语言根本不同。理解原型能揭示 JavaScript 实际的工作方式——并解释许多“令人惊讶”的行为。
每个对象都有一个原型
const dog = { name: 'Rex', sound: 'Woof' };
// dog 的原型是 Object.prototype
console.log(Object.getPrototypeOf(dog) === Object.prototype); // true
// Object.prototype 的原型是 null(链的终点)
console.log(Object.getPrototypeOf(Object.prototype)); // null
原型链
当你访问一个属性时,JavaScript 会沿着链向上查找:
const animal = {
breathe() { return 'breathing...'; },
eat() { return 'eating...'; }
};
const dog = {
bark() { return 'Woof!'; }
};
// 将 dog 的原型设置为 animal
Object.setPrototypeOf(dog, animal);
// dog 现在可以使用 animal 的方法
console.log(dog.bark()); // 'Woof!'(自有属性)
console.log(dog.breathe()); // 'breathing...'(来自原型)
console.log(dog.eat()); // 'eating...'(来自原型)
// 属性查找链:
// dog.breathe → 在 dog 上未找到 → 检查 dog 的原型(animal)→ 找到!
__proto__ 与 prototype 的区别
这困扰了所有人。以下是清晰的区分:
// __proto__ — 每个对象实例上的实际原型链接
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
// .prototype — 仅存在于函数上,用作新实例的模板
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() { return 'Woof!'; };
const rex = new Dog('Rex');
console.log(rex.__proto__ === Dog.prototype); // true ← 关键关系!
Dog(构造函数)
│
│ .prototype
↓
Dog.prototype ←─── rex.__proto__
{ bark: fn } (同一个对象)
│
│ __proto__
↓
Object.prototype
{ toString, hasOwnProperty, ... }
│
│ __proto__
↓
null
new 关键字
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
const alice = new Person('Alice', 30);
// new 做了 4 件事:
// 1. 创建空对象:{}
// 2. 设置 __proto__:{}.__proto__ = Person.prototype
// 3. 以新对象作为 this 调用 Person
// 4. 返回该对象(除非构造函数返回了不同的对象)
console.log(alice.name); // 'Alice'(自有属性)
console.log(alice.greet()); // 'Hi, I'm Alice'(来自原型)
console.log(alice instanceof Person); // true
Class 语法:原型之上的语法糖
ES6 的 class 是语法糖——底层仍然使用相同的原型系统。
// ES5 基于原型
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a noise.`;
};
function Dog(name) {
Animal.call(this, name); // 调用父构造函数
}
Dog.prototype = Object.create(Animal.prototype); // 设置继承
Dog.prototype.constructor = Dog; // 修复构造函数引用
Dog.prototype.bark = function() {
return `${this.name} barks.`;
};
// ES6 class(行为相同)
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a noise.`;
}
}
class Dog extends Animal {
bark() {
return `${this.name} barks.`;
}
}
const d = new Dog('Rex');
console.log(d.speak()); // Rex makes a noise.(来自 Animal.prototype)
console.log(d.bark()); // Rex barks.(来自 Dog.prototype)
Object.create() — 显式原型继承
const vehicleProto = {
start() { return `${this.model} starting...`; },
stop() { return `${this.model} stopping.`; },
};
// 创建以 vehicleProto 为原型的对象
const car = Object.create(vehicleProto);
car.model = 'Tesla Model 3';
car.drive = function() { return `Driving ${this.model}`; };
console.log(car.start()); // Tesla Model 3 starting...(来自原型)
console.log(car.drive()); // Driving Tesla Model 3(自有方法)
// Object.create(null) — 无原型(适用于纯字典)
const pureDict = Object.create(null);
pureDict.key = 'value';
// 没有 toString,没有 hasOwnProperty——真正干净的对象
检查自有属性与继承属性
class Animal {
constructor(name) { this.name = name; }
speak() { return 'some noise'; }
}
class Dog extends Animal {
bark() { return 'Woof!'; }
}
const rex = new Dog('Rex');
// hasOwnProperty — 仅检查自有(非继承)属性
console.log(rex.hasOwnProperty('name')); // true(在构造函数中设置)
console.log(rex.hasOwnProperty('bark')); // false(在 Dog.prototype 上)
console.log(rex.hasOwnProperty('speak')); // false(在 Animal.prototype 上)
// in 操作符 — 检查整个链
console.log('name' in rex); // true
console.log('bark' in rex); // true
console.log('speak' in rex); // true
console.log('fly' in rex); // false
// instanceof — 检查原型链
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true
console.log(rex instanceof Object); // true(所有对象都是!)
Mixin 模式(组合行为)
// Mixin 让你组合行为,无需深层继承链
const Serializable = {
serialize() {
return JSON.stringify(this);
},
static deserialize(json) {
return Object.assign(new this(), JSON.parse(json));
}
};
const Validatable = {
validate() {
return Object.keys(this).every(key => this[key] !== null);
}
};
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
// 混入行为
Object.assign(User.prototype, Serializable, Validatable);
const user = new User('Alice', 'alice@example.com');
console.log(user.serialize()); // '{"name":"Alice","email":"alice@example.com"}'
console.log(user.validate()); // true
常见陷阱
// ❌ 陷阱:原型上的共享可变状态
function Team() {}
Team.prototype.members = []; // 所有实例共享!
const t1 = new Team();
const t2 = new Team();
t1.members.push('Alice');
console.log(t2.members); // ['Alice'] — 糟糕!
// ✅ 修复:在构造函数中初始化
function Team() {
this.members = []; // 每个实例自有属性
}
// ❌ 陷阱:箭头函数不能作为构造函数
const Foo = () => {};
new Foo(); // TypeError: Foo is not a constructor
// ❌ 陷阱:箭头函数没有自己的 prototype
const bar = () => {};
console.log(bar.prototype); // undefined
总结
- 每个 JS 对象都有一个原型(除了
Object.create(null)) - 属性查找沿着链向上,直到找到或到达
null __proto__:实例上的实际原型链接.prototype:存在于函数上,成为new实例的__proto__- ES6 class 是语法糖——底层仍是相同的原型系统
- 优先使用组合(mixin)而非深层继承层次
→ 使用 JSON 查看器 检查嵌套的 JSON/对象结构。