关于javascript:原型链构造函数链关于prototypeprotoconstructor我终于搞懂了

20次阅读

共计 6695 个字符,预计需要花费 17 分钟才能阅读完成。

放两个链接,强烈建议去读一读:

文一:JavaScript 原型精华 #一篇就够系列
文二:用本人的形式(图)了解 constructor、prototype、__proto__和原型链

在文一中我理解到为什么我始终看不懂各路大神的原型图,晓得了原来要分成原型链、构造函数链两个来了解。
文二则帮我彻底了解了什么是构造函数链什么是原型链。

上面讲讲我所了解的原型链,看看我学成啥样了。如果对大家有所帮忙就再好不过了!

初识三个属性

constructor

借鉴文二的程序,先从 constructor 讲起吧。

这个单词在不同场景下有不同的指代含意。
作为一个名词,它能够指「构造函数」这个函数类型。
在原型链这个场景下,它作为对象的一个属性名呈现。

构造函数 constructor

construtor谷歌翻译为 构造函数,所以会在有些场景中,他就指构造函数。

在 ES6 标准中,是这么形容 constructor 的:

4.3.4 constructor

function object that creates and initializes objects

翻译:创立和初始化对象的函数对象

在这个语境中,construtor指的就是 构造函数

对象的属性 constructor

同时 constructor 也是 对象的属性

置信咱们想晓得的也不是构造函数相干的,更多想要理清作为对象属性的 constructor 是什么。

对象的属性?

比如说一个函数对象 fun,咱们能在他的属性外面发现起码三个跟constructor 相干的属性:

let Fun = function(){this.a = 'a'}

Fun.constructor // ƒ Function() { [native code] }
Fun.__proto__.constructor // ƒ Function() { [native code] }
Fun.prototype.constructor // ƒ ƒ (){this.a = 'a'}

let afun = new Fun

afun.constructor // ƒ (){this.a = 'a'}
afun.__proto__.constructor // ƒ (){this.a = 'a'}

不用晓得他们是做什么的,不用晓得为什么输入这些,也不用晓得 __proto__prototype是什么,前面都会讲的,当初理解 constructor 会在这三种中央呈现即可。

constructor 是做什么的呢?

从他的直译上也可见,该属性与构造函数相干,
constructor 作为对象的属性名,用处则是 指向该对象的构造函数
所以 fun.constructor 指向的就是 fun 的构造函数:JS 内置对象Functionƒ Function() { [native code] }

prototype

咱们看标准中对 prototype 的定义(看不懂没关系,前面细讲):

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.

翻译:为其余对象提供共享属性的对象
留神:当构造函数创立一个对象的时候,这个对象会隐式援用这个构造函数的 prototype 属性中的属性,以便于解析属性的援用

我来解释一下吧:

首先要晓得的一点是,`prototype` 只存在于函数对象中,下文说起【`prototype`】时代表的意思就是【构造函数的 `prototype` 属性】。

先看第一句:

prototype 是为 其余对象 提供 共享属性 对象

prototype 是对象

这句话略去定语,剩下的就是:【prototype 是对象】。
prototype属性中寄存一个对象,或者说 prototype 属性是一个对象。
对象中有很多属性。
模式大略相似这样(当然不会是这些内容,这里仅仅打个样儿):

Fun.prototype = {a: function(){console.log('a') }, // 寄存办法
    b: function(){ console.log('b') },
    c: 'c' // 寄存具体值
    ...
}

再说这句话外面的【其余对象】是哪些对象:
是通过构造函数 Fun 创立进去的对象,
比方 let obj1 = new FunFun的实例 obj1 就是这个【其余对象】。

提供 共享属性 是怎么回事?

通过 new Fun 能够发明实例 obj1、天然也能够发明obj2、obj3...
这是一个陈词滥调的问题,看一下这个场景:

function Fun(name){
    this.name = name;
    this.sayHi = function(){ console.log('hi~ i am' + this.name) }
}
let obj1 = new Fun('obj1Name')
let obj2 = new Fun('obj2Name')
let obj3 = new Fun('obj3Name')

如果依照下面这种形式为每一个实例增加 name 属性,那么每创立一个实例,都会创立一个 sayHi 办法。
下面创立了三个 Fun 实例,每个实例都创立一个内存空间保留sayHi,总共三个。


这仅仅三个实例,咱们创立就创立了,但如果有成千盈百个实例呢?这样就会很占内存。
怎么说呢,很没必要,很吃力。

