1.前言
原型与原型链知识历来都是面试中考察的重点,说难不算太难,但要完全理解还是得下一定的功夫。先来看一道面试题开开胃口吧:
functionUser(){}User.prototype.sayHello=function(){}varu1=newUser();varu2=newUser();console.log(u1.sayHello===u2.sayHello);console.log(User.prototype.constructor);console.log(User.prototype===Function.prototype);console.log(User.__proto__===Function.prototype);console.log(User.__proto__===Function.__proto__);console.log(u1.__proto__===u2.__proto__);console.log(u1.__proto__===User.__proto__);console.log(Function.__proto__===Object.__proto__);console.log(Function.prototype.__proto__===Object.prototype.__proto__);console.log(Function.prototype.__proto__===Object.prototype);
2.基础铺垫
JavaScript
所有的对象本质上都是通过new 函数
创建的,包括对象字面量的形式定义对象(相当于new Object()
的语法糖)。
所有的函数本质上都是通过new Function
创建的,包括Object
、Array
等
所有的函数都是对象。
3. prototype
每个函数都有一个属性prototype
,它就是原型,默认情况下它是一个普通Object
对象,这个对象是调用该构造函数所创建的实例的原型。
4. contructor属性
JavaScript
同样存在由原型指向构造函数的属性:constructor
,即Func.prototype.constructor --> Func
5. __proto__
JavaScript
中所有对象(除了null
)都具有一个__proto__
属性,该属性指向该对象的原型。
functionUser(){}varu1=newUser();//u1.__proto__->User.prototypeconsole.log(u1.__proto__===User.prototype)//true
显而易见,实例的__proto__
属性指向了构造函数的原型,那么多个实例的__proto__
会指向同一个原型吗?
varu2=newUser();console.log(u1.__proto__===u2.__proto__)//true
其实学到这里就可以产生一些骚想法了,多个实例的__proto__
都指向构造函数的原型,那么实例如果能通过一种方式,访问原型上的方法,属性等,就可以实现继承的效果。
我们继续更新一下原型与原型链的关系图:
6. 原型链
实例对象在查找属性时,如果查找不到,就会沿着__proto__
去与对象关联的原型上查找,如果还查找不到,就去找原型的原型,直至查到最顶层,这也就是原型链的概念。
就借助面试题,举几个原型链的例子:
6.1举例
u1.sayHello()
: u1
上是没有sayHello
方法的,因此访问u1.__proto__(User.prototype)
,成功访问到sayHello
方法
u2.toString()
u2,User.prototype
都没有toString
方法,User.prototype
也是一个普通对象,因此继续寻找User.prototype.__proto__(Object.prototype)
,成功调用到toString
方法
7. 提高升华
学完上面那些,大多数面试题都可以做出来了,例如下面这种
functionA(){}functionB(a){this.a=a;}functionC(a){if(a){this.a=a;}}A.prototype.a=1;B.prototype.a=1;C.prototype.a=1;console.log(newA().a);//1console.log(newB().a);//undefinedconsole.log(newC(2).a);//2
但距离解决文章的最初的面试题还欠缺些什么,比如Function.__proto__ === Object.__proto__、Function.prototype.__proto__ === Object.prototype.__proto__
等,接着我们来一一攻克它。
7.1 Objcet.__proto__
、 Object.prototype
、Object.prototype.__proto__
Object
是构造函数,在第二部分我们讲过所有的函数都是通过new Function
创建了,因此Object
相当于Function
的实例,即Object.__proto__ --> Function.prototype
。
Object.prototype
是Object
构造函数的原型,处于原型链的顶端,Object.prototype.__proto__
已经没有可以指向的上层原型,因此其值为null
//总结:Object.__proto__-->Function.prototypeObject.prototype.__proto__-->null
7.2 Function.__proto__
、Function.prototype
、Function.prototype.__proto__
Function.prototype
是Function
的原型,是所有函数实例的原型,例如上面讲的Object.__proto__
Function.prototype
是一个普通对象,因此Function.prototype.__proto__ --> Object.prototype
Function.__proto__
: __proto__
指向创造它的构造函数的原型,那谁创造了Function
那?实践证明只存在Object.__proto__ --> Function.prototype
Function.proto --> Function.prototype
Function
函数不通过任何东西创建,JS
引擎启动时,添加到内存中
苦思冥想没得出结果,难道Function
函数是猴子不成,从石头缝里面蹦出来的?于是我进行了一同乱七八糟的测试,没想到找出了端倪。
猜想:函数也是对象,那Function.__proto__
会指向Object.prototype
吗?但上文提到,Object
是new Function
生成,Object.__proto__ --> Function.prototype
。如果真的如此指向,未免有些混乱,因此我去做了一下测试:
7.3 升华
最后将原型与原型链方面的知识凝结成一张图:
所有函数(包括Function
)的__proto__
指向Function.prototype
自定义对象实例的__proto__
指向构造函数的原型
函数的prototype
的__proto__
指向Object.prototype
Object.prototype.__proto__ --> null
8. 总结
知识的海洋往往比想象中还要辽阔,原型与原型链这边也反复的学过多次,我认为应该学的比较全面,比较完善了。但遇到这个面试题后,我才发现我所学的只不过是一根枝干,JS里面真的有很多深层次的宝藏等待挖掘。学海无涯,与君共勉。
最后再附赠个简单的面试题,提高一下自信:
varF=function(){}Object.prototype.a=function(){}Function.prototype.b=function(){}varf=newF();console.log(f.a,f.b,F.a,F.b);//f.__proto__-->F.prototype-->Object.prototype//F.__proto__-->Function.prototype-->Object.prototype
本文部分图源:渡一教育的学习资料
作者:战场小包