JavaScript 中原型链的概念很难理解,它到底是什么,有什么作用,对 JavaScript 又有什么意义

5 阅读5分钟

JavaScript 中原型链的概念很难理解,它到底是什么,有什么作用,对 JavaScript 又有什么意义

核心答案

原型链(Prototype Chain)是 JavaScript 实现对象之间继承的核心机制。它的本质是一条__proto__ 指针串联起来的对象链条

它是什么: 每个 JavaScript 对象内部都有一个隐藏属性 [[Prototype]](可通过 __proto__Object.getPrototypeOf() 访问),它指向另一个对象(即该对象的原型)。原型本身也是对象,也有自己的 [[Prototype]],如此层层向上,直到某个对象的原型为 null,这条完整的链路就是原型链

它有什么作用: 当你访问一个对象的属性或方法时,如果对象自身没有,JS 引擎会沿着原型链逐级向上查找,直到找到该属性或到达链顶端 null。这就是 JavaScript 实现属性继承和方法共享的方式。

对 JavaScript 的意义: JavaScript 没有传统的"类"概念(ES6 的 class 只是语法糖),它是一门基于原型(Prototype-based) 的面向对象语言。原型链是整个对象系统的地基,instanceof 判断、属性查找、方法继承、Object.create() 等核心能力全部建立在原型链之上。

深入解析

三个关键概念的关系

要理解原型链,必须先厘清三个概念之间的关系:

graph LR
    A["Person<br/>(构造函数)"] -->|"prototype"| B["Person.prototype<br/>(原型对象)"]
    B -->|"constructor"| A

    C["person1<br/>(实例对象)"] -->|"__proto__"| B

用一张完整的关系图来看:

graph TD
    Person["🔧 Person<br/>构造函数"]
    PersonProto["📦 Person.prototype<br/>原型对象<br/>{ constructor, greet, ... }"]
    ObjectProto["📦 Object.prototype<br/>{ toString, valueOf, ... }"]
    Null["🚫 null<br/>链顶端"]
    Instance["🟢 person1 实例<br/>{ name: 'Tom' }"]

    Person -->|"prototype"| PersonProto
    PersonProto -.->|"constructor"| Person

    Instance -->|"__proto__"| PersonProto
    PersonProto -->|"__proto__"| ObjectProto
    ObjectProto -->|"__proto__"| Null

三条核心规则:

  1. 每个函数都有一个 prototype 属性,指向它的原型对象
  2. 每个对象都有一个 __proto__(即 [[Prototype]]),指向创建它的构造函数的 prototype
  3. 原型对象的 constructor 属性指回构造函数本身

属性查找机制

当你执行 person1.toString() 时,JS 引擎的查找过程如下:

person1 自身有 toString 吗? → 没有
      ↓
person1.__proto__(即 Person.prototype)有吗? → 没有
      ↓
Person.prototype.__proto__(即 Object.prototype)有吗? → 找到了!执行它
      ↓
如果还没有,Object.prototype.__proto__ === null → 返回 undefined

这就是原型链的查找过程——从实例出发,沿着 __proto__ 逐级向上,直到 null

为什么需要原型链?——方法共享

如果没有原型链,每创建一个实例,方法都会被复制一份,这是巨大的内存浪费:

// 不用原型 —— 每个实例各持有一份 sayHi 函数
function Person(name) {
  this.name = name;
  this.sayHi = function() {     // 每次 new 都创建新的函数对象
    console.log(`Hi, I'm ${this.name}`);
  };
}
​
const a = new Person('A');
const b = new Person('B');
console.log(a.sayHi === b.sayHi);  // false —— 两份不同的函数,浪费内存
// 用原型 —— 所有实例共享同一份 sayHi
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log(`Hi, I'm ${this.name}`);
};
​
const a = new Person('A');
const b = new Person('B');
console.log(a.sayHi === b.sayHi);  // true —— 同一份函数,节省内存

这就是原型链最实际的作用:让所有实例共享原型上的方法,避免重复创建

new 操作符与原型链的建立

new Person('Tom') 背后发生了四件事:

function myNew(Constructor, ...args) {
  // 1. 创建一个空对象,将其 __proto__ 指向构造函数的 prototype
  const obj = Object.create(Constructor.prototype);
​
  // 2. 将构造函数的 this 绑定到新对象,执行构造函数
  const result = Constructor.apply(obj, args);
​
  // 3. 如果构造函数返回了一个对象,则使用该对象;否则返回新创建的对象
  return (result !== null && typeof result === 'object') ? result : obj;
}

关键在第 1 步:Object.create(Constructor.prototype) 把新对象的 __proto__ 指向了 Constructor.prototype原型链在这一刻被建立

原型链的终点

所有原型链最终都汇聚到同一个终点:

Object.prototype.__proto__ === null   // true —— 这就是链的尽头// 任何对象最终都能沿链到达 Object.prototype
[].__proto__.__proto__ === Object.prototype              // true(Array → Object)
(function(){}).__proto__.__proto__ === Object.prototype   // true(Function → Object)

特殊情况: Object.create(null) 创建的对象没有原型,它是一个"纯净"的字典对象:

const dict = Object.create(null);
dict.__proto__    // undefined
dict.toString     // undefined —— 连 Object.prototype 上的方法都没有

instanceof 的本质就是原型链检测

a instanceof B 的底层逻辑就是:沿着 a 的原型链向上查找,看能不能找到 B.prototype

function Person() {}
const p = new Person();

p instanceof Person;   // true  → p.__proto__ === Person.prototype ✓
p instanceof Object;   // true  → p.__proto__.__proto__ === Object.prototype ✓