那要怎么优化呢?
咱们留神到,这三个实例的构造函数都是 Fun,那有没有方法能在Fun 上创立一个内存空间保留sayHi,而后供它的实例们调用呢?

既然都这么问了,当然是有方法的,这时候就是 prototype 闪亮退场了!

咱们下面曾经晓得 prototype 对象中保留了一些属性、办法。
第一步咱们能够先把 sayHi 办法保留在 Fun.prototype 中。
第二步创立实例。
代码如下:

// 初始化 Fun
function Fun(name){this.name = name;}
// 将办法保留在 Fun 原型对象中
Fun.prototype.sayHi = function(){ console.log('hi~ i am' + this.name) }
// 创立实例
let obj1 = new Fun('obj1Name')
let obj2 = new Fun('obj2Name')
let obj3 = new Fun('obj3Name')

sayHi 保留在 Fun.prototype 中,则只在内存中创立了一次 sayHi 办法,
然而办法创立在 Fun.prototype 上,实例们该怎么调用呢?
是否应该有一个属性保留一下 Fun.prototype 的地址呢(保留 Fun.prototype 的援用)?
还是那句话,都这样问了,当然是要,不然找不到 Fun.prototype,谈何找到保留在Fun.prototypesayHi办法呢。

【一个属性】是什么属性

讲到这儿,再说说 NOTE 中内容

留神:当构造函数创立一个对象的时候,这个对象会隐式援用这个构造函数的 prototype 属性中的属性,以便于解析属性的援用

当构造函数 Fun 创立了一个实例对象 obj1 时,obj1中会有一个属性指向 Fun.prototypeobj1 也能够调用 Fun.prototype 中保留的属性、办法等
为什么说是隐式?
因为 obj1 中指向 Fun.prototype 的那个属性按理来讲不应该被开发人员获取到,应该是标准中外部实现的一个属性,然而各个浏览器都不谋而合实现了这个属性,能够被咱们获取到,这个属性就是__proto__.

对于【__proto__】和【prototype】翻译成中文是什么,可能大家都是随性而为,没有一个对立标准称说它。比拟多的说法是:称【__proto__】为【隐式原型】称【prototype】为【原型对象】

obj1.__proto__ === Fun.prototype,这就是 obj1 中隐式原型属性指向其构造函数 Fun 的原型对象。

看到这儿是不是有点似曾相识呢,这个 __proto__ 就是咱们下面讲的要指向 Fun.prototype 的那个「一个属性」呀!

既如此,下面那张图稍作批改:

__proto__

看到这儿应该对于 obj.__proto__ 是什么有所理解了:
我所了解的是:obj.__proto__保留了 obj 的构造函数prototype 属性 (原型对象)的 地址(援用)

这么一说,对于这个属性,反而没什么好说的了。

constructor 进阶

说是进阶,也能够叫做补充,下面也仅仅是阐明了一下三个属性是什么罢了。
对于原型链、对于构造函数链,对于原型对象的 constructor、隐式原型的 constructor 等等都没有提到。
然而不焦急,就要聊到这里了。

fun 的 constructor 真的是 constructor 属性吗

参考自:参考链接 – 文二的【## 四、真正的 constructor 属性藏在哪】

先看跟咱们下面说到的 sayHi 相似的问题:

function Fun(name){
    this.name = name;
    this.sayHi = ...
    this.constructor = function(){ return Fun};
}
let obj1 = new Fun('obj1Name')
...

说到底 constructor 也是对象的一个属性,存储着返回其构造函数的办法,大略内容可能相似下面写的那样。

如果创立每个实例的时候都创立一遍 constructor 函数,还是跟 sayHi 一开始的问题一样:占内存!没必要!吃力!

那怎么办呢?sayHi怎么解决的呢?放到构造函数的原型对象 prototype 外面!

// 初始化 Fun
function Fun(name){this.name = name;}
// 将办法保留在 Fun 原型对象中
Fun.prototype.sayHi = ...
Fun.prototype.constructor = function(){ return Fun};
// 创立实例
let obj1 = new Fun('obj1Name')
...

obj1.constructor 是个什么货色呢?
我认为跟 obj1.__proto__ 相似,也是保留地址(援用)的,
不过 obj1.constructor 保留的援用是 Fun.prototype.constructor 的地址(援用)!

Fun.prototype.__proto__.constructor

prototype 是一个对象是吧,
对象都有隐式原型 __proto__ 这点也无可否认吧。

好的,那问题来了,Fun.prototype.__proto__.constructor输入的是什么呢?

