一文读懂 JS 原型链

开篇先放大招:

总结起来 JS 的原型链就只有两条规则:

  1. 构造对象 的原型指向 构造函数 的 prototype 属性。
  2. Object.prototype 的原型不可更改且指向 null(原型链的尽头)。

这有点类似于数学归纳法:先有一个初始条件,再有一套递归规则。接下来,我会用几个例子来证明这个观点。

JS 中最重要的三个数据结构就是对象、函数和数组。下面我将分别捋清这 3 类对象的原型链。

相信你一定看过这张图来源:www.cnblogs.com/dreamcc/p/1...

这张图虽然画得不错,但对于初学者来说,更像是一堆零碎知识点的堆叠。其实,关于原型链,核心规则就是我上面说的那两条。接下来我们来验证。

对象

一个普通对象 {} 是由 Object 这个构造函数构造出来的。根据第一条规则,构造对象的原型会指向构造函数的 prototype 属性,所以:

javascript 复制代码
console.log(Object.getPrototypeOf({}) === Object.prototype); // true

Object 本身又是一个函数。函数都是通过 Function 构造出来的,所以:

javascript 复制代码
console.log(Object.getPrototypeOf(Object) === Function.prototype); // true

Object.prototype 的值是一个对象。按理来说,所有对象都应该通过 Object 构造出来,但这里为了避免循环,规范对它做了特殊处理,让它的原型直接指向 null

javascript 复制代码
console.log(Object.getPrototypeOf(Object.prototype) === null); // true

PS:这里补充一个知识点。null 可以理解为空对象,undefined 可以理解为空值。这里会涉及 JS 中的原始值,不懂的同学可以去 MDN 上看一看。

数组

一个普通数组是由 Array 这个函数构造出来的,所以:

javascript 复制代码
console.log(Object.getPrototypeOf([]) === Array.prototype); // true

和上面的 Object 一样,Array 本身也是一个函数,所以:

javascript 复制代码
console.log(Object.getPrototypeOf(Array) === Function.prototype); // true

Array.prototype 是一个对象,而对象都是由 Object 构造的。这里和 Object.prototype 不一样,需要注意:

javascript 复制代码
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true

函数

无论是普通函数还是箭头函数,它们默认都是由 Function 这个函数构造出来的,所以:

javascript 复制代码
function fnc() {
  return 0;
}

const arrFnc = () => {
  return 0;
};

console.log(Object.getPrototypeOf(fnc) === Function.prototype); // true
console.log(Object.getPrototypeOf(arrFnc) === Function.prototype); // true

Function.prototype 是一个对象,所以:

javascript 复制代码
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype); // true

constructor

constructor 这部分的规则也比较简单,首先我们要知道,设置 constructor 属性的目的是什么。

假设我现在有一个对象,想知道它是如何构造出来的,那我应该怎么找?

假设有这样一种情况:

javascript 复制代码
class Person {
  constructor(name: string) {
    this.name = name;
  }

  name: string = '';
}

const person = new Person('bo');

console.log(Object.getPrototypeOf(person) === Object.prototype); // false

难道我要把项目里所有的 class 都遍历一遍,才能找到它是谁构造出来的吗?显然不现实。constructor 的意义就在这里,它提供了一条可以快速回溯到构造函数的路径。

javascript 复制代码
class Person {
  constructor(name: string) {
    this.name = name;
  }

  name: string = '';
}

const person = new Person('bo');

console.log(Object.getPrototypeOf(person).constructor === Person); // true

const copyPerson = Object.getPrototypeOf(person).constructor;
const child = new copyPerson('child');

console.log(child.name); // child
console.log(Object.getPrototypeOf(child) === Object.getPrototypeOf(person)); // true

总结一下,constructor 的作用就是让你可以通过原型链快速找到对应的构造函数,所以这里形成了一个回环结构。

flowchart LR instance["instance"] proto["Class.prototype"] cls["Class"] instance -->|"__proto__"| proto proto -->|"constructor"| cls cls -->|"prototype"| proto cls -->|"create"| instance

了解了这四个部分之后,就能和前面那种大图一一对上了。总结来说,原型链的知识其实并不复杂。它看起来乱,是因为相关概念比较分散,但底层规则并不复杂。学习编程和学习数学一样,关键是把握规律;如果只是死记硬背,思路就会越学越乱。

补充

为了方便讲解,文章里有些地方采用了不那么严谨的说法。这里先列出一部分,如果有遗漏,也欢迎各位同学指出:

  1. 不是所有对象的原型都指向 Object.prototype,只有默认创建出来的对象通常是这样。比如直接字面量创建,或者仅仅调用 Object()。你也可以使用 Object.create 在创建对象时指定原型,还可以在创建之后使用 Object.setPrototypeOf 更改对象原型,但这些都不违背我上面总结的那两条规则。
  2. 普通函数和箭头函数的区别,我会在以后的文章里再写;但在原型这一节里,你可以先把它们看作没有本质区别。
  3. 我提出的这两点关于原型链的总结,只是我个人当前的理解。如果有我没有覆盖到的地方,也欢迎大家积极评论,帮我找出错误,我们一起进步。
相关推荐
汉堡大王95272 小时前
为了搞懂 Promise 源码,我重写了 MiniPromise
前端·javascript
Hilaku2 小时前
OpenClaw 跟病毒的区别是什么?
前端·javascript·人工智能
小龙报4 小时前
【Coze-AI智能体平台】Coze OpenAPI 开发手册:鉴权、接口调用与 SDK 实践
javascript·人工智能·python·深度学习·microsoft·文心一言·开源软件
神の愛4 小时前
利用json-to-ts工具进行转换,放置在typeScript.ts文件中
javascript·typescript·json
悟空瞎说5 小时前
深度解析:Vue3 为何弃用 defineProperty,Proxy 到底强在哪里?
前端·javascript
leafyyuki5 小时前
告别 Vuex 的繁琐!Pinia 如何以更优雅的方式重塑 Vue 状态管理
前端·javascript·vue.js
某人辛木5 小时前
nodejs下载安装
开发语言·前端·javascript
zzginfo6 小时前
javascript 类定义常见注意事项
开发语言·前端·javascript
天下无贼!6 小时前
【功能实现】基于Vue3+TS实现大文件分片上传
开发语言·javascript·node.js·vue·html5