Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一张图带你搞懂Javascript原型链关系

一张图带你搞懂Javascript原型链关系

作者头像
xing.org1^
发布于 2021-08-10 02:29:41
发布于 2021-08-10 02:29:41
1.1K00
代码可运行
举报
文章被收录于专栏:前端说吧前端说吧
运行总次数:0
代码可运行

在某天,我听了一个老师的公开课,一张图搞懂了原型链。

老师花两天时间理解、整理的,他讲了两个小时我们当时就听懂了。

今天我把他整理出来,分享给大家。也让我自己巩固加深一下。

就是这张图:

为了更好的图文对照,我为每条线编了标号,接下来的细节讲解,都会用到这张图里的编号:

为了你更好的对照阅读,你可以单独打开这张图片,然后对比着文章看。

当然,我后边也会贴心的把对应区域截小图贴在文案附近。

前置知识

在对这张图进行详细拆解前,我们先来说几个前置的基础知识。以便后续更好的理解。

  • Function、Object、Array、String、Symbol等这些都是JavaScript的内建函数,也叫原生函数(js创造时,他们就存在的,是js内部提供的)
  • prototype:原型对象;
  • __proto__:隐式原型、对象的私有属性;
  • 所有的函数都是Function构造出来的,包括Object等原生函数。可以说,每个函数都是Function类型的实例。
  • 函数实际上是对象,但比较特殊,我们叫做函数对象
  • 每个函数被创造出来时都有一个prototype,表示该函数的原型。他就是原型对象
  • 每个对象身上都有一个私有属性__proto__,指向该对象的构造函数的原型对象。函数作为对象也有__proto__
  • prototype是一个对象,由Object构造出来的。所以他身上也有__proto__,永远指向对象的构造函数Object的原型(即:Object.prototype)
  • 函数都是被Function构造出来的,所以每个函数的__proto__都指向Function的原型(即:Function.prototype)
  • Object.prototype的__proto__不能再指向自身无限循环,所以指向null
  • Function.__proto__指向自身原型。因为Function没人构造,“生下来”就有。

如下图: 函数a,既有prototype、也有__proto__

内建函数Object,既有prototype、也有__proto__

对象身上就只有__proto__

口诀提炼

为了更好的掌握,我把相关的知识点汇总成下列几条口诀。接下来的剖析中都会用到。

  1. 函数是Function构造出来的
  2. 一切函数都是对象
  3. 只要是函数对象,就会有原型prototype和隐式原型__proto__两个属性。
  4. 普通对象身上只有__proto__,没有prototype
  5. 实例化对象的__proto__都指向构造函数的prototype
  6. 所有函数的prototype都指向自身prototype
  7. 所有prototype的__proto__都指向Object.prototype(Object的除外)
  8. 所有函数对象的__proto__都指向Function.prototype(包括Function自身)
  9. 对象身上都有constructor指向函数自身

注意:这里不考虑原型链指向修改、Object.create(null)这些特殊情况

剖析一张图

接下来我们根据基础知识和口诀,正式来看图中的每一个细节

图例:

观察一个图之前,我们先看他的图例

右边表示节点的类型:

绿色方块:表示普通对象,比如平时创建的对象obj {}、arr **[]**等 红色方块:表示函数对象,也就是函数。他是一种特殊的对象。

左边表示箭头的指向:

绿色箭头:表示用 new + 构造函数调用 的方式创建实例化对象 白色箭头:表示当前节点的prototype原型对象的指向 蓝色箭头:表示当前节点的__proto__私有属性的指向

详情

Function

我们先看最右边的Function(图中色块1)。

他是js的内部函数,你打印Function会得到标示着“本地代码”的结果。

也就是说他是js一开始就有的。

而伴随他出生的就是他的原型: Function.prototype。(图中色块2) prototype是函数特有的标志,每个函数被创建出来,身上就有一个prototype的属性,表示自己的原型对象。 根据口诀:所有函数的prototype都指向自身prototype。 也就是说,Function.prototype指向Function原型。 所以 Function.prototype === Function.prototype(图中线条a)

然后说下比较特殊的Function.__proto__

因为Function他是个函数,函数又是一种特殊的对象(函数类对象,又叫函数对象)。所以作为对象身上特有的标志__proto__,在Function身上也有一个。 另外,任何对象都可以理解为实例化对象,所以我们总结出口诀:实例化对象的__proto__都指向构造函数的prototype所有prototype的__proto__都指向Object.prototype(Object的除外) 如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const obj = new Object() // 或 const obj = {} 的字面量写法

obj.__proto__ === Object.prototype // true

// 实例化对象obj,其隐式原型__proto__指向构造函数Object的原型

