Skip to main content

moregeek program

prototype是原型对象,那__proto__又是什么呢,原型深度解析_流指斜阳的博客-多极客编程

做过前端的都知道,两个必会的知识就是原型和原型链,如果有人问你,原型是什么?你是不是回答对象中都有一个默认的属性叫prototype,指向的就是原型。如果再追问你,那原型链是什么呢?你是不是回答如果在当前对象中找不到某个属性,就会去父对象的原型中去查找,这样一层一层的向上查找,一直到顶层null,这样形成的一条链就叫原型链。

prototype是原型对象,那__proto__又是什么呢,原型深度解析_原型对象

那到底什么是原型与原型链呢,先不说上面的回答对不对,这样的答案肯定是小伙伴们听别人说的之后,似懂非懂的记在了心里,并没有去验证,只是在工作中觉得确实好像是这么回事。

下面我们来一起看一下,相信看完之后你能对它们有更深的了解。

prototype

所有的js对象都会继承原型对象上面的属性和方法。其中原型对象就是prototype所指向的那个对象。我们一般叫它原型属性。

而原型属性,是只有函数才有的,或者说是只有typeof为function的对象才有的(箭头函数除外),在js里面,函数可以作为构造函数使用,可以生成自己对应的实例化对象,而它所生成的这些实例,就会共享这个函数的原型对象里面的属性和方法,也就是我们所说的继承。

从下面的例子我们来看一下:

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_02

我们可以看到,不同的函数都拥有自己的原型属性,而非函数不具有prototype属性,因此返回undefined,其中Symbol不是构造函数,但是本身具有原型属性,箭头函数也不是构造函数,但其本身并无原型属性。

对于字符串、数值、布尔类型与其对应的构造函数也是一样的:

prototype是原型对象,那__proto__又是什么呢,原型深度解析_原型链_03

上面的输出是否有跟你想的不一样的呢?

我们通过一个简单的例子来理解一下原型对象:

function PaperNovel(author, title, date, pages, content) {
this.author = author
this.title = title
this.date = date
this.pages = pages
this.content = content
}
PaperNovel.prototype.medium = "纸质"
PaperNovel.prototype.category = "小说"
PaperNovel.prototype.wordsNum = function () {
return this.content.length
}
let 三国演义 = new PaperNovel("罗贯中", "三国演义", "2022-07-31", "327", "东汉末年,皇帝昏聩,宦官专权,民不聊生。爆发了大型农民起义——黄巾起义。乱世之中,一代英雄人物竞相涌现。")
let 西游记 = new PaperNovel("吴承恩", "西游记", "2022-07-30", "465", "东胜神州傲来国海边有一花果山,山顶一石,受日月精华,产下一个石猴。石猴在花果山做了众猴之王,为求长生,出海求仙,在西牛贺州拜菩提祖师为师。")
console.log(三国演义.author)//罗贯中
console.log(三国演义.medium)//纸质
console.log(西游记.author)//吴承恩
console.log(西游记.medium)//纸质
console.log(西游记.wordsNum())//69

我们定义了函数PaperNovel,当它被当做构造函数来调用的时候,实例化了两个对象:三国演义和西游记。其中author属于实例属性,不同的实例拥有各自对应的值,medium属于原型属性,各个实例之间共用这个值。

用图形来表述上面的关系的话,大概长成这样:

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_04

构造函数与实例

其中的author、title、date、pages、content属于实例属性,每个实例对象都会有自己的实例属性值,存储在当前的实例对象中。medium、category、wordsNum属于原型属性,每个实例对象都共有这些原型属性,存储在构造函数的原型中,实例对象只保存一个对它们的引用。

因此原型中的属性改变的时候,所有的实例对象都会受到影响,请看如下结果:

console.log(三国演义.category)//小说
console.log(西游记.category)//小说
PaperNovel.prototype.category = "散文"
console.log(三国演义.category)//散文
console.log(西游记.category)//散文
三国演义.__proto__.category = "科普"
console.log(西游记.category)//科普

在实例对象访问一个值的时候,会先在实例属性中查找,如果没有找到,那么将会去它对应的构造函数的原型中去查找,还是以上面的代码为例,我们来看一下效果:

