《JavaScript》重学JS-细聊一下prototype__proto__与constructor(超详解版)
Posted Oliver尹
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《JavaScript》重学JS-细聊一下prototype__proto__与constructor(超详解版)相关的知识,希望对你有一定的参考价值。
求关注,求收藏,求点赞,非常感谢!你的每一个阅读都是我的力量源泉!
前言
最近在比对class以及将class编译成es5后的代码,看的是晕头转向,尤其在原型链这一块,发现之前的了解实在有些不够,因此决定重学prototype、__proto__与constructor三者;
我们知道不管是TypeScript还是javascript的class,归根结底都是基于prototype实现的,因此才将class称作语法糖,本文以我之重学视角,结合图解、示例、ECMAScript原文以及prototype、__proto__与constructor的历史,换个角度学习一下三者并记录分享;
耐心看完,你一定有所收获!
目标
希望通过本文能以另外一种角度重新学习JavaScript中的prototype、__proto__与constructor,毕竟这三个实在太重要,重要到可以说整个JavaScript是在这基础上实现的;
名词解释
- ECMA:欧洲计算机制造商协会(European Computer Manufacturers Association)的简称,是一家国际性会员制度的信息和电信标准组织。
- ECMA 国际:1994 年之后,由于 ECMA 组织的标准牵涉到很多其他国家,为了体现其国际性,更名为 Ecma 国际(Ecma International)。
- ECMAScript: 由 Ecma 国际在标准 ECMA-262 中定义的脚本语言规范。JavaScript 是 ECMA-262 标准的实现和扩展。
- ECMA-262:Ecma 国际的标准,都会以 Ecma-Number 命名,ECMA-262 就是 ECMA 262 号标准,具体就是指 ECMAScript 遵照的标准。1996 年 11 月,网景公司将 JavaScript 提交给 Ecma 国际进行标准化。ECMA-262 的第一个版本于 1997 年 6 月被 Ecma 国际采纳。
正文
在JavaScript中,万物皆对象!本以为是一句玩笑,没想到却是在陈述一个事实;
通过一个示例,来看下原型链到底干了什么,直接看一个例子:
function Foo() ...;
let f1 = new Foo();
这是一段我们实际项目中非常常用且基础的代码:new了一个实例,但在其背后的原型链则有点复杂
别晕,我们一步一步来慢慢分析这张图,分别拆解__proto__、prototype与constructor后逐步分析;
另外,先记住两个个知识点:
- __proto__和constructor是属性,因此它是对象特有的;
- prototype是JS在函数定义时自动创建的,它是函数特有的,但因为在JavaScript中函数也是一个对象,因此,它不仅有prototype也有__proto__和constructor;
为此,我们可以在chrome的控制台中验证这两点,如下图所示
定义了一个对象obj以及一个函数func:
- 打印obj.__proto__以及obj.constructor可以看出都是存在值的,而打印obj.prototype则是显示undefined;
- 打印func.__proto__和func.constructor、func.prototype则是三者都有值;
到这里,侧面也可以验证上面那句在JavaScript中,万物皆对象不是句戏言了;
proto
__proto__它的全称为:double underscore proto,其作用就干了一件事,关联对象的指向,换个通俗点的说法,就是当访问对象上的某个属性或方法时发现该属性或方法在当前这个对象上不存在,那么它就会沿着__proto__的指向到它的上级去找,直到最上级null,这个查找沿着的路径,我们称作为__原型链,并且你没看错,原型链的终点就是null…
举个实际点的例子:
const array=[];
array.push("oliver")
我们定义了一个变量array,为其赋值一个数组,此外我们并没有为它添加任何属性或方法,但是有没有想过我们为什么可以使用push这个方法?归其原因,就是因为__proto__,当我们使用push,JS发现array上不存在这个东西,因此它就沿着__proto__到它的上级也就是Array的原型上去找,如果Array的原型上也没有,那就沿着Array的__proto__的指向再去找,这么说能明白了吧;
另外,在查询这个__proto__的历史时发现一个比较有意思事,这个属性刚开始 并不是ECMAScript中定义的官方属性,它是各个浏览器厂商自己实现的私有属性,直到ES2015版本ECMA 国际将其纳入了ECMAScript规范,为此我特意去验证了早期的ECMAScript规范:
ECMA-262_5.1_edition_june_2011(前往下载:下载)
未搜索到结果
ECMA-262_6th_edition_june_2015(前往下载:下载)
搜索到结果
再往后又发现,这个属性又被web规范删除了,改用为:[[Prototype]] ,用 [[Prototype]] 完全替代了__proto__,具体可以查看MDN中关于__proto__的解释,但是到这里又发现虽然该属性被移除了,但是在chorme中仅仅就是将__proto__换了个变量名改为了[[Prototype]] ,其里子依旧是__proto__,某种程度上,我们可以将这两者视为等同,很神奇;
另外,由于__proto__这个属性已经被删除,实际开发中尽量不要使用__proto__修改指向了,可能存在未知风险;
扯远了,话题回到图解
图中可以得知,实例f1的__proto__指向的是构造函数的prototype,chrome中验证一下
定义了函数Foo,并且实例化了一个f1,通过Foo.prototype和f1.__proto__之间的全等判断,两者确实是同一个,因此这也就解释了,实例化后的f1上的那些方法都来自于哪,来自于构造函数的prototype,如果持有怀疑,那么我们再论证一下:
function Foo();
Foo.prototype.save=function()
const f1=new Foo()
// 判断方法来源,是否相等?
f1.save===Foo.prototype.save
根据我们上面的猜测,这肯定是相等的,如下图
小结
__proto__其作用就是修改指向,将父子级通过__proto__关联起来,实现了当如果子级上不存在某个属性或方法时,可以沿着__proto__到其父级上寻找,直至终点null,而__proto__的这条指向链条,就是原型链;
prototype
prototype,称作原型,这个属性一度让我非常困惑,不明白这么设计的意义是什么,直到我看到了阮一峰老师的这篇文章《Javascript继承机制的设计思想》才明白原来是想复杂了;
简单的说就是历史原因,设计之初的JavaScript仅仅为了解决简单的网页脚本交互,不需要太复杂、太沉重的语言代码结构设计,prototype的出现也仅仅是为了解决JS中没有继承这个概念的折中方案,它的目标仅仅是为了让构造函数与实例之间可以共享属性和方法;确实,去掉了继承的js在结构以及学习成本上远远低于Java等面向对象语言;
可以看下ECMA-262_6th_edition_june_2015上的原文描述(ECMA-262_6th_edition_june_2015文档可以这里下载:下载)
4.3.5
prototype
object that provides shared properties for other objects
NOTE
When a constructor creates an object, that object implicitly references the constructor’s prototype property for
the purpose of resolving property references. The constructor’s prototype property can be referenced by the
program expression constructor.prototype, and properties added to an object’s prototype are shared,
through inheritance, by all objects sharing the prototype. Alternatively, a new object may be created with an
explicitly specified prototype by using the **Object.create **built-in function.
到这里其实我们就明白,prototype存在的目的其实就只有一个:共享,共享属性和方法,比如:
function Person()
Person.prototype.getName=function()
// 实现代码
Person.prototype.getAge=function()
// 实现代码
const man = new Person();
const woman = new Person();
如果我们将方法写在原型上,那么由Pserson实例化而来的对象都可以继承到同一个方法,并且,一旦需求变更方法需要修改,那么由于prototype上的属性和方法是共享的,可以一次性修改掉所有实例上的属性和方法;
话题扯回来,prototype是JavaScript自动创建的,任何一个函数在创建的时候都会有默认生成一个prototype,它并不是构造函数独有的属性,而且,构造函数本身也是一个函数,我们要明白JavaScript中并不存在构造函数这一个规范性的名词,只是在用法上,我们区分了某些函数专门用来作实例化,某些函数仅用来做功能性处理;
其实在上面一小节__proto__中我们已经验证了prototype是共享属性或方法这一说,还是如下代码
function Foo();
Foo.prototype.save=function()
const f1=new Foo()
f1.save===Foo.prototype.save
由于是共享,那么在实例f1没有重写save这个方法之前,f1的save来自哪?当然是来自于Foo的prototype;
小结
prototype是JavaScript在创建函数时自动创建的,其作用就是为了将属性和方法共享,由于JS没有继承机制,那么需要被共享的属性和方法都会被放置在prototype中;
constructor
还是先看ECMA-262_6th_edition_june_2015上的原文描述吧(ECMA-262_6th_edition_june_2015文档可以这里下载:下载)
4.2.1 Objects
…Each constructor is a function that has a property named
"prototype" that is used to implement _prototype-based inheritance _ and shared properties. Objects are
created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date
object. Invoking a constructor without using new has consequences that depend on the constructor. For
example, Date() produces a string representation of the current date and time rather than an object
4.3.4
constructor
function object that creates and initializes objects
NOTE
The value of a constructor’s prototype property is a prototype object that is used to implement inheritance and
shared properties.
可能原文看的不是很明白,我的理解是:初始化的时候,将function的prototype指向function本身,另外contructor是prototype上的一个属性…,也就是说constructor的作用也是指向,而且是将prototype指向函数本身,迷惑中,这有啥用,规范中也没有说明白,后来经过度娘,谷歌等一系列的查证,几乎都是一个解释:
constructor其实没有什么特别大的用处,仅仅是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。
好家伙,也就是说和__proto__、prototype不一样,constructor在现代并没有真正意义上的作用,只是历史遗产,那就直接回到图例吧,分析一下正常情况下constructor的指向
从图例可以看出,prototype指向函数本身,验证一下
没问题,Function.prototype的constructor确实指向Function本身,但是,到这里可能有部分小伙伴会有疑问,prototype上有contructors属性可以理解,这是JavaScript生成的,那为什么图中f1,Foo()这些也有constructor,这里的属性哪来的?
你没猜错,是由于__proto__继承来的,验证一下
完全相等,没问题,可是图例中还有一个非常特殊的Function(),它的contructor为什么会指向本身,这其实是ECMA-262_6th_edition_june_2015规定的,姑且可以认为是规范吧
19.2.1 The Function Constructor
The Function constructor is the %Function% intrinsic object and the initial value of the Function property of
the global object. When Functionis called as a function rather than as a constructor, it creates and initializes
a new Function object. Thus the function call Function(…) is equivalent to the object creation expression new
Function(…) with the same arguments.
The Function constructor is designed to be subclassable. It may be used as the value of an extends clause
of a class definition. Subclass constructors that intend to inherit the specified Function behaviour must
include a super call to the Function constructor to create and initialize a subclass instances with the internal
slots necessary for built-in function behaviour. All ECMAScript syntactic forms for defining function objects
create instances of Function. There is no syntactic means to create instances of Function subclasses
except for the built-in Generator Function subclass.
小结
JavaScript语言设计的历史遗留物,初始化的时候,将function的prototype指向function本身,另外contructor是prototype上的一个属性;
参考文档
CSDN参考文章:《帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)》;文中的图例就是来自于这篇博文,写博文时为画了一部分之后搜到这篇文章发现的,画的比我画的更好更全…跟大佬确认过可以借用了,非常感谢
MDN:《mozilla.org》;
ECMA:https://www.ecma-international.org/;
ECMA-262_6th_edition_june_2015:https://download.csdn.net/download/zy21131437/74397294
总结
- __proto__和constructor是属性,因此它是对象特有的;
- prototype是函数特有的,但因为在JavaScript中函数也是一个对象,因此,它不仅有prototype也有__proto__和constructor;
- __proto__其作用就是修改指向,将父子级通过__proto__关联起来,实现了当如果子级上不存在某个属性或方法时,可以沿着__proto__到其父级上寻找,直至终点null,而__proto__的这条指向链条,就是原型链;
- prototype是JavaScript在创建函数时自动创建的,其作用就是为了将属性和方法共享,由于JS没有继承机制,那么需要被共享的属性和方法都会被放置在prototype中;
- JavaScript语言设计的历史遗留物,初始化的时候,将function的prototype指向function本身,另外contructor是prototype上的一个属性;
以上是关于《JavaScript》重学JS-细聊一下prototype__proto__与constructor(超详解版)的主要内容,如果未能解决你的问题,请参考以下文章