[JS] 高能!typeof Function.prototype 引发的先有 Function 还是先有 Object 的探讨

来个摸底测试,说出以下每个表达式的结果

function F(){};
var o = {};

typeof F;
typeof o;
typeof F.prototype;
typeof o.prototype;
typeof new F;
typeof (new F).prototype;
typeof (new F).__proto__;
typeof F.__proto__;
typeof o.__proto__;
typeof Object;
typeof Function;
typeof (new Function).prototype;
typeof (new Function).__proto__;
typeof (new Object).prototype;
typeof (new Object).__proto__;
typeof Object.prototype;
typeof Object.__proto__;
typeof Function.prototype;
typeof Function.__proto__;

function F(){};
var o = {};
                
typeof F;                          //==> function
typeof o;                          //==> object
typeof F.prototype;                //==> object
typeof o.prototype;                //==> undefinded
typeof new F;                      //==> object
typeof (new F).prototype;          //==> undefined
typeof (new F).__proto__;          //==> object
typeof F.__proto__;                //==> function
typeof o.__proto__;                //==> object
typeof Object;                     //==> function
typeof Function;                   //==> function
typeof (new Function).prototype;   //==> object
typeof (new Function).__proto__;   //==> function
typeof (new Object).prototype;     //==> undefined
typeof (new Object).__proto__;     //==> object
typeof Object.prototype;           //==> object
typeof Object.__proto__;           //==> function
typeof Function.prototype;         //==> function
typeof Function.__proto__;         //==> function

看到这里相信有不少入门不久的同学已经产生疑惑了,是真的吗?然后在浏览器试过一番发现真是如此。

解开疑惑之前先回顾些大家都知道的知识点:

引用 MDN 关于 对象实例和对象原型对象 的阐述:

JavaScript 语言的所有对象都是由 Object 衍生的对象; 所有对象都继承了 Object.prototype 的方法和属性,尽管它们可能被覆盖。 例如,其它的构造器原型覆盖了 constructor 属性并提供了其自己的 toString 方法。 原型对象的更改会传播给所有的对象,除非这些属性和方法在原型链中被再次覆盖。

就如我们经常在各类教科中看到的 所有的实例对象都是 Object 类型的实例

那么我们平时都是如何确定一个对象是否是另一个类型或对象的实例的呢?

对我们可以使用 typeof 关键字 亦或可以使用关键字 instanceof 来确定某个对象是否是指定类型或对象的实例:

typeof {} //object
({}) instanceof Object //true

typeof Date                      //function
Date instanceof Function         //true
typeof Date.prototype            //obejct
Date.prototype instanceof Object //true

然而针对 Object 的 prototype 属性:

typeof Object.prototype //object

Object.prototype instanceof Object // false

为什么,要想搞清楚为什么就得明白 instanceof 这个关键字在表达式中发生了什么?

在弄清楚 instanceof 之前 还得弄清楚一样东西 就是 new 一个对象到底做了什么:

var a = new A(); 认为 “a为A函数的实例对象”

new操作的过程是什么? 可以总结如下:

  1. new 创建一个空对象{}
  2. 然后将 A.prototype 的引用放置到该对象的原型链上。即 a.__proto__ 指向 A.prototype
  3. 执行 A 函数,将 A 中 this 指向该对象,执行结束,如果没有 return 那么默认返回this引用

那么new的其中一个的作用便是把 A.prototype 的指向添加到了 a 的原型链中。

至此我们便知道了如下关系:

a.\_\_proto__ === A.prototype //true

a instanceof A //true

故为何不得出一个结论:

instanceof 操作符其实就是检查左侧的元素的 __proto__ 链上有没有右侧类或对象的 prototype 存在。 同理 当某某某是某某某的实例时,其实也是证明左侧的 __proto__ 链上有右侧类或对象的 prototype 存在。

