JS 鸡生蛋与蛋生鸡问题,Object与Function究竟谁出现的更早?Function算不算Function的实例?

2,091 阅读7分钟

壹 ❀ 引

我在JS 疫情宅在家,学习不能停,七千字长文助你彻底弄懂原型与原型链一文中介绍了JavaScript原型与原型链,以及衍生的__proto__constructor等一系列属性。在解答了多个问题的同时,也得出了很多有趣的结论。比如我们常说JavaScript中函数是一等公民,这是因为函数扮演了创造万物的角色,原始构造函数Function创造了function fn(){}(ES5中函数与构造函数并无区别)Object()Array()Number()String()等诸多构造函数,而构造函数也拥有创造对应实例对象的能力,比如Array()生产数组,String()生产字符串,你会发现JavaScript中绝大多数的数据类型,都能找到创造自己的构造函数,所以说函数是一等公民不无道理。

但在之后的时间我发现这个结论存在部分误导性以及问题,比如:

  • 如果说Function()扮演着创世主的角色,那Function.prototype不应该是仅次于原型链顶端null的存在吗?
  • 在介绍原型链的过程中,我们知道紧接在null之下的是Object.prototypeObject.prototype的起源地位似乎比Function.prototype更早,那Object.__proto__ === Function.prototype又是怎么回事?ObjectFunction到底谁的起源更早,谁才是真正的创世主?
  • 我们说函数扮演着实例和构造器的双重身份,这句话虽然没错,但Function.__proto__ === Function.prototype又是怎么回事?难道FunctionFunction的实例?自己生产自己?

带着这些问题,我们开始本篇文章的探讨。

贰 ❀ Function()与Function.prototype

回到文章开头,如果我们用图解关系来表示Function是造物主的结论,应该这样:

img

但是大家心里都清楚,Object()String()等构造器并不是真的由原始构造函数Function()创建,本质上来说,它们都由JavaScript底层实现。

我们之所以说出了这个结论,是因为这些构造器的__proto__均指向了构造器Function.prototype,取自上篇博客的代码:

var fn = function () { };
​
Number.__proto__ === Function.prototype //true
Number.constructor === Function //trueString.__proto__ === Function.prototype //true
String.constructor === Function //trueObject.__proto__ === Function.prototype //true
Object.constructor === Function //true
​
fn.__proto__ === Function.prototype //true
fn.constructor === Function //true

让我们把图画的更精准点,应该是这样:

img

到这里不知道大家有没有感觉到,Function()Function.prototype其实是两个东西。我们得到的实际结论是,所有函数对象的__proto__都指向Function.prototype,包括Function()本身。

Function.__proto__ === Function.prototype //true
Function.constructor === Function //true

用图表示就是这样:

img

这句话至少能证明,Object()Number()甚至Function()这些构造器在诞生时,确实继承了Function.prototype,至于底层在实现中时有没有使用Function()那就不得而知。

所以单站在构造器的角度,从某种角度上说,Function.prototype确实是扮演了创世主的角色,所以说Function()是创世主这句话有一定误导性,这里我做个纠正。

说了这么多,Function.prototype又是什么?我们尝试打印:

img

console.log(typeof Function.prototype) // "function"

可以看到Function.prototype居然是一个函数!!!!在我印象中原型一直就是一个对象,实事如此,Function的原型就是特殊的存在。

var fn = function () {};
console.log(typeof fn.prototype) // "Object"

我们通过console.dir()展开Function.prototype这个函数的属性,如下:

img

为什么所有的函数,原始构造器都可以使用call()、apply()这些方法呢?原来在Function.prototype这个函数上已经绑好了这些方法,而其它函数在创建时又继承了Function.prototype这个函数,能用这些方法也是意料之内的事情了。

我们再来引用ECMAScript 15.3.4 上关于Function.prototype的概念加深印象:

The Function prototype object is itself a Function object (its [[Class]] is "Function") that, when invoked, accepts any arguments and returns undefined.

The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object (15.2.4). The initial value of the [[Extensible]] internal property of the Function prototype object is true.

The Function prototype object does not have a valueOf property of its own; however, it inherits thevalueOf property from the Object prototype Object.

