今天给大家分享一篇关于在 JS 中非常不好懂理解,而且面试时候还经常问到的关于原型和原型链的知识。
上面这个张图各位看官是不是觉得很眼熟?可能第一次看到时候会觉得很懵逼,心想这是什么玩意呀?这么多线该怎么看呀?其实我第一次看到时候也很懵逼的,百思不得其解,当后面慢慢理解了一些简单原理后,在回头来看,其实也没有那么 难。那现各位观众姥爷们就跟着我这只菜鸟一起用最简单,朴素的语言,慢慢去分析,慢慢去理解吧,相信自己也是可以学会的!
1构造函数和原型对象之间的关系
收集的前端面试题库
1.1 认识原型对象:
当我们使用function
关键字来创建一个函数的时候,其实内存中自动创建一个包含prototype
属性的对象,这个属性对象指向的就是该构造函数的原型对象。
function Foo() { } console.log(Foo.prototype);//constructor: ƒ Foo()
所以:构造函数和原型对象之间的关系图解如下:
2函数实例与原型对象之间的关系
2.1:认识函数实例
__proto__
是每一个除null
外的 JavaScript 对象都具有的一个属性,它指向该对象构造函数的原型对象
function Foo() { } var fo = new Foo();
既然prototype
和__proto__
指向该对象构造函数的原型对象,那么prototype
和__proto__
是什么关系呢?是不是相等?下面我们就开始进行论证
console.log(fo.__proto__ === Foo.prototype); //true
可以看到打印 fo.__proto__
=== Foo.prototype
返回true
可能对于爱学习的同学可能又要问了,他们两个为啥相等?可以演示下过程吗,好的 不急,看我下面给你慢慢分解。我们可以看到fo
对象是new
出来的,那么同学们好好想想new
干了些什么?其实new
做了以下的几件事:
2.2:函数的 new 主要是做了以下几个步骤:
- 在内存中创建了一个空对象;
- 这个对象内部[[prototype]]属性会被赋值为该类的 prototype 属性;
- 构造函数中的 this,会指向创建出来的新对象;
- 执行构造函数中的代码
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
下面用伪代码演示下
function Foo() { } var fo = new Foo(); console.log(fo.__proto__ === Foo.prototype); //true 下面是模拟数据 function Foo() { var moni={} moni.__proto__=Foo.prototype//这一步非常重要 this=moni ... return moni } var fo=Foo()
可以看到上面伪代码中 moni.__proto__=Foo.prototype
其实这个可以发现moni.__proto__
是指向的是Foo.prototype
,所以我们可以知道moni.__proto__
指向的内存地址就是和Foo.prototype
指向的是同一个内存地址。
既: fo.__proto__ === Foo.prototype
是true
既然 Foo.prototype
指向了一个原型对象
那么 fo.__proto__
也是指向 Foo.prototype
指向的那个原型对象
可以在看下面的代码案列:
function Foo() { } var fo = new Foo(); var f1 = new Foo(); console.log(fo.__proto__ === Foo.prototype); //true console.log(f1.__proto__ === Foo.prototype); //true console.log(fo.__proto__ === f1.__proto__); //tru
所以:函数实例与原型对象的关系图解如下:
3原型对象(constructor 与proto)与构造函数直接的关系
当我们使用Foo.prototype
打印时候会发现如下信息,一个是constructor
,另一个是__prto__
(其实就是代表这个[[Prototype]]
),我们先探讨这个constructor
,后面在探讨这个__proto__
3.1:原型对象中 constructor 与构造函数的关系
先说结论:原型对象中constructor是指向构造函数
的。你可能会说 口说无凭,我要你证明给我看。既然你都有这样的疑问了,那我就满足你。
function Foo() { } var fo = new Foo(); console.log(Foo.prototype.constructor);//ƒ Foo() {}
打印结果
我们会发现Foo.prototype.constructor
是指向Foo
这个构造函数的。下面我们在来做一步验证,看下是不是真的是这样。
function Foo() { } var fo = new Foo(); console.log(Foo.prototype.constructor===Foo);//true
我们会发现Foo.prototype.constructor===Foo
是true
,所以我们可以肯定的是Foo.prototype.constructor
就是指向Foo
函数的
所以原型对象中 constructor 与构造函数的关系图解如下:
3.2:原型对象中__proto__与构造函数的关系
你可能会想原型对象中的__proto__
是不是指向Foo
呢?其实不是的。看如下代码
console.log(Foo.prototype.__proto__===Foo)//false
我们会发现打印是false
,那么它是指向的谁的呢?这个问题问的好,他到底指向谁,先打印看下就知道了 我们会惊奇的发现Foo.prototype.__proto__
指向的是Object
的原型。你可能又会有疑惑,为啥是Object
?其实Object
就是最顶层的原型,如果没有这层限制,当根据原型链去找一个不存在的数据,我们怎么会知道它没找到呢!!
所以我们可以的得出结论是Foo.prototype.__proto__
指向的是Object.prototype
论证
:Foo.prototype.__proto__
是否指向的是Object.prototype
function Foo() { } var fo = new Foo(); console.log(Foo.prototype.__proto__===Object.prototype)//true
可以看出他们的打印是相等的,所以之前说的Foo.prototype.__proto__===Object.prototype
结论是成立的。 细心的同学可能会有这样的疑问?Object.prototype__proto__
指向谁呢?其实它指向的是null
。
console.log(Object.prototype.__proto__)//null
其实到这里我们还可以继续丢出一个疑问?既然我们知道了Object
的函数原型对象,那肯定有自己的constructor
,那它的constructor
是谁呢?下面我们继续探讨。
3.3:Object函数原型与constructor关系
结论
:Object
原型中的constructor
指向的是Object
函数。
代码验证如下:
console.log(Object.prototype.constructor)
打印后我们发现指向的是Object
的函数,在继续进行验证
console.log(Object.prototype.constructor===Object)
所以在这里我们可以很确定看到Object.prototype.constructor
就是指向Object
函数的。既然Object
是函数它肯定也是有自己的Prototype
的,它的Prototype
肯定是指向Object
的函数原型对象的。
所以:Object函数原型与constructor关系
其实这里也已经形成了原型链
了,我们是可以发现如果fo
在自己的原型上找不到,他就会父类的原型上去找,这样一层层的向上查找,其实就像一个链条一样的,所以叫做原型链
。当然这里也不是永无止境的,找到最后都找不到就返回了null
。
其实到这里最基本原型知识我们差不多已经掌握了。但是其实我们可以继续来探讨下,Foo
函数和Object
函数它有没有__proto__
呢?其实是有
的,为什么?因为函数的本质其实也是一个对象
,下面我们就对函数是不是一个对象的问题进行探讨。
4函数其实也是一个对象
function bar() {} bar.names = "戈亚"; console.log(bar.names);
通过打印我们确实看到了names
打印出来的,所以我们也能证明函数的确是一个对象
,只不过是一个比较特殊的对象
而已。所以我们可以说上面的Foo
函数和Object
函数也是一种对象。既然是对象它肯定也有自己__proto__
,那么它的__proto__
指向谁呢?
5函数的__proto__
结论
:函数的__proto__
其实是指向Functon
原型对象。
下面来进行验证,具体代码如下
function Foo(){} var p1 = new Foo(); console.log(Person.__proto__)
这个可能有点不明显,下面我们换种方法进行验证,如下:
function Foo() {} var p1 = new Foo(); console.log(Foo.__proto__) console.log(Foo.__proto__===Function.prototype) console.log(Object.__proto__===Function.prototype) console.log(Foo.__proto__===Object.__proto__)
所以我们可以说函数的__proto__
其实是指向Functon原型对象
。即Object/Foo
都是Fuction
的实例对象
所以:函数的__proto__
指向Function
的原型的图解如下:
6Function的原型和原型链
6.1:Function原型中的指向
6.1.1:Function原型中的constructor
结论:Function
原型中的constructor
的指向Function
函数
我们可以以Foo
这个函数来做验证,代码如下:
function Foo() {} //Foo.__proto__:这里其实已经是Function的原型对象了 console.log(Foo.__proto__.constructor === Function);
通过打印其实我们可以看出Function
原型对象中的constructor
其实是指向的Function
函数。那么它的__proto__
指向的是谁呢?
6.1.2:Function原型中的__proto__的指向
结论
:Function
原型中的__proto__
指向的是Object
的原型对象
function Foo() {} //Foo.__proto__:这里其实已经是Function的原型对象了 console.log(Foo.__proto__.__proto__===Object.prototype);
通过打印其实我们可以看出Function
原型对象中的__proto__
其实是指向Object
的原型对象的。其实也很好理解,原型对象本来就是一个对象,既然是对象肯定有自己的原型,我们都知道所有对象的父类就是Object
。
所以:Function原型对象的图解:
6.2:Function函数的原型
6.2.1:Function函数的原型中的prototype
我们通过前面的总结中是可以知道,函数中的prototype
是指向该函数的原型对象的。所以我们这里是Function
函数中的prototype
就是指向Functon
函数的原型对象。
6.2.2:Function函数的原型中的__proto__
结论
:Function
函数的原型中的__proto__
是指向Funtion
原型的。 代码如下:
console.log(Function.__proto__===Function.prototype)
可以看到打印是true
,说明Function
的__proto__
就是指向Function原型
的。其实我们结合6.1
就可以知道所有的函数的__proto__
都是指向的是函数的原型对象
的。而这里它本来就是Function
所以指向自己的原型对象
。
现在在看下面张图就会变得非常简单,大致可以分为3大模块,我用序号进行的划分。
把上面的图可以拆成这样的函数,在结合我上面的分析 其实是非常容易的
function Foo(){ } let f1=new Foo() let f2=new Foo()