前言

在 segmentfault 上看到这样一道题目

var F = function(){};
Object.prototype.a = function(){};
Function.prototype.b = function(){};
var f = new F();

问:f 能取到a,b吗?原理是什么?

乍一看真的有点懵,仔细研究了一下,发现还是对原型理解不透彻,所以总结一篇,填个洞~


Function和Object

在解题之前,先再说说 原型、原型链,以及 Function 和 Object 的关系,这也是本文的重点。

原型

  • 在创建一个函数的时候,会自动为其创建一个原型对象,可以通过函数的prototype属性访问到。

  • 创建一个构造函数的实例对象,该实例对象内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第5版中管这个指针叫[[prototype]]。虽然在脚本中没有标准的方式访问[[prototype]],但Firefox、 Safari、 Chrome在每个对象上都支持一个属性 __proto__,用于访问其构造函数的原型对象。

  • 重要的事情再说一遍:
    构造函数通过 prototype 属性访问原型对象。
    实例对象通过 [[prototype]] 内部属性访问原型对象,浏览器实现了 _proto_ 属性用于实例对象访问原型对象。

    var F = function () {};
    var f = new F();
    // 假设F的原型对象是 p, 则
    // F.prototype === p;
    // f.__proto__ === p;
    

再重复一遍。。prototype说的是构造函数和原型对象之间的关系,__proto__说的是实例对象和原型对象之间的关系。

原型链

类 A继承B,B继承C……其实就是A的原型对象中有指针指向B的原型对象,而B的原型对象中有指针指向C的原型对象……注意是原型对象之间的联系,A B C 这三个构造函数之间并没什么关系,所以才称为“原型链”吧~

假设a是A的实例对象,则 a 的原型链为下图中紫色线条所示,橙色线条连接了构造函数和其原型对象。

由图可以看出,原型链的末端是Object.prototype.__proto__null。当查找a的某个属性或方法时,首先查找a自身有没有,没有则沿着原型链一直查找,直到找到或者最后到null返回undefined

Function 和 Object

FunctionObject 之间的关系有点绕:
Object 是构造函数,既然是函数,那么就是Function的实例对象;Function是构造函数,但Function.prototype是对象,既然是对象,那么就是Object的实例对象。

一切对象都是Object的实例,一切函数都是Function的实例。
ObjectFunction的实例,而Function.prototypeObject的实例。

二者的关系如下图所示。

可见,Object作为构造函数,它有 prototype 属性指向 Object.prototype , 作为实例对象, 它有 Object.__proto__ 指向Function.prototypeFunction是构造函数,它有prototype属性指向Function.prototype,而Function是函数,从而也是Function的实例,所以它有Function.__proto__指向Function.prototype,从而 Function.__proto__ === Function.prototypetrue

可在Chrome控制台下进行验证,如图。

原题解析

解决原型链问题最好的办法就是画图了,经过前面的分析,这个图画起来应该不成问题,如下~

f 的原型链为蓝色线所画,所以 f 可以访问到 a , 不能访问到 b 。

如果不画图,乍一看,可能会觉得f 可以访问到 b,那是可能跟我一样误认为F.prototype指向Function.prototype,但其实F.prototype是对象而不是函数,所以它的原型对象不会Function.prototype

所以,原型链问题一应要画图啊~

原题扩展

在上题中,f 只能访问 a,不能访问 b 。但 F 既可以访问 a ,又可以访问 b。
如果把题修改成下面的样子, F.b()的结果是什么呢?为什么呢?可以想一下哦~

var F = function(){};
Object.prototype.a = function(){};
Function.prototype.b = function(){ console.log('F.__proto__') };
F.prototype.b = function (){console.log('F.prototype');};

总结

  • 读到这里,有没有发现函数一个比较特殊的地方?
    一般的对象,只有一个__proto__属性用于访问其构造函数的原型对象,而对于函数来说,它既是函数又是对象。
    作为函数,它生来就有prototype属性指向其原型对象函数名.prototype
    作为Function的实例对象,它有__proto__属性指向Function.prototype
    通常,这两个属性是指向两个对象的,但Function的这两个属性指向相同,都指向Function.prototype

  • 对于函数 A( ) 来说,A.prototype 中的方法是供其实例对象调用的,自己并不会用;当A 作为实例运行时,调用的是 A.__proto__ 中的方法。也就是说,作为构造函数使用时,走的是A.prototype这条链,方法、属性赋给其实例;作为对象使用时,走的是A.__proto__这条链。在不同的场景下,分清它的身份就不会错了。


整篇下来,感觉自己说的也比较絮叨……不足之处,还请各位指正~ 至于题目,真的不知道该叫什么好。。

愿本文能带给坚持看完的你一些收获~ ^_^

参考

js 原型的问题 Object 和 Function 到底是什么关系?—— 高票答案

如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的文章

15 条评论
边城 · 2015年08月28日

你这图画得漂亮,是什么工具画的?看样子有点像直接手工绘的啊,那可有点花时间,赞个

回复

梦禅 作者 · 2015年08月29日

大神就是厉害,一眼看穿,一时找不到合适的,就用PPT画的,真的花了好长时间。。求推荐画图工具~ :)

回复

边城 · 2015年08月29日

我一般也是用PPT画,在找好用的工具,所以看到别人的图好看的,一般都要问问用的啥。Visio不错,但是不想装这么巨的东西。有时候画的图不是很复杂就用 ProcessOn 了。如果可以用脑图(或分支形式)表达的图就用 XMind,或者百度脑图。不过现在 ProcessOn 有脑图了,所以用这 Xmind 和 百度脑图的时候也少了。像你这种图,我真没想到有合适的工具。

回复

梦禅 作者 · 2015年08月29日

又收获一个新工具 ProcessOn,谢谢~

回复

Tgor · 2015年08月29日

ProcessOn 真神器。
凌乱了,f.__proto__指向Function.prototype,那Function.prototype.__proto__同样指向Object.prototype,那应该是可以访问b吧

回复

老李快跑 · 2015年08月29日

关键的地方是要理解构造函数Function的实例

回复

老李快跑 · 2015年08月29日

f.__proto__指向的是F.prototype,并不是Function.prototype

F 继承自Object,并不是Function, F只是Function 的实例(F instanceof Function === true)

回复

honger05 · 2015年08月29日

一语道破

回复

文刀 · 2015年08月29日

很厉害~我得好好理解一下

回复

b5156 · 2015年08月29日

讲得很明白,谢谢

回复

十九车厢 · 2015年08月30日

bucuo

回复

嘻倪孢 · 2015年08月31日

好累。。。看懂了一些。。。就记住了一句话
prototype说的是构造函数和原型对象之间的关系,proto说的是实例对象和原型对象之间的关系。

回复

风过林梢 · 2015年09月02日

楼主太棒了,完全理解了,函数既是Function的实例对象,又是构造函数。

回复

hackerws · 2016年09月06日

我真的很想推荐两次,可惜只能推荐一次,非常好,希望楼主继续保持下去。很棒~

回复

梦禅 作者 · 2016年09月09日

惭愧之前没能坚持,以后会尽量继续

回复

载入中...
梦禅 梦禅

3k 声望

发布于专栏

前端攻城笔记

努力从来都不晚

43 人关注

系列文章