JS 究竟是先有鸡还是有蛋,Object与Function究竟谁出现的更早,Function算不算Function的实例等问题杂谈
壹 ❀ 引
我在JS 疫情宅在家,学习不能停,七千字长文助你彻底弄懂原型与原型链一文中介绍了JavaScript原型与原型链,以及衍生的__proto__
、constructor等一系列属性。在解答了多个问题的同时,也得出了很多有趣的结论。比如我们常说JavaScript中函数是一等公民,这是因为函数扮演了创造万物的角色,原始构造函数Function创造了function fn(){}(ES5中函数与构造函数并无区别)
、Object()
、Array()
、Number()
、String()
等诸多构造函数,而构造函数也拥有创造对应实例对象的能力,比如Array()
生产数组,String()
生产字符串,你会发现JavaScript中绝大多数的数据类型,都能找到创造自己的构造函数,所以说函数是一等公民不无道理。
但在之后的时间我发现这个结论存在部分误导性以及问题,比如:
- 如果说
Function()
扮演着创世主的角色,那Function.prototype
不应该是仅次于原型链顶端null的存在吗?- 在介绍原型链的过程中,我们知道紧接在null之下的是
Object.prototype
,Object.prototype
的起源地位似乎比Function.prototype
更早,那Object.__proto__ === Function.prototype
又是怎么回事?Object与Function到底谁的起源更早,谁才是真正的创世主?- 我们说函数扮演着实例和构造器的双重身份,这句话虽然没错,但
Function.__proto__ === Function.prototype
又是怎么回事?难道Function是Function的实例?自己诞生自己?
带着这些问题,我们开始本篇文章的探讨。
贰 ❀ Function()与Function.prototype
回到文章开头,如果我们用图解关系来表示Function是创世主的结论,应该这样:

但是大家心里都清楚,Object()
,String()
等构造器并不是真的由原始构造函数Function()
创建,本质上来说,它们都由JavaScript底层实现。
我们之所以说出了这个结论,是因为这些构造器的__proto__
均指向了构造器Function.prototype
,取自上篇博客的代码:
var fn = function () { };
Number.__proto__ === Function.prototype //true
Number.constructor === Function //true
String.__proto__ === Function.prototype //true
String.constructor === Function //true
Object.__proto__ === Function.prototype //true
Object.constructor === Function //true
fn.__proto__ === Function.prototype //true
fn.constructor === Function //true
让我们把图画的更精准点,应该是这样:

到这里不知道大家有没有感觉到,Function()
与Function.prototype
其实是两个东西。我们得到的实际结论是,所有函数对象的__proto__
都指向Function.prototype
,包括Function()
本身。
Function.__proto__ === Function.prototype //true
Function.constructor === Function //true
用图表示就是这样:

这句话至少能证明,Object()
、Number()
甚至Function()
这些构造器在诞生时,确实继承了Function.prototype
,至于底层在实现中时有没有使用Function()
那就不得而知。
所以单站在构造器的角度,从某种角度上说,Function.prototype
确实是扮演了创世主的角色,所以说Function()
是创世主这句话有一定误导性,这里我做个纠正。
说了这么多,Function.prototype
又是什么?我们尝试打印:

console.log(typeof Function.prototype) // "function"
可以看到Function.prototype
居然是一个函数!!!!在我印象中原型一直就是一个对象,实事如此,Function的原型就是特殊的存在。
var fn = function () {};
console.log(typeof fn.prototype) // "Object"
我们通过console.dir()
展开Function.prototype这个函数的属性,如下:

为什么所有的函数,原始构造器都可以使用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原型上借用了这个方法。
我怕大家看到后面又忘记了前面的概念,这里我们画成一个图:

到这里我们又知道了并不是所有的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
有点绕,因为这是从官方说明中得到的信息,所以我们补充上面的图:

那么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;
fn.prototype.__proto__ === Object.prototype;
你看,fn
的__proto__
属性不是指向了创建自己的构造函数Function了吗,前面我之所以提出疑问,就是将fn.prototype
与fn
理解成了一个东西。
我们将这段理解变成图,继续补充我们之前的图:

其实说到这,文章开头提出的几个疑惑,我心中都大致有了答案。首先扮演创世主的我更倾向于是函数Function.prototype
,它创造了Function(),fn(),Number(),object()
等一系列构造函数,如果说Function是Function的实例,我更倾向于Function是Function.prototype
的实例,毕竟这些方法都继承了Function.prototype
上的方法属性。当然这个理解可能有误,我现在做的就是站在结论的角度倒推原因,只是这个原因我更容易理解。至于Object与Function谁更早出现,文中其中已经给了答案,真要说起源,对象Object.prototype
确实更早一点,毕竟Function.prototype
都是继承于Object.prototype
。
除此之外,通过本文的分析,我们知道并不是所有的prototype都是对象类型,比如Function.prototype。并不是所有的函数都有prototype属性,比如Function.prototype这个函数就没有。
肆 ❀ 总
我成功抓住了二月了的尾巴,在最后一天又更新了一篇我想写的文章。今天又是从中午十二点写到了晚上七点(中间摸了会鱼),哈哈哈,还是挺充实。这篇文章说到底我并不是太满意,其实写到最后,我解答了心中部分疑惑,也产生了新的疑惑,毕竟JavaScript不是咱们写的,大部分结论都是靠自己推测,但既然写到了这里,我也确实有了新的收获,虽然对于编程帮助可能不大,但应付面试,我想各位和我一样应该是绰绰有余了。
至于三月,我想花一部分时间研究小程序,去年脑抽写了一篇入门教程,居然还有不错的点击量,那么还是给自己定个目标,继续更新下去吧。
JavaScript知识的进阶学习还会继续,各位,一起努力吧。
另外,关于文中原型的图解,还是请参考下面这张好点,文中的图只是为了自己理清思路,一旦懂了这些知识,下面这张图就是日后复习不错的提示点。

