正在加载,请稍候…

JavaScript 原型链详解:无类的继承机制

掌握 JavaScript 原型继承。了解原型链的工作原理、__proto__ 与 prototype 的区别、class 语法糖以及常见模式。

JavaScript 原型链详解:无类的继承机制

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 原型链详解:无类的继承机制 插图

原型链

当你访问一个属性时,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

JavaScript 原型链详解:无类的继承机制 插图

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(所有对象都是!)

JavaScript 原型链详解:无类的继承机制 插图

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/对象结构。