所以,Function.__proto__本来也应该指向Function的构造函数的原型。 但是因为Function比较特殊,他是祖宗级别的函数,是JS中万物开天辟地就有的,不能说谁把他构造出来的, 因此Function的__proto__的指向就比较特殊,他没有自己的构造函数,于是就指向了自己的原型。 于是Function.__proto__指向自己的原型Function.prototype。 所以 Function.__proto__ === Function.prototype(图中线条b)

这是原型链中第一个特殊点。 口诀:所有函数对象的__proto__都指向Function.prototype(包括Function自身)

扩展:

原型对象prototype身上都有constructor属性,指回构造函数自身。 所以 Function.prototype.constructor === Function

Object

再说Object。(图中色块3) 我们平时见过这种创建函数的书写形式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const obj = new Object()

可见Object是一个函数。但同时函数又是一个对象。所以Object就是一个函数对象。 只要是函数对象,就会有原型prototype和隐式原型__proto__两个属性。

我们先看Object.prototype。(图中色块4) Object作为一个函数,他就有自己的原型:Object.prototype。 根据口诀:所有函数的prototype都指向自身prototype: 所以 Object.prototype === Object.prototype(图中线条d)

而对于Object.__proto__ 我们可以这样理解: Object作为一个函数,他是Function构造出来的。形似下面这种写法:(图中线条c)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const Object = new Function()

因此可以说Object是实例化函数对象。 根据口诀:一切 实例化对象的__proto__都指向构造函数的prototype函数是Function构造出来的 所以 Object.__proto__ === Function.prototype。(图中线条f)

原型的原型

我们来分析下两个内置函数的原型的原型:

先看Function.prototype.__proto__ Function.prototype作为Function的原型对象,他就是一个普通对象,但凡普通对象就都是Object构造出来的, 根据口诀:实例化对象的__proto__都指向构造函数的prototype所有prototype的__proto__都指向Object.prototype(Object的除外) 所以所有prototype对象的__proto__都指向构造函数Object的原型。包括Function函数的原型的隐式原型,也指向Object的原型。 所以 Function.prototype.__proto__ === Object.prototype。(图中线条g)

再看Object.prototype.__proto__ Object.prototype作为是一个普通对象,他的隐式原型__proto__也本应该指向构造函数的原型。 但由于prototype对象都是Object函数构造的,按照上边的规则,Object.prototype.__proto__也本应该指向Object.prototype。但是这么死循环的指没完没了了不是,还没有意义。 所以这里是原型链中第二个特殊点:让Object.prototype的原型指向null,好结束这段轮回。 也就是 Object.prototype.__proto__ === null。(图中线条e)

口诀:所有prototype的__proto__都指向Object.prototype(Object的除外)

好在,Object.prototye的构造函数还是诚实的,知道自己的祖宗是谁,于是他的constructor属性还是Ojbect。 Object.prototype.constructor === Object

自定义函数

我们都知道,平时我们用字面量的形式创建一个对象、数组、function, 其实都是new Object()、或 new Array()、new Function 这样的形式创建的。(图中线条h)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var obj = {};  // 类似写法 var obj = new Object();
var arr = [];  // 类似写法 var arr = new Array();
function Person() {}  // 类似写法 var Person = new Function(){}

所以对象、数组、函数这些都是实例化对象。 对象、数组稍后再谈,他们就是自定义对象。(图中色块7) 先说我们创建的函数 — — 自定义函数。(图中色块5)

「自定义函数」和Object性质一样,都是函数对象。只不过自定义函数的名字是我们用户自定义的,比如Person、Animal、clickHandle等。而Object、Array等是JS内部原生提供的。 但记住口诀:只要是函数对象,就会有原型prototype和隐式原型__proto__两个属性

先说自定义函数.prototype(图中色块6)。 前边说过,所有函数的prototype都指向自身prototype,原型上边的constructor再指回函数自身。

所以 Person.prototype === Person.prototype(图中线条i)

再说自定义函数.prototype.__proto__ 自定义函数的原型作为普通对象,由Object构造出来,其原型__proto__肯定指向Object.prototype。原理同Function.prototype。 根据口诀:实例化对象的__proto__都指向构造函数的prototype所有prototype的__proto__都指向Object.prototype(Object的除外) 所以 自定义函数.prototype.__proto__ === Object.prototype。(图中线条J)

再说自定义函数.__proto__ 根据口诀:实例化对象的__proto__都指向构造函数的prototype 因此,实例化对象(这里的自定义函数)的__proto__就指向构造函数的原型。根据口诀:函数是Function构造出来的、所有函数对象的__proto__都指向Function.prototype(包括Function自身)。所有自定义函数的构造函数是Function,他的原型也就是右边的Function.prototype。 自定义函数.__proto__ === Function.prototype。(图中线条k)