细节剖析如下:

  1. 看右侧的 A 获取其 prototype 得到 A.prototype。
  2. 看左侧 a 对象的原型链上是否有第一步得到 A.prototype。
    • 获取 a.__proto__ 对象看是否为 A.prototype,是则返回 true
    • 获取 a.__proto__.__proto__ 对象看是否为 A.prototype,是则返回 true
    • 重复获取左侧的原型链上的 [[Prototype]] 特性即 __proto__ 属性进行判断直到为空返回 false。

校验真理,我们都知道 js 有几大内置类型 这些类型都是 Function 的实例,是 Function 类型:

out(typeof Date)     //Function
out(typeof RegExp)   //Function
out(typeof Number)   //Function
out(typeof Boolean)  //Function
out(typeof String)   //Function
out(typeof Array)    //Function
out(typeof Error)    //Function
//...

out(Date.__proto__  === Function.prototype)    //true
out(RegExp.__proto__ === Function.prototype)   //true
out(Number.__proto__ === Function.prototype)   //true
out(Boolean.__proto__ === Function.prototype)  //true
out(String.__proto__ === Function.prototype)   //true
out(Array.__proto__ === Function.prototype)    //true
out(Error.__proto__ === Function.prototype)    //true
//...

out(Object.getPrototypeOf(Date)  === Function.prototype)    //true
out(Object.getPrototypeOf(RegExp) === Function.prototype)   //true
out(Object.getPrototypeOf(Number) === Function.prototype)   //true
out(Object.getPrototypeOf(Boolean) === Function.prototype)  //true
out(Object.getPrototypeOf(String) === Function.prototype)   //true
out(Object.getPrototypeOf(Array) === Function.prototype)    //true
out(Object.getPrototypeOf(Error) === Function.prototype)    //true
//...

out( Date instanceof Function)    //true
out( RegExp instanceof Function)  //true
out( Number instanceof Function)  //true
out( Boolean instanceof Function) //true
out( String instanceof Function)  //true
out( Array instanceof Function)   //true
out( Error instanceof Function)   //true
//...

回到上述针对 Object 的 prototype 属性疑惑 为什么到了 Object 就得不出一样的结果了呢?

我们都知道每个函数对象亦或函数类型都会有个 prototype 属性,在其上挂载的方法和属性均能够被该类型实例化出来的对象共享,因为实例化出来的对象拥有 [[Prototype]] 特性即 \_\_proto__ 属性,js 便是通过该特性实现原型链机制。

那么这些函数的 prototype 属性对象是否也有自己的 [[Prototype]] 特性即 \_\_proto__ 属性呢?

out(typeof Date.prototype)    //object
out(typeof RegExp.prototype)  //object
out(typeof Number.prototype)  //object
out(typeof Boolean.prototype) //object
out(typeof String.prototype)  //object
out(typeof Array.prototype)   //object
out(typeof Error.prototype)   //object

out(typeof Object.getPrototypeOf(Date.prototype))        //object
out(typeof Object.getPrototypeOf(RegExp.prototype))      //object
out(typeof Object.getPrototypeOf(Number.prototype))      //object
out(typeof Object.getPrototypeOf(Boolean.prototype))     //object
out(typeof Object.getPrototypeOf(String.prototype))      //object
out(typeof Object.getPrototypeOf(Array.prototype))       //object
out(typeof Object.getPrototypeOf(Error.prototype))       //object

out(Object.getPrototypeOf(Date.prototype) === Object.prototype)    //true
out(Object.getPrototypeOf(RegExp.prototype) === Object.prototype)  //true
out(Object.getPrototypeOf(Number.prototype) === Object.prototype)  //true
out(Object.getPrototypeOf(Boolean.prototype) === Object.prototype) //true
out(Object.getPrototypeOf(String.prototype) === Object.prototype)  //true
out(Object.getPrototypeOf(Array.prototype) === Object.prototype)   //true
out(Object.getPrototypeOf(Error.prototype) === Object.prototype)   //true

可以看出每个函数对象的 prototype 属性也有自己的 [[Prototype]] 特性 而且指向的是 Object.prototype

那么是否所有对象都会有直接的 [[Prototype]] 特性 呢?