不要慌,咱们一点点剖析。
Fun.prototype是一个对象吧,咱们把它看作一个整体:PrototypeFun
即:

let FunPrototype = Fun.prototype

那问题就变成了 FunPrototype.__proto__.constructor 输入什么?
想一下上文说过的:

obj.__proto__保留了 obj 的构造函数prototype 属性 (原型对象)的 地址(援用)

翻译成人话就是:obj.__proto__ === obj 的构造函数.prototype
那就要找到 FunPrototype 的构造函数了


中途插播:八一八 Fun 的原型对象「prototype」的构造函数

prototype是 JS 的内置原型对象,根据在这里:Table 7.

再来看一段跟 prototype 相干的标准(能够不必看翻译,有点艰涩难懂,一会我会翻译成人话):

17 ECMAScript Standard Built-in Objects

Unless otherwise specified every built-in prototype object has the Object prototype object, which is the initial value of the expression Object.prototype (19.1.3), as the value of its [[Prototype]] internal slot, except the Object prototype object itself.

翻译:17 ECMAScript 规范内置对象

除非另有阐明,否则每个内置原型对象都有 Object 原型对象,它是表达式 Object.prototype (19.1.3) 的初始值,作为其 [[Prototype]] 外部槽的值,除了 Object 原型对象自身。

翻译成人话是什么呢?

每个 prototype 原型对象都有(保留着)一个 Object 的原型对象「prototype」,这个原型对象初始值就是 Object.prototype 的值 (说的真啰嗦),那这个原型对象保留在哪里呢?保留在 [[Prototype]] 外面(就是__proto__

翻译成人话就是:任何一个原型对象fun.prototype.__proto__ === Object.prototype


ok👌,话题回到 Fun.prototype.__proto__.constructor

既然任何一个原型对象的隐式原型都指向 Object.prototype
那么 FunPrototype.__proto__ === Object.prototype 就是成立的!

FunPrototype.__proto__.constructor返回什么呢?
返回 FunPrototype 的构造函数!

它的构造函数是什么?
内置对象 Object
为什么呢?最开始咱们就晓得 prototype 是一个对象,Fun.prototype天然也是一个对象 (废话) 对象天然由 Object 创立的。

构造函数链

构造函数链的止境是 Function

构造函数链的底层逻辑是:
实例.__proto__ === 实例的构造函数.prototype
其中,下图,
obj2 是(Fun 的)实例,Fun 是(obj2 的)构造函数,
Fun 是(Function 的)实例,Function 是(Fun 的)构造函数。

咱们能够看到有两条构造函数链:
一条是:obj2 – Fun – Function
另一条:Function – Function
如果要问为什么 Function-Function,只能说是规定,设计之初,总要能跳出构造函数的链条,那就让它的止境是 Function 好了。

加上 Object
咱们能够看到有三条构造函数链:
一条是:obj2 – Fun – Function
另一条:Function – Function
第三条:Object – Function

Object 是内置函数,new Object 是对象(通过 new 关键字返回的是一个对象)」

原型链

原型链的底层逻辑是从最底下的实例能始终追溯到 null:
实例.__proto__.__proto__....__proto__ === null

构造函数链就是这样的链,如果真的要像这样写出一个链条,只能是:` 实例.__proto__.constructor.__proto__.constructor...__proto__.constructor === Function.prototype`
当然,这个链条感觉写进去也没什么意义。解释一下:【实例.__proto__】指向其【构造函数 1.prototype】【构造函数 1.prototype】外面有一个属性【constauctor】指向构造函数 1 自身
那么【构造函数 1.__proto__】示意进去就是【实例.__proto__.constructor.__proto__】

原型链的起点是 null

加上Function 就是:

原型链、构造函数链 图片比照

【留神箭头的方向】

补充解释:
构造函数链跟原型链要离开了解,我以前就是不晓得为什么一个函数的实例既继承了 Object 的原型办法,又是 Function 的实例。
应该要离开了解,而后合在一起才对。
obj1、obj2 -> Fun -> Function 这是构造函数链。
obj1、obj2 -> Fun -> Object,是原型链。
如果在 Fun.prototype 上增加‘aaFun’办法,
在 Function.prototype 上增加‘aaFunction’办法,
在 Object.prototype 上增加‘aaObject’办法,
那么 obj1、obj2 能够继承(不晓得说继承对不对)aaFun、aaObject 办法,
然而运行 aaFunction 就会返回 undefined 办法,因为这个 aaFunction 办法不在原型链!

正文完
 0