原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!

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

文一: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,总共三个。

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第1张图片
这仅仅三个实例,我们创建就创建了,但如果有成百上千个实例呢?这样就会很占内存。
怎么说呢,很没必要,很费劲。

那要怎么优化呢?
我们注意到,这三个实例的构造函数都是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方法呢。

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第2张图片

【一个属性】是什么属性

讲到这儿,再说说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的那个「一个属性」呀!

既如此,上面那张图稍作修改:

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第3张图片

__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的地址(引用)!

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第4张图片

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好了。
原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第5张图片

加上 Object
我们可以看到有三条构造函数链:
一条是:obj2 - Fun - Function
另一条:Function - Function
第三条:Object - Function

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

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第6张图片

原型链

原型链的底层逻辑是从最底下的实例能一直追溯到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

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第7张图片

加上Function 就是:

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第8张图片

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

【 注意箭头的方向 】

原型链、构造函数链!关于prototype、__proto__、constructor我终于搞懂了!_第9张图片

补充解释:
构造函数链跟原型链要分开理解,我以前就是不知道为什么一个函数的实例既继承了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方法不在原型链!

你可能感兴趣的:(javascript前端)