自定义对象

说清楚了自定义函数,我们再来说自定义对象。(图中色块7) 比如obj、arr这样的对象,他们和我们平时“new + 构造函数()”得到的实例化对象一样:(图中线条L)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const object = new Object()
const person = new Person()

以这个person为例,说一下图中绿色块7:自定义对象

既然叫「自定义对象」,那他肯定就只是一个对象。 对象就好说了,普通对象身上只有__proto__,而且普通对象(实例化对象)的__proto__指向构造函数的prototype。 根据口诀:实例化对象的__proto__指向构造函数的prototype 即自定义对象.__proto__ 指向 自定义对象的构造函数(即自定义函数)的prototype 所以 person.__proto__ === Person.prototype。(图中线条m)

于是图中(实例化)自定义对象.__proto__ 指向了上边自定义函数原型。

至此,这张图我们都过了一遍。

总结

  • Function.__proto__ === Function.prototype【特殊】
  • Function.prototype === Function.prototype
  • Function.prototype.constructor === Function
  • Function.prototype.__proto__ === Object.prototype
  • Object.__proto__ === Function.prototype。
  • Object.prototype.__proto__ === null 【特殊】
  • Person.__proto__ === Function.prototype
  • Person.prototype.__proto__ === Object.prototype
  • person.__proto__ === Person.prototype

原型链

由于原型对象prototype本身是一个对象,因此,他也有隐式原型__proto__。隐式原型指向的规则不变,指向构造函数的原型; 这样一来,原型 -> 隐式原型、隐式原型 -> 原型。 从某个对象出发,依次寻找隐式原型的指向,将形成一个链条,该链条叫做原型链。 在查找对象成员时,若对象本身没有该成员,则会到原型链中查找。

在上图和知识总结中我们看到: 自定义对象的__proto__指向自定义函数的原型。 而自定义函数的原型也是一个对象,他虽然在函数一生下来就有了,但是他作为对象,也是Object函数对象构建的。因此自定义函数原型身上的__proto__指向Object的原型对象。 而Object.prototype又指向null。 观察发现这最左边的一条居然形成了一个链式指向:自定义对象 -> 自定义函数的原型 -> Object原型 -> null。 当我们在最低部的自定义对象身上寻找一个属性或方法找不到的时候,JS就会沿着这条原型链向上查找,若找到就返回,直到null还查不到就返回undefined

同样的,函数 -> Function原型 -> Object原型 -> null, 也形成了原型链。当我们在函数身上调用一个方法或属性时,根据原型链的查找规则,会一直层层向上查找到null。

这也就是为什么,call、apply、bind这些函数是定义在Function原型身上的,我们也能用Person.call、Person.apply这样调用;hasOwnProperty、isPrototypeOf这些函数是定义在Object原型身上的,我们也能用Person.isPrototypeOf、obj.hasOwnProperty这样使用了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Person() {
  console.log('我是Person函数');
}
let obj = new Object()
let person = new Person()

console.log(person.hasOwnProperty('a')); 
// 原型链查找:person -> person.__proto__(即Person.prototype) -> Person.prototype.__proto__ (即Object.prototype) 找到hasOwnProperty函数,执行调用
console.log(Person.call());
// 原型链查找:Person -> Person.__proto__(即Function.prototype) 找到call函数,执行调用
console.log(obj.xxx)
// 原型链查找:obj -> obj.__proto__(即 Object.prototype) -> null 没找到,返回undefined

知识点扩展

函数对象和普通对象

普通对象是通过 new 函数() 创建/构造的 函数对象是通过 new Function() 构造的

所有对象都是通过 new 函数() 的方式创建的

  • 该函数叫做构造函数;
  • 创建的对象被称作实例化对象
  • 对象赋值给变量后,变量中保存的是地址,地址指向对象所在的内存。

函数也是一个对象,他是通过 new Function() 创建的

原型对象 prototype

原型prototype的本质:对象。 prototype又称作原型对象。 原型对象也有一个自己的原型对象:__proto__ 所有的函数都有原型属性prototype 默认情况下,prototype是一个Object对象。也就是说由Object构造函数创建,其原型指向Object的prototype。 prototype中默认包含一个属性:constructor,该属性指向函数对象本身 prototype中默认包含一个属性:__proto__,该属性指向构造函数的原型(默认情况是Object.prototype)

隐式原型 __proto__

