前端JS: 原型链

6 阅读4分钟

前端面试中关于原型链的考点主要集中在以下几个方面:

核心概念

  1. prototype​ - 函数特有的属性,指向该函数的原型对象
  2. __proto__ ​ - 对象特有的属性,指向其构造函数的原型
  3. constructor​ - 原型对象的属性,指向构造函数本身

理解:

null > 爷(Object) > 父(构造函数) > 子 (对象)

爷,父,子都有 proto,指向它上级的prototype原型

父 有prototype,原型,只要构造函数有prototype

父 的structor是原型的属性,就是自己

原型链关系

// 基本关系
function Person() {}
const p = new Person();

// 关系链
p.__proto__ === Person.prototype
Person.prototype.constructor === Person
Person.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

new 操作符原理

function myNew(constructor, ...args) {
  // 1. 创建空对象
  const obj = {}
  // 2. 设置原型链
  obj.__proto__ = constructor.prototype
  // 3. 绑定this并执行构造函数
  const result = constructor.apply(obj, args)
  // 4. 返回结果
  return typeof result === 'object' ? result : obj
}

instanceof 原理实现

function myInstanceof(left, right) {
  let proto = left.__proto__
  const prototype = right.prototype
  
  while (proto) {
    if (proto === prototype) return true
    proto = proto.__proto__
  }
  return false
}

从构造函数 → prototype → __proto__→ 原型链逐层讲解

第一步:构造函数 (Constructor)

构造函数是用来创建特定类型对象的函数。通常约定首字母大写。

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

当用 new调用时,它会创建一个新对象,并将函数内部的 this指向这个新对象。

此时,这个 Person函数有一个特殊的自带属性:prototype


第二步:prototype (原型对象)

prototype是函数独有的属性。它是一个对象,我们称之为“原型对象”。

当你创建 Person函数时,JavaScript 会自动为你创建这个 Person.prototype对象,并给它一个 constructor属性指回构造函数本身。

console.log(Person.prototype); // 输出:{}
console.log(Person.prototype.constructor === Person); // 输出:true

作用prototype的主要作用是实现基于原型的继承与共享属性。所有由该构造函数创建的对象,都可以访问到这个原型对象上的属性和方法。


第三步:__proto__(隐式原型)

__proto__是对象(包括函数对象,但这里我们主要讨论实例对象)独有的属性。它指向创建该对象的构造函数的 prototype

当我们用 new创建实例时:

const p1 = new Person('小明');

实例 p1的内部 [[Prototype]]链(可通过 __proto__属性访问,但不建议在生产环境使用,它是历史遗留的getter)就被设置了。

console.log(p1.__proto__ === Person.prototype); // 输出:true

关键等式

实例的 __proto__ ​ === 其构造函数的 prototype

这意味着,当访问 p1.name时,引擎直接在 p1上找到。当访问 p1.toString()时,引擎会:

  1. 先在 p1自身上找 toString,没找到。
  2. 通过 p1.__proto__Person.prototype上找,没找到。
  3. 再通过 Person.prototype.__proto__继续往上找... 这就形成了“链”。

第四步:原型链 (Prototype Chain)

原型链是由 __proto__链接起来的链式结构,它定义了对象属性的查找机制。

让我们补全链条:

// 1. p1 是 Person 的实例
p1.__proto__ === Person.prototype

// 2. Person.prototype 本身也是一个普通对象,它是谁创建的?
// 答案是:Object
Person.prototype.__proto__ === Object.prototype

// 3. Object.prototype 是原型链的顶端吗?不是,它的 __proto__ 是 null
Object.prototype.__proto__ === null

// 4. 那 Object 函数呢?
// Object 本身是一个构造函数,所以它也有 prototype
Object.prototype.constructor === Object

// 5. 那 Person 函数是谁的实例?函数是 Function 的实例
Person.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype

用图表示这个查找链(以 p1.toString为例):

p1
  ↓ 查找 toString? 没有
p1.__proto__Person.prototype
                  ↓ 查找 toString? 没有
Person.prototype.__proto__Object.prototype
                                  ↓ 查找 toString? 找到了!调用。
Object.prototype.__proto__null (查找终点)

总结与比喻

概念归属指向比喻
构造函数​ (Person)函数(创建对象的蓝图)模具
prototype函数的属性原型对象模具附带的共享零件库
实例​ (p1)对象(具体的产品)用模具生产出的产品
__proto__实例的属性构造函数的 prototype产品的说明书,写着“缺零件时去共享库找”
原型链__proto__链接一条从实例到顶端的链按照说明书,去A库找,A库的说明书说去B库找...直到总库。

最终答案:原型链的尽头是 nullObject.prototype.__proto__ === null。当查找属性到达 null时,意味着未找到,返回 undefined

所以,回答时可以这样串起来:

“当我们访问一个对象的属性时,如果对象自身没有,JS 引擎就会通过它的 __proto__去它的构造函数的 prototype上找,如果还没有,就继续通过 prototype.__proto__往上找,直到原型链顶端 null为止。这条由 __proto__连接起来的查找路径,就是原型链。”