console.log(三国演义.category)//小说
三国演义.category = "散文"
console.log(三国演义.category)//散文
console.log(三国演义.__proto__.category)//小说
console.log(西游记.category)//小说

至此我们知道了,prototype是函数的原型对象,当函数被当做构造函数调用的时候,区别于实例属性,原型属性会被所有实例所共用,实现的方式就是所有实例对象保存一个指向该原型对象的指针。

原型属性大概先介绍这些。

proto

我们上文中说到,实例对象没有prototype属性,只有构造函数才有,实例对象会有一个指针来指向构造函数的原型对象,而这个指针就是用__proto__来存储和表示的。

也就是说实例对象的__proto__指向它的构造函数的原型。

知道了这一点,我们很容易得出:

prototype是原型对象,那__proto__又是什么呢,原型深度解析_实例化_05

到这就为止了吗?,我们还可以深挖一下,其实看到这里,有眼尖的小伙伴可能会问:PaperNovel.prototype也是一个对象,那它有没有__proto__属性,有的话指向哪里呢?

很好,提出这个问题说明你的求知欲很强,我们对待技术就是要充满好奇心。给你鼓掌[手动鼓掌]。

我们先来简单思考一下,PaperNovel.prototype是一个对象,它应该也是被实例化出来的,那么它应该有__proto__属性,并且指向它的构造函数的原型。

好了,对象的构造函数是什么呢?没错!就是Object,已经很清晰了,我们来下是否像你想的样子:

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_06

这个时候小伙伴们又会问了:Object.prototype也是一个对象,它有__proto__属性吗?有的话指向哪里呢?

我们直接来看:

prototype是原型对象,那__proto__又是什么呢,原型深度解析_实例化_07

呀!我们发现虽然有这个属性,但是它为null了,其实这也就是我们平常所说的,循着原型链查找,一直查找到null为止,那么那个null是什么它又在哪呢?就是这个,已经到达了原型链顶端,发现是null,就不会再继续往上查找了。

至此我们知道了,对象的__proto__属性指向它的构造函数的原型,通过它可以把一系列的原型连接起来,我们在访问一个对象的属性的时候,如果当前对象不存在这个实例属性,那么它就会去从它的__proto__指向的对象中去查找,层层往上,一直到null。

我们可以通过一个示例来感受一下它的魔力:

prototype是原型对象,那__proto__又是什么呢,原型深度解析_原型链_08

__proto__先大概介绍这些。

原型与原型链扩展

相信看到这里,大家已经知道了什么是原型以及原型的存在形式,也知道了__proto__是做什么用的,它是如何把各个原型对象连接起来的,也能明白了对象属性访问时对于原型链的查找机制。

下面我们来扩展一些内容,理解既有一些构造函数他们之间的关系。

如下示例:

① 数组的__proto__指向构造函数Array的原型

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_09

② 函数的原型对象的__proto__指向构造函数Object的原型

prototype是原型对象,那__proto__又是什么呢,原型深度解析_原型链_10

③ 函数的__proto__指向构造函数Function的原型

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_11

④ Function的__proto__指向构造函数Function(也就是它自己)的原型

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_12

⑤ Object的__proto__指向Function的原型

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_13

⑥ Function的原型的__proto__指向Object的原型

prototype是原型对象,那__proto__又是什么呢,原型深度解析_构造函数_14

我们一切操作基本都离不开上面的这些关联关系,只要了解prototype和__proto__,相信你会很快明白上面的这些行为。

主要总结为两点:

  1. prototype为函数特有属性,只有函数才具有原型属性,以供继承,对象也能继承主要是通过__proto__的连接
  2. __proto__表示一个对象指向它的构造函数的原型的指针,也就是说一个对象的__proto__指向它的构造函数的原型

好好理解一下上面的这两点内容,结合之前的例子,基本就对原型与原型链有了深刻的认识,相信你也能很好的回答了文章开始的问题。

常用方法解析

① Object.prototype.toString.call

相信你看到这种形式的写法不会感到陌生,这是调用Object原型上面的toString方法,通过call指定了它的执行作用域,也就是改变了this的指向。

