一文读懂 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. 我提出的这两点关于原型链的总结,只是我个人当前的理解。如果有我没有覆盖到的地方,也欢迎大家积极评论,帮我找出错误,我们一起进步。
相关推荐
Y38153266228 分钟前
body-upload-js
前端·javascript·浏览器·深拷贝·数据克隆
Json____37 分钟前
java-宿舍安全卫生检查系统项目源码
java·前端·javascript·课程设计·it学习·wwwoop.com
AbelTomato1 小时前
博客评论系统搭建回顾
javascript·typescript·cloudflare·supabase·hyperdrive
梦醒沉醉1 小时前
4、函数function
javascript
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 22
javascript·spring boot·笔记
xiaominlaopodaren2 小时前
three.js地图视口瓦片(一):屏幕角点射线
javascript·gis·three.js
一水行2 小时前
博客评论系统搭建回顾
javascript·后端
穗余3 小时前
Typescript let和var区别
前端·javascript·typescript
晓得迷路了3 小时前
栗子前端技术周刊第 144 期 - Rspack 2.2、pnpm 12、Solid 2.0 RC...
前端·javascript·css
奈斯先生Vector3 小时前
从“能调用”到“可运营”:AI Agent 进入多模型时代后的架构升级
java·javascript·数据库·人工智能·算法·架构·aigc