out( Object.getPrototypeOf( Object.getPrototypeOf(Date.prototype)))     //null
out( Object.getPrototypeOf( Object.getPrototypeOf(RegExp.prototype)))   //null
out( Object.getPrototypeOf( Object.getPrototypeOf(Number.prototype)))   //null
out( Object.getPrototypeOf( Object.getPrototypeOf(Boolean.prototype)))  //null
out( Object.getPrototypeOf( Object.getPrototypeOf(String.prototype)))   //null
out( Object.getPrototypeOf( Object.getPrototypeOf(Array.prototype)))    //null
out( Object.getPrototypeOf( Object.getPrototypeOf(Error.prototype)))    //null
out( Object.getPrototypeOf( Object.prototype))                          //null

答案是否。

有个例外他就是 Object.prototype

回看前面的 Demo:

Object.prototype instanceof Object // false

从前面的代码输出我们看到 Object.prototype 对象是没有 [[Prototype]] 特性的,同时前面我们也讨论过 instanceof 这个关键字所涉及的具体操作。

我们可以具体剖析如下:

  1. 看右侧的 Object 获取其 prototype 得到 Object.prototype。
  2. 看左侧 Object.prototype 对象的原型链上是否有第一步得到 Object.prototype。
    • 获取 Object.prototype.__proto__ 对象为空直接返回 false。

那么为什么所有的对象都有 [[Prototype]] 特性,唯独 Object.prototype 没有呢。答案很简单:既然 JS的继承机制是基于原型链的那总该有个头吧,这个头便是 Object.prototype

再来一发:

out( typeof Function)                                          //function
out( typeof Object)                                            //function
out( Object instanceof Function)                               //true
out( Function instanceof Function)                             //true

在学习 JS 的过程中我们已经知道所有的数据类型都是 Function 类型的实例,包括 Object 在内,但是我们都知道所有的对象都是 Object 的实例,这时便引出文章的题目

到底是先有 Function 还是先有 Object?

out( Function.__proto__  === Function.prototype)               //true
out( Object.__proto__  === Function.prototype)                 //true
out( Object.getPrototypeOf(Function) === Function.prototype)   //true
out( Object.getPrototypeOf(Object) === Function.prototype)     //true
out( Object instanceof Function)                               //true 
out( Function instanceof Function)                             //true

从上述代码加上前面得出的结论我们可以看出

Function 和 Object 的原型链上都有 Function.prototype

我们再来详细看看 Function.prototype

out( typeof Function.prototype);                // function
out( Function.prototype instanceof Object)      //true

这时候问题升华为

Function.prototype 和 Object.prototype 的关系。

out( Function.prototype.__proto__ === Object.prototype)

这时候关系已经很明了了:

我们都知道除了 Object.prototype 之外所有对象都会有 [[Prototype]] 特性 即 __proto__ 属性,故 Function.prototype 也不例外, Function.prototype 指向的是 Object.prototype

这时候就可以清楚的知道为什么说所有类型都是 Function 的实例,同时也是 Object 的实例:

因为所有的类型的 [[Prototype]] 特性 即 \_\_proto__ 属性均指向的是 Function.prototype ,同时 Function.prototype 的[[Prototype]]特性 即 \_\_proto__ 属性又指向了 Object.prototype 。

故大王是 Object.prototype,二王是 Function.prototype,其他均是小弟,但是小弟也有等级划分

先接着来看 Function:

out( typeof Function.prototype);                // function
out( typeof Function.__proto__);                // function
out( Function.prototype === Function.__proto__);// true

首先我们可以看出 Function.prototype 和其他类型的 prototype 属性是 object 类型不一样, 是 function 类型。 其次 Function.__proto__ 指向了 Function.prototype。

我们知道当一个类型实例化出对象时,这些对象的便会共享挂载在该类型的 prototype 属性上的资源,因为这些对象均有 [[Prototype]] 特性,指向的就是实例化出这些对象的类型的 prototype。