通过上面的学习我们知道Object的原型,其实就是它的实例化对象的__proto__,因此我们换一种方式书写也是可以的:

//注意,下面的结果是false
//因此这两个方法转化的字符串规则是不一样的
//我们在例子中使用的是Object.prototype.toString
Object.prototype.toString === Object.toString
//通过上面的讲解,我们知道下面的结果是true
//因此这两种写法是等价的
Object.prototype.toString === ({}).__proto__.toString

Array.prototype.slice.call

//下面的结果为true
//因此这两种写法是等价的
Array.prototype.slice === [].__proto__.slice

总结

理解原型与原型链,对我们书写代码、构造高级函数、理解深层次的执行机制等,都是非常有帮助的,利用这些属性和它们的特点能创造出很多优雅的代码。

现在是不是对于原型和原型链有了更好的理解了呢?对于面试官给出的问题也能不慌不忙的解释清楚了呢?

希望本文对你有所帮助,不胜欣慰。

©著作权归作者所有:来自51CTO博客作者流指斜阳的原创作品,请联系作者获取转载授权,否则将追究法律责任
prototype是原型对象,那__proto__又是什么呢,原型深度解析
https://blog.51cto.com/u_15685951/5577333

js的众多小技巧之高傲的正则表达式(regexp):你真能行_流指斜阳的博客-多极客编程

我们对正则表达式并不感到陌生,平时的工作中一般都会遇到使用它们的场景,即使自己没有用到,在一些插件库或者依赖包里面也经常能看到正则表达式的身影。你在平时写代码的过程中使用的多吗?是选择尽量避免使用然后找其它的方式实现,还是直接找一些现成的实现直接拿过来用呢?//vue中匹配模板里面插值的正则var defaultTagRE = /{{((?:.|\r?\n)+?)}}/g;如果你对正则表达式望而却

typescript对于duck类型和模块命名空间的应用实战_mb625ae00326074的博客-多极客编程