The length property of the Function prototype object is 0.

从官方说明中可以得知,Function.prototype确实是一个函数对象,它的__proto__属性又指向Object.prototype。比如Function.prototype自身其实没有valueOf方法,但它从Object原型上借用了这个方法。

我怕大家看到后面又忘记了前面的概念,这里我们画成一个图:

img

到这里我们又知道了并不是所有的prototype对象都是Object类型,比如Function.prototype是一个函数。最重要的一点,Function()Function.prototype不是同一个东西,同理,Object()Object.prototype也不是同一个东西,一定要区分这两者,这对于后面我们理解Object.prototype也有帮助。

叁 ❀ Object()与Object.prototype

通过前面的分析,我们知道Object()这个构造函数在创建时继承了Function.prototype这个函数,怕你们弄混,上代码:

Object.__proto__ === Function.prototype//true

Function.prototype.__proto__又指向了Object.prototype,上代码:

Function.prototype.__proto__ === Object.prototype//true

有点绕,因为这是从官方说明中得到的信息,所以我们补充上面的图:

img

那么Object.prototype是个啥,这里还是直接上ECMAScript 15.2.4官方解释:

The value of the [[Prototype]] internal property of the Object prototype object is null, the value of the [[Class]] internal property is "Object", and the initial value of the [[Extensible]] internal property is true.

从官方解释中可以知道,Object.prototype确确实实是一个对象,它的__proto__指向null,所以站在原型链的角度,Object.prototype的起源确实比Function.prototype要早。通过上面的关系图就很清楚表示了这点。

//伪代码
Object.prototype => Function.prototype => Object() => {}

还记得文章开头提出的疑问中,我们说随便创建一个函数fn,fn.prototype.__proto__指向Object.prototype,这是因为除了Function.prototype之外,其它对象的prototype都是Object类型,这里指向Object.prototype其实也不难理解。别忘了,我们说所有对象都有__proto__属性,但函数除了有__protot__之外,还有prototype属性,而一般函数的prototype都是对象类型,所以指向Object.prototype。避免大家混淆,我们直接上代码:

var fn = function () {};
fn.__proto__ === Function.prototype; // true
fn.prototype.__proto__ === Object.prototype; //true

你看,fn__proto__属性不是指向了创建自己的构造函数Function了吗,前面我之所以提出疑问,就是将fn.prototypefn理解成了一个东西。

我们将这段理解变成图,继续补充我们之前的图:

img

其实说到这,文章开头提出的几个疑惑,我心中都大致有了答案。首先扮演创世主的我更倾向于是函数Function.prototype,它创造了Function(),fn(),Number(),object()等一系列构造函数,如果说FunctionFunction的实例,我更倾向于FunctionFunction.prototype的实例,毕竟这些方法都继承了Function.prototype上的方法属性。当然这个理解可能有误,我现在做的就是站在结论的角度倒推原因,只是这个原因我更容易理解。至于ObjectFunction谁更早出现,文中其中已经给了答案,真要说起源,对象Object.prototype确实更早一点,毕竟Function.prototype都是继承于Object.prototype

除此之外,通过本文的分析,我们又解锁了几个额外的概念:

  • 并不是所有的函数的prototype都是对象类型,比如原始构造器函数Functionprototype的类型居然是函数类型。
  • 并不是所有的函数都有prototype属性,比如Function.prototype虽然也是个函数但没有原型prototype
  • 虽然Function.prototype是函数类型,但是它的__proto__还是与传统prototype保持一致指向了Object.prototype

肆 ❀ 总

这篇文章说到底我并不是太满意,其实写到最后,我解答了心中部分疑惑,也产生了新的疑惑,毕竟JavaScript不是咱们写的,大部分结论都是靠自己推测,但既然写到了这里,我也确实有了新的收获,虽然对于编程帮助可能不大,但应付面试,我想各位和我一样应该是绰绰有余了。

另外,关于文中原型的图解,还是请参考下面这张好点,文中的图只是为了自己理清思路,一旦懂了这些知识,下面这张图就是日后复习不错的提示点。

img

参考

深入探究 Function & Object 鸡蛋问题

从探究Function.proto===Function.prototype过程中的一些收获

详解prototype与proto