所有的对象都有隐式原型:__proto__属性 隐式原型是一个对象,指向创建该对象的构造函数的原型 prototype 在查找对象成员时,若对象本身没有该成员,则会到隐式原型中查找。 层层向上知道Object.prototype。若到null还找不到则返回undefined。

__proto__ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。 来自《es6》<http://es6.ruanyifeng.com/#docs/class> ❞

隐式原型和原型出现的根本原因:

js没有记录类型的元数据。因此,js只能通过对象的隐式原型找到创建他的函数的原型,从而确定其类型。

特殊的两个情况

Function的隐式原型指向自己的原型 Object原型的隐式原型指向null

两个固定情况

所有函数的隐式原型,都指向Function的原型(包括Function函数自身) 所有函数原型的隐式原型,都指向Object的原型。(不包括Object原型对象自身)

constructor

原型中的constructor指向函数本身:

思考

Function原型上都有什么?

执行下列代码,创建一个普通该函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function a(){}

观察window.a在控制台的打印结果,展开a.__proto__,得到Function.prototype的所有默认属性:

图中可以看到,a.prototype.__proto__,即Function的原型中:

  • 有函数方法:call、apply、bind、toString、constructor、Symbol;(标志性就是call、apply、bind这仨)
  • 有属性:arguments、caller、以及这俩属性的getter和setter;
  • 最后还有对象:__proto__指向他的构造函数原型(也就是Object.prototype)

Object原型上都有什么?

有函数方法:hasOwnProperty、isPrototypeOf、propertyIsEnumerable、toLocaleString、toString、valueOf、以及constructor 特殊的还有:get __proto__、set __proto__,估计是为了返回null给拦截的。 标志就是get __proto__、set __proto__这俩

其他探索问题

数组函数的原型上都有什么? 自定义函数的原型上都有什么?

练手面试题

最后来两道面试题,欢迎评论区一起探讨:

让我们一起携手同走前端路, 关注公众号【前端印记】即可。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-08-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
关于javascript的原型和原型链,看我就够了(三)
原型对象有一个constructor属性,指向该原型对象对应的构造函数 foo 为什么有 constructor 属性?那是因为 foo 是 Foo 的实例。 那 Foo.prototype 为什么有 constructor 属性??同理, Foo.prototype Foo 的实例。 也就是在 Foo 创建的时候,创建了一个它的实例对象并赋值给它的 prototype
陌上寒
2019/04/02
5500
关于javascript的原型和原型链,看我就够了(三)
四张图带你搞定原型和原型链
「所有的对象都是通过new 函数生成的。」 包括let obj = {},这种形式其实是语法糖,本质上是通过let obj = new Object()生成的。那么函数又是如何生成的呢?从图中可以清晰的看出函数本质上是通过new Function生成的,尽管我们平时不会这么去写,当然也不建议这么去写
程序员法医
2022/08/11
6530
四张图带你搞定原型和原型链
JS完美收官——原型和原型链
原型和原型链这部分知识会影响到写面试题,或者做一些公共的组件和插件,总之是通用型的一些东西
程序员法医
2022/08/11
4540
JS完美收官——原型和原型链
一文带你彻底搞懂JavaScript原型链
Brendan Eich(布兰登·艾奇) 作为JavaScript的作者曾说过 “它是C语言和Self语言一夜情的产物。”
童欧巴
2020/03/30
4070
一文带你彻底搞懂JavaScript原型链
【前端芝士树】Javascript的原型与原型链
1994年,网景公司(Netscape)发布了Navigator浏览器0.9版,但是刚开始的Js没有继承机制,更别提像同时期兴盛的C++和Java这样拥有面向对象的概念。在实际的开发过程中,工程师们发现没有继承机制很难解决一些问题,必须有一种机制能将所有的对象关联起来。
CloudCat
2022/05/06
2640
【前端芝士树】Javascript的原型与原型链
再谈javascriptjs原型与原型链及继承相关问题
普通的内置对象与基本包装类型的主要区别就是对象的生命期,使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中,而自动创建的基本包装类型的对象,则只是存在于一行代码的执行瞬间,然后立即被立即销毁。这意味着我们不能再运行时为基本包装类型值添加属性和方法。
IMWeb前端团队
2019/12/04
5700
彻底弄懂JS原型与原型链
说到JavaScript的原型和原型链,相关文章已有不少,但是大都晦涩难懂。本文将换一个角度出发,先理解原型和原型链是什么,有什么作用,再去分析那些令人头疼的关系。
hellocoder2029
2022/12/07
1.3K0
Javascript之其实我觉得原型链没有难的那么夸张!
  原型链、闭包、事件循环等,可以说是js中比较复杂的知识了,复杂的不是因为它的概念,而是因为它们本身都涉及到很多的知识体系。所以很难串联起来,有一个完整的思路。我最近想把js中有点意思的知识都总结整理一下,虽然逃不开一些一模一样的内容,但是自己造一下轮子,按照自己的思路。也别有一番味道。
