1

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

 1 year ago
source link: https://blog.51cto.com/u_15685951/5577333
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

做过前端的都知道,两个必会的知识就是原型和原型链,如果有人问你,原型是什么?你是不是回答对象中都有一个默认的属性叫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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK