原型和原型链

135 阅读3分钟

原型和原型链

原型和原型链是 JS 中非常基础但又非常重要的东西。本篇文章争取简单直接的说清楚这两个东西究竟是什么,以及 prototype__proto__,又代表着什么?

顶层构造函数

Object 是 JS 中的顶层构造函数,以下两种写法是等价的:

// 写法1
const obj = { name: '小明' };

// 写法2
const obj2 = new Object({ name: 'obj2' })

其实明白了 Object 是如何创建对象的,也就了明白了什么是原型。

在 JS 中,每个函数都有一个属性 prototype,这个属性指向一个对象。这个对象中有一个属性 constructor,这个属性又指回了函数本身。我们以 Object 这个顶层构造函数为例,他们的关系如下图所示。

20250528163621

以下代码的输出结果可以验证:

console.log(Object.prototype.constructor === Object);  // true

那这个关系到底又有什么用呢?

当我们执行const obj2 = new Object({ name: 'obj2' })这行代码时,JS 引擎做了下面这些事:

  1. JS 底层(通常是浏览器或者 Node 引擎)创建一个空对象。
  2. 将这个空对象的 __proto__ 属性赋值为 Object.prototype
  3. 对这个对象执行 constructor 里面的代码,这个例子中也就是设置 name: 'obj2'

这几件事儿做完之后,三者的关系如下:

20250528163646

Object.prototype.__proto__ = null,也就是说 Object.prototype 如果继续往上找圆原型就是 null 了。这个图比较重要,里面 prototype, constructor, __proto__,到底是什么意思其实不太重要,归根结底,它们只是对象身上的属性。但是它们各自的起点和终点大家是要了然于胸的。

  • prototype:构造函数的属性,指向其创建的原型对象,该原型对象将被实例共享。
  • constructor:原型对象的属性,默认指回构造函数本身,用于标识实例的创建者。
  • __proto__:实例对象的属性,指向构造函数的原型对象(即 prototype)。

自定义构造函数

再来一个案例,这个案例中我们自定义一个构造函数,通过这个构造函数再来分析一遍。

function Human(name) {
    this.name = name;
}

const xh = new Human('小红');

Human.prototype.eat = function(food) {
    console.log("I'm eating " + food);
}

console.log(xh);
  1. 创建函数 Human。

20250528163707

  1. 创建 xh,具体流程图中体现。这个时候原型链就已经体现出来了,xh 的原型是 Human.prototype,Human.prototype 的原型是 Object.prototype。

20250528163719

  1. 挂载 eat 方法到原型上。

20250528163733

这个时候如果调用 xh.eat(), JS 引擎就会发现 xm 这个对象实例本身没有 eat 方法,但是往上找它的原型(__proto__),就能找到 eat 方法,接着就会调用这个 eat 方法。所以这也是为什么公用方法应该挂在到原型上。

xh 的 __proto__ 是通过 new Human 创建对象是绑定的,绑定的值就是 Human.prototype,而 Human.prototype__proto__ 是因为在创建 Human.prototype 时调用了 new Object(),所以绑定的就是 Object.prototype。这就是原型链的由来。

总结一下

  • prototype 就是构造函数的一个属性,这个属性决定了该构造函数 new 出来的对象实例的 __proto__ 到底是谁;
  • __proto__ 则是对象身上的一个属性,指向该对象的原型到底是谁(虽然这个属性严格来说其实并不存在,但在大多数浏览器环境是支持这样访问的。)
  • 而通过构造函数的层层连接,原型链就形成了。