今天给大家分享一篇关于在 JS 中非常不好懂理解,而且面试时候还经常问到的关于原型和原型链的知识。 

手把手教你学会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 主要是做了以下几个步骤:

  1. 在内存中创建了一个空对象;
  2. 这个对象内部[[prototype]]属性会被赋值为该类的 prototype 属性;
  3. 构造函数中的 this,会指向创建出来的新对象;
  4. 执行构造函数中的代码
  5. 如果构造函数没有返回非空对象,则返回创建出来的新对象

下面用伪代码演示下

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

所以:函数实例与原型对象的关系图解如下:

手把手教你学会js的原型与原型链,猴子都能看懂的教程_构造函数_02

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() {}

打印结果 

手把手教你学会js的原型与原型链,猴子都能看懂的教程_javascript_03

 我们会发现​​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关系 

手把手教你学会js的原型与原型链,猴子都能看懂的教程_原型模式_04

其实这里也已经形成了​​原型链​​​了,我们是可以发现如果​​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__)

手把手教你学会js的原型与原型链,猴子都能看懂的教程_原型对象_05

 这个可能有点不明显,下面我们换种方法进行验证,如下:

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__)

手把手教你学会js的原型与原型链,猴子都能看懂的教程_构造函数_06

 所以我们可以说函数的​​__proto__​​​其实是指向​​Functon原型对象​​​。即​​Object/Foo​​​都是​​Fuction​​的实例对象

所以:函数的​​__proto__​​​指向​​Function​​的原型的图解如下: 

手把手教你学会js的原型与原型链,猴子都能看懂的教程_原型模式_07

6Function的原型和原型链

6.1:Function原型中的指向

6.1.1:Function原型中的constructor

结论:​​Function​​​原型中的​​constructor​​​的指向​​Function​​函数

我们可以以​​Foo​​这个函数来做验证,代码如下:

 function Foo() {} 
//Foo.__proto__:这里其实已经是Function的原型对象了
console.log(Foo.__proto__.constructor === Function);

手把手教你学会js的原型与原型链,猴子都能看懂的教程_原型模式_08

 通过打印其实我们可以看出​​Function​​​原型对象中的​​constructor​​​其实是指向的​​Function​​​函数。那么它的​​__proto__​​指向的是谁呢?

6.1.2:Function原型中的__proto__的指向

​结论​​​:​​Function​​​原型中的​​__proto__​​​指向的是​​Object​​的原型对象

 function Foo() {} 
//Foo.__proto__:这里其实已经是Function的原型对象了
console.log(Foo.__proto__.__proto__===Object.prototype);

手把手教你学会js的原型与原型链,猴子都能看懂的教程_原型对象_09

 通过打印其实我们可以看出​​Function​​​原型对象中的​​__proto__​​​其实是指向​​Object​​​的原型对象的。其实也很好理解,原型对象本来就是一个对象,既然是对象肯定有自己的原型,我们都知道所有对象的父类就是​​Object​​。

所以:Function原型对象的图解: 

手把手教你学会js的原型与原型链,猴子都能看懂的教程_javascript_10

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)

手把手教你学会js的原型与原型链,猴子都能看懂的教程_构造函数_11

 可以看到打印是​​true​​​,说明​​Function​​​的​​__proto__​​​就是指向​​Function原型​​​的。其实我们结合​​6.1​​​就可以知道所有的函数的​​__proto__​​​都是指向的是函数的​​原型对象​​​的。而这里它​​本来就是Function​​​所以指向自己的​​原型对象​​。 

现在在看下面张图就会变得非常简单,大致可以分为3大模块,我用序号进行的划分。 

手把手教你学会js的原型与原型链,猴子都能看懂的教程_原型模式_12

 把上面的图可以拆成这样的函数,在结合我上面的分析 其实是非常容易的

function Foo(){

}
let f1=new Foo()
let f2=new Foo()