zaking
2020/08/19
8000
原型和原型链的深入浅出
对象是 javascript 基本数据类型。对象是一种复合值: 它将很多值(原始值或者其它对象)聚合在一起,可通过名字访问这些值。
前端老鸟
2022/03/07
4630
关于javascript的原型和原型链,看我就够了(二)
Object就是一个构造函数,是js内置的构造函数,上面的例子中Object就是obj的构造函数,这个例子似乎不太明显,我们继续看
陌上寒
2019/04/02
5340
关于javascript的原型和原型链,看我就够了(二)
JavaScript中的显示原型和隐形原型(理解原型链)
在js中万物皆对象,方法(Function)是对象,方法的原型(Function.prototype)是对象,对象具有属性(__proto__)称为隐式原型,对象的隐式原型指向构造该对象的构造函数的显式原型。
IT人一直在路上
2019/09/18
3.4K0
JavaScript中的显示原型和隐形原型(理解原型链)
第202天:js---原型与原型链终极详解
JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object 、Function 是 JS 自带的函数对象。下面举例说明
半指温柔乐
2018/09/11
1K0
第202天:js---原型与原型链终极详解
js原型及原型链解析
首先,明确一点:js中的对象分为普通对象和函数对象,一般我们自定义的可以被new的函数称作函数对象,另外js内置了譬如:Array、Date、Object、Function、Number、String、Boolean、RegExp、Error等这些函数对象:
用户1141560
2019/05/24
2.4K0
详解原型与原型链
其实,刚开始学 JavaScript 时,就有学过原型与原型链的相关知识了,只是当时还没有养成写笔记的习惯,导致现在已经忘的七七八八了。
赤蓝紫
2023/01/02
4400
详解原型与原型链
JS 原型链
Function 是JavaScript 里最顶层的构造器,它构造了系统中的所有对象,包括定义对象、系统内置对象、甚至包括它自己。
用户8921923
2022/10/24
2.5K0
JS 原型链
【前端基础进阶】JS原型、原型链、对象详解
在上面的例子中 o1 o2 o3 为普通对象, f1 f2 f3 为函数对象。 怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。 f1,f2,归根结底都是通过 new Function()的方式进行创建的。
super.x
2019/04/12
8230
【前端基础进阶】JS原型、原型链、对象详解
【JS】479- 又见原型和原型链
在前端这块领域,原型与原型链是每一个前端er必须掌握的概念。我们多次在面试或者一些技术博客里面看见这个概念。由此可见,这个玩意对于前端来说有多重要。其实它本身理解起来不难,但是很多刚入行前端的同学,看到prototype、__proto__理解起来还是有点吃力,然后脑子里面就乱成一锅粥,就像我一样。但是这是很正常的事情,没什么大不了的,就像我们想要学会跑步,那么我们就必须先学会走路。任何事情都是有个过程的。所以现在就跟我一起来攻克这个难点吧。通过这篇文章你将掌握以下知识点:
pingan8787
2020/02/17
7170
JS面试必问-JS原型及原型链
在js中万物皆对象,对象可以说是重中之重了。每一个对象都拥有自己的属性。但是在这个世界中有很多东西都是相似的,可以归为一类,他们有共同的方法和属性。不可能让每一个对象都定义一个属性吧。那样太消耗内存了。所以,在js中怎么才能让多个对象共享一个或多个方法呢?原型的出现就是为了解决这个问题。
用户10106350
2022/10/26
1.8K0
JS面试必问-JS原型及原型链
吊打前端专栏 | 吊打JavaScript之从原型到原型链
每个函数都有一个 prototype 原型属性,这个属性它是一个指针,指向一个对象,而这个对象的用途是可以由特定类型的所有实例共享的属性和方法。则这个 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。
达达前端
2022/04/13
3560
吊打前端专栏 | 吊打JavaScript之从原型到原型链
JavaScript进阶--原型、原型链、闭包
在JavaScript中,每个函数 都有一个prototype属性,当一个函数被用作构造函数来创建实例时,这个函数的prototype属性值会被作为原型赋值给对象实例(也就是设置 实例的__proto__属性),也就是说,所有实例的原型引用的是函数的prototype属性。
软件架构师Michael
2022/08/09
6020
相关推荐
关于javascript的原型和原型链,看我就够了(三)
更多 >
LV.0
这个人很懒,什么都没有留下~
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验