@[toc] 一.TypeScript 鸭子类型 Duck类型是一种动态类型和多态形式。在这种风格中,对象的有效语义不是通过从特定类继承或实现特定接口来确定的,而是通过“当前方法和属性的集合”来确定的。 var object_name = { key1: "value1", // 标量 key2: "value", key3: function() {

#yyds干货盘点# 前端歌谣的刷题之路-第二十五题-数据类型转换_前端歌谣的博客-多极客编程

前言我是歌谣 我有个兄弟 巅峰的时候排名c站总榜19 叫前端小歌谣 曾经我花了三年的时间创作了他 现在我要用五年的时间超越他 今天又是接近兄弟的一天人生难免坎坷 大不了从头再来 歌谣的意志是永恒的 放弃很容易 但是坚持一定很酷 本题目源自于牛客网 微信公众号前端小歌谣题目请补全JavaScript函数,要求以字符串的形式返回两个数字参数的拼接结果。 示例: 1. _splice(223,233)

js中的重载——如何实现一个类似这样的功能,我也想玩玩_流指斜阳的博客-多极客编程

我们先来看一下效果,然后再慢慢分析,如下面一样定义了一个sum函数,也可说是定义了多个sum函数,调用的时候只需使用sum即可,程序会自动根据它们传参的不同调用不同的sum函数,从而产生了不同的结果。在其他语言中,我们一般都听说过重载的概念,对于一个方法,如果我们期望传入不同的参数类型,或者传入不同的参数个数,甚至传入参数的顺序不一样,而去执行不同的处理代码,以返回相应的结果,那么他们的方法名是可

f12-开发者工具常用操作与使用说明之源代码sources_流指斜阳的博客-多极客编程

我们先来一个小示例给大家看一下:大家能看出来上面的代码为什么输出的是17吗?按照正常的输出应该是7才对呀!如果你对此有疑惑,说明你在平时调试代码的时候已经浪费了很多时间了哦。今天就带大家来探索一下开发者工具中源代码的使用,这里提供了非常方便并且有用的功能。大家可以借助它的一些神奇的能力,来解决那些困扰我们的问题,希望你能够掌握并熟练的使用它们,下面就让我们来一起看看它的魅力吧!概要说明源代码面板从

面试官:工作两年了,这么简单的算法题你都不会?_安东尼漫长的技术岁月的博客-多极客编程

工友们,看到题目,先别愤怒,第一小节,咱们先铺垫讲一个 “面试问题应该从问【是什么】到问【为什么】” 的逻辑。🤔面试问题从【是什么】到【为什么】关于前端是否有必要面试算法,本瓜看过很多争论,相信你也一样听过不少,诸如此类:正方:“工作中又用不到,有必要吗?”、“一般后端都会把数据格式处理好”、“算法跟面试背八股文一样,不能测出真实能力”。。。反方:“面试内卷,拉开层次,算法必会”、“数据结构和算法

js的众多小技巧之高傲的正则表达式(regexp):你真能行_流指斜阳的博客-多极客编程

我们对正则表达式并不感到陌生,平时的工作中一般都会遇到使用它们的场景,即使自己没有用到,在一些插件库或者依赖包里面也经常能看到正则表达式的身影。你在平时写代码的过程中使用的多吗?是选择尽量避免使用然后找其它的方式实现,还是直接找一些现成的实现直接拿过来用呢?//vue中匹配模板里面插值的正则var defaultTagRE = /{{((?:.|\r?\n)+?)}}/g;如果你对正则表达式望而却

字节跳动基于clickhouse优化实践之upsert_字节跳动数据平台的博客-多极客编程

更多技术交流、求职机会、试用福利,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群相信大家都对大名鼎鼎的ClickHouse有一定的了解,它强大的数据分析性能让人印象深刻。但在字节大量生产使用中,发现了ClickHouse依然存在了一定的限制。例如:缺少完整的upsert和delete操作多表关联查询能力弱集群规模较大时可用性下降(对字节尤其如此)没有资源隔离能力因此,我们决定将Cli

【存储数据恢复】netapp存储误删除文件夹的数据恢复案例_sun的博客-多极客编程

存储服务器数据恢复环境: 某公司一台netAPP某型号存储。 存储服务器故障&分析: 管理员在工作中误删除了NetApp存储中某重要文件夹,刚开始没有怎么在意这个事情,后来发现问题严重了,管理员紧急寻找一家数据恢复公司上门进行存储的数据恢复,从管理员找到我们进行数据恢复到删除数据这中间已经间隔了几个月的时间。 我们数据恢复中心安排数据恢复工程师前往现场对NetApp存储进行初检。虽然数据已

js中的重载——如何实现一个类似这样的功能,我也想玩玩_流指斜阳的博客-多极客编程

我们先来看一下效果,然后再慢慢分析,如下面一样定义了一个sum函数,也可说是定义了多个sum函数,调用的时候只需使用sum即可,程序会自动根据它们传参的不同调用不同的sum函数,从而产生了不同的结果。在其他语言中,我们一般都听说过重载的概念,对于一个方法,如果我们期望传入不同的参数类型,或者传入不同的参数个数,甚至传入参数的顺序不一样,而去执行不同的处理代码,以返回相应的结果,那么他们的方法名是可

技术实践 | 如何给nvme做raid_沃趣qfusion的博客-多极客编程

1 传统RAID硬盘阵列(Redundant Arrays of Independent Disks,RAID),是由很多块独立的硬盘,组合成一个容量巨大的硬盘组。1.1  阵列卡现如今,基本所有的服务器都通过RAID卡对硬盘进行管理。硬盘插入到服务器的槽位后由背板的线缆连接RAID卡进行一个统一的管理,然后提供给操作系统,操作系统不能直接使用硬盘。 以下为一些常见的RAID级别:RAID 0将数

f12-开发者工具常用操作与使用说明之源代码sources_流指斜阳的博客-多极客编程

我们先来一个小示例给大家看一下:大家能看出来上面的代码为什么输出的是17吗?按照正常的输出应该是7才对呀!如果你对此有疑惑,说明你在平时调试代码的时候已经浪费了很多时间了哦。今天就带大家来探索一下开发者工具中源代码的使用,这里提供了非常方便并且有用的功能。大家可以借助它的一些神奇的能力,来解决那些困扰我们的问题,希望你能够掌握并熟练的使用它们,下面就让我们来一起看看它的魅力吧!概要说明源代码面板从