// 手动实现 instanceof
function myInstanceof(obj, Constructor) {
  let proto = Object.getPrototypeOf(obj);
  while (proto !== null) {
    if (proto === Constructor.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

ES6 class 只是原型链的语法糖

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  bark() {
    console.log('Woof!');
  }
}

// 本质上等同于:
// Dog.prototype.__proto__ === Animal.prototype
// 原型链:dog → Dog.prototype → Animal.prototype → Object.prototype → null

const dog = new Dog('Rex');
dog.bark();    // 自身原型上找到
dog.speak();   // 沿链到 Animal.prototype 找到
dog.toString(); // 沿链到 Object.prototype 找到

常见误区

  • 误区一:prototype__proto__ 是同一个东西。 不是。prototype 是函数的属性,指向原型对象;__proto__ 是所有对象的属性,指向其构造函数的 prototype。普通对象没有 prototype 属性。
  • 误区二:修改原型是安全的。 直接替换整个 prototype 对象会断开已有实例的关联,且会丢失 constructor 指向:
function Foo() {}
const old = new Foo();

Foo.prototype = { newMethod() {} };   // 替换整个原型

const newer = new Foo();
old.newMethod;     // undefined —— old 的 __proto__ 还指向旧的原型
newer.newMethod;   // function  —— newer 指向新的原型
newer.constructor === Foo;  // false —— constructor 丢失了
  • 误区三:for...in 只遍历自身属性。 错,for...in 会遍历原型链上所有可枚举属性,所以通常需要配合 hasOwnProperty 使用:
function Person(name) { this.name = name; }
Person.prototype.type = 'human';

const p = new Person('Tom');

for (let key in p) {
  console.log(key);  // 'name', 'type' —— type 来自原型
}

// 只获取自身属性
for (let key in p) {
  if (p.hasOwnProperty(key)) {
    console.log(key);  // 只有 'name'
  }
}

// 更推荐用 Object.keys(),天然只返回自身可枚举属性
Object.keys(p);  // ['name']
  • 误区四:箭头函数也有 prototype 没有。箭头函数不能作为构造函数,没有 prototype 属性,不能用 new 调用。

代码示例

完整的原型链验证

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  return `Hello, I'm ${this.name}`;
};

const tom = new Person('Tom');

// 实例的 __proto__ 指向构造函数的 prototype
console.log(tom.__proto__ === Person.prototype);           // true
console.log(Object.getPrototypeOf(tom) === Person.prototype); // true(推荐写法)

// 原型对象的 constructor 指回构造函数
console.log(Person.prototype.constructor === Person);      // true

// 原型链向上追溯
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null);            // true

// 属性查找顺序验证
console.log(tom.greet());     // "Hello, I'm Tom"  → 来自 Person.prototype
console.log(tom.toString());  // "[object Object]"  → 来自 Object.prototype
console.log(tom.foo);         // undefined          → 整条链都没有

基于原型链的继承

function Animal(name) {
  this.name = name;
}
Animal.prototype.eat = function() {
  console.log(`${this.name} is eating`);
};

function Dog(name, breed) {
  Animal.call(this, name);   // 继承实例属性
  this.breed = breed;
}

// 继承原型方法:将 Dog.prototype 的 __proto__ 指向 Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;  // 修复 constructor 指向

Dog.prototype.bark = function() {
  console.log('Woof!');
};

const rex = new Dog('Rex', 'Labrador');

// 原型链:rex → Dog.prototype → Animal.prototype → Object.prototype → null
rex.bark();      // "Woof!"         → Dog.prototype
rex.eat();       // "Rex is eating"  → Animal.prototype
rex.toString();  // "[object Object]" → Object.prototype

console.log(rex instanceof Dog);    // true
console.log(rex instanceof Animal); // true
console.log(rex instanceof Object); // true

属性遮蔽(Shadowing)

function Person() {}
Person.prototype.x = 10;

const p = new Person();
console.log(p.x);       // 10 —— 来自原型

p.x = 20;               // 在实例自身上创建属性 x
console.log(p.x);       // 20 —— 自身属性遮蔽了原型属性
console.log(p.__proto__.x);  // 10 —— 原型上的 x 没被修改

delete p.x;              // 删除自身属性
console.log(p.x);       // 10 —— 原型属性重新"露出来"

面试技巧

面试官可能的追问方向:

  1. "prototype__proto__ 有什么区别?"prototype 是函数独有的属性,__proto__ 是所有对象都有的。实例.__proto__ === 构造函数.prototype

  2. "手写 instanceof" → 本质就是沿着原型链查找(见上文代码)。

  3. "手写 new 操作符" → 要体现 Object.create 建立原型链这一步。

  4. "ES6 class 和原型链什么关系?" → class 是语法糖,extends 本质是设置 子类.prototype.__proto__ === 父类.prototype

  5. "如何创建一个没有原型的对象?"Object.create(null),常用于创建纯净的 map/字典。

  6. "原型链有性能问题吗?" → 链太长会影响属性查找速度。现代引擎有隐藏类(Hidden Class)和内联缓存(Inline Cache)优化,但仍应避免过深的继承层次。

    如何展示深度:

  • 不要只背概念,用"查找机制"串联起 __proto__prototypeconstructor 三者的关系
  • 能画出完整的原型链指向图(面试中可以在白板上画)
  • 提到 Object.create(null) 的实际用途(Vue 2 源码中大量使用)
  • 提到 for...in 会遍历原型链属性这一实际踩坑点
  • 提到 ES6 class 是语法糖,说明你理解底层机制而非只会用新语法

一句话总结

原型链就是对象通过 __proto__ 层层链接到 null 的一条查找链路,JavaScript 的属性继承、方法共享、instanceof 判断全部建立在它之上——理解原型链,就理解了 JS 对象系统的根基。