从前面的例子可以看到所有的类型的 [[Prototype]] 特性均指向了 Function.prototype,所以这些类型都具有了使用挂载在 Function.prototype 上的资源的权利。因此可以看出,当一个对象具有使用挂载在 Function.prototype 上的资源的权利时,及该对象 [[Prototype]] 指向 Function.prototype 时代表这个对象是个 Function 实例是一个类型,能够实例化出该类型的对象,当然包括 Function 在内。

又因为所有类型的 [[Prototype]] 指向 Function.prototype 而 Function.prototype 的 [[Prototype]] 指向是 Object.prototype,所以这些类型都具有使用挂载在 Object.prototype 上的资源的权利。

那用这些类型实例化出来的对象呢? 类型的原型链并不在实例化出来的对象上呀,但是这些实例化出来的对象的 [[Protitype]] 指向的是其类型的 prototype 属性。

往回看前面的例子 可以看到有一段

out(Object.getPrototypeOf(Date.prototype) === Object.prototype)    //true
out(Object.getPrototypeOf(RegExp.prototype) === Object.prototype)  //true
out(Object.getPrototypeOf(Number.prototype) === Object.prototype)  //true
out(Object.getPrototypeOf(Boolean.prototype) === Object.prototype) //true
out(Object.getPrototypeOf(String.prototype) === Object.prototype)  //true
out(Object.getPrototypeOf(Array.prototype) === Object.prototype)   //true
out(Object.getPrototypeOf(Error.prototype) === Object.prototype)   //true
out(Object.getPrototypeOf(Function.prototype) === Object.prototype)   //true

可以看到这些类型的 prototype 属性的 [[Protitype]] 指向的是 Object.prototype 故至此,所有对象包括类型对象亦或类型实例化出来的对象都有使用挂载在 Object.prototype 上的资源的权利。

到这里所有对象之间的关系就已经清除了,相信已经有不少人已经晕乎了,没关系 我准备了图(看不太清晰是因为上传后被压缩了,下图 object.__proto__ 处有个错误找时间更新):

clipboard.png

当然这里我还是省略了部分细节 譬如对应类型的 prototype 属性对象均有 constructor 属性指向该类型,以及省略部分类型。

对着这张图重新看一遍本文和文章开头的摸底,相信你会有收获。

那么为什么使用 typeof 获取 Object.prototype 会返回 object 呢。

我们知道浏览器底层对 JS 的实现的是基于 C/C++ 通过上图,我们可以猜测

浏览器在初始化JS 环境时都发生了些什么(存在争议,不一定正确)

  1. 用 C/C++ 构造内部数据结构创建一个 OP 即(Object.prototype)以及初始化其内部属性但不包括行为。
  2. 用 C/C++ 构造内部数据结构创建一个 FP 即(Function.prototype)以及初始化其内部属性但不包括行为。
  3. 将 FP 的 [[Prototype]] 指向 OP。
  4. 用 C/C++ 构造内部数据结构创建各种内置引用类型。
  5. 将各内置引用类型的 [[Prototype]] 指向 FP。
  6. 将 Function 的 prototype 指向 FP。
  7. 将 Object 的 prototype 指向 OP。
  8. 用 Function 实例化出 OP,FP,以及 Object 的行为并挂载。
  9. 用 Object 实例化出除 Object 以及 Function 的其他内置引用类型的 prototype 属性对象。
  10. 用 Function 实例化出除Object 以及 Function 的其他内置引用类型的 prototype 属性对象的行为并挂载。
  11. 实例化内置对象 Math 以及 Grobal 至此,所有 内置类型构建完成。

现在我们可以回答为什么使用 typeof 获取 Object.prototype 会返回 object 了。 因为我们在使用 typeof 的时候得到的 object 类型并不完全代表是 Object 类型实例化出来的对象,有可能是底层实现的内部数据结构,包括 null。真正想知道这个类型还是需要去到当前该类的内部 [[Class]]属性,至于如何获取可以使用 Object 的 toString 方法。

最后的最后,你还对是现有 Function 还是现有 Object 有想法了吗?

以上均为个人查阅及实践总结的观点。

谢谢~


tangzixiang