参考
从探究Function.proto===Function.prototype过程中的一些收获
JS 究竟是先有鸡还是有蛋,Object与Function究竟谁出现的更早,Function算不算Function的实例等问题杂谈的更多相关文章
- javascript原型深入解析2--Object和Function,先有鸡先有蛋
1.提出两个问题: Js 的prototype和__proto__ 是咋回事? 先有function 还是先有object? 2.引用<JavaScript权威指南>的一段描述: 每个JS ...
- packages/wepy-web/src/wx.js 分析storage 的加载原理 wx.getStorage(OBJECT)
是小程序实例化后 读入内存 还是每次调用从文件系统读取 https://github.com/Tencent/wepy/blob/bd0003dca2bfb9581134e1b05d4aa1d80fc ...
- hexo的next主题博客中加入分类页面的js,实现多级目录,并且能够点击展开,隐藏下级目录~(不知道算不算深度优化~~~)
个人博客:https://mmmmmm.me 源码:https://github.com/dataiyangu/dataiyangu.github.io 多级标题 在自己的xxxx.md文件中做如下修 ...
- js获取对象属性的两种方法,object.属性名,[‘属性名’ ]
1.通过点的方式 2.通过括号的方式 例: <input type="text" value="hello" id="text"/&g ...
- QQ好友状态,QQ群友状态,究竟是推还是拉? 网页端收消息,究竟是推还是拉?
https://mp.weixin.qq.com/s/KB1zdKcsh4PXXuJh4xb_Zw 网页端收消息,究竟是推还是拉? 原创 58沈剑 架构师之路 2020-12-28 https:/ ...
- vue-router.esm.js:1905 TypeError: Cannot convert undefined or null to object
环境:vue+vuex+element 报错原因 ...mapState('isDialNumber') mapState如果传递一个参数,参数必须是数组 ...mapState(['isDialNu ...
- Vue.js 源码学习笔记 -- 分析前准备2 -- Object.defineProperty
解析神奇的 Object.defineProperty 几行代码看他怎么用 var a= {} Object.defineProperty( a, "b", { value ...
- js遍历ajax回调函数返回值中的object对象
function printObject(obj) { //obj = {"cid":"C0","ctext":"区县& ...
- 菜鸡的Java笔记 Object 类
Object 类 Object类 的主要作用 Object 类的常用方法 内容 虽然有了对象的向上转型,可以解决了参数的统一问题,但是 ...
随机推荐
- 使用spring连接mysql数据库出错
最近在学习spring框架,但是在学到JdbcTemplate时连接数据库一直报错,百度谷歌各种查找都能没有解决问题,简直要癫狂,报错信息如下: org.springframework.jdbc.Ca ...
- Mycat使用配置实践
本来写了好多,关于配置的解释和使用以及注意,但是发现有点啰嗦含金量也不高,所以直接把实际使用的一个例子放着吧,供参考. <!DOCTYPE mycat:schema SYSTEM "s ...
- 第八节:time和random模块
定义: 模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用. 重点: 1.模块的名字不要和自带的模块名字相同,不然会优先调用自己的那个模块,因为查找模块的时候是按照sys.pat ...
- ThreeJs 导入外部三维模型,并实现鼠标滚动放大缩小旋转效果
let i = ; function init() { // create a scene, that will hold all our elements such as objects, came ...
- Docker安装Redis并介绍漂亮的可视化客户端进行操作
1 简介 Redis是使用ANSI C语言开发的基于Key-Value的高性能NoSQL数据库,在解决高并发.高可用等一系列问题中,它扮演着重要的角色.它的优势主要有: 速度快. 持久化. 原子性. ...
- 接触 Jmeter
Apache JMeter是 Apache组织开发的基于 Java的开源压力测试工具.接口以及自动化测试. JMeter 可以进行参数化测试,实现自动化脚本与测试数据分离,能够对应用程序做功能/回归测 ...
- [GO] mac安装GO 初次尝试
其实试了好多方法,我用的是下面这种方法,简单快捷! 安装homebrew 在终端输入命令 ruby -e "$(curl -fsSL https://raw.githubuserconten ...
- 用pip install不能成功安装时的处理方法
解决办法: pip install pymysql -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
- BIOS时间与系统时间(windows/linux时间同步问题)
写作动机 双系统是不少人喜欢的方式,但安装双系统之后一般会出现两个系统时间不一样的问题,刚开始用双系统的时候也没怎么在意,就是装上后在网上找找相关解决方法,复制粘贴代码完事儿.但是次数多了就有点烦了, ...
- python信息收集(二)
在第二层主机发现中,除了使用arping命令外,还可以使用Kali下自带的一个工具----netdiscover. netdiscover是一个专门用于二层主机发现的工具,它有两种扫 ...