Fork me on GitHub

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少,买房就差你们打赏之外那部分钱

打开支付宝扫一扫,即可进行扫码打赏哦

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

全文总共4,057字,阅读时长预计需要15分钟,感谢您的阅读,有错误还望留言批评指正,一起交流学习。

在引出下面要阐述的问题答案之前,先深入了解几个重要慨念,之前这些概念也是模模糊糊,从最原始的一步步搞清楚。

什么是函数(function)?

解释定义最好的地方就是它的起源,这里便是ECMAScript规范,我们看看规范怎么定义函数(function)的。

摘录来自ECMAScript 5.1规范的4.3.24小节:

对象类型的成员,标准内置构造器 Function的一个实例,并且可做为子程序被调用。

注: 函数除了拥有命名的属性,还包含可执行代码、状态,用来确定被调用时的行为。函数的代码不限于 ECMAScript。

至于什么是实例对象,什么构造器(构造函数)下面的会详细讲,这里我们只引用定义,知道有这么个东西它叫这个名字就够了。

函数使用function 关键字来定义,其后跟随,函数名称标识符、 一对圆括号、一对花括号。

结合一个栗子理解这句话:

1
2
3
4
5
function demo(){
console.log('jawil');
}

上面这几行代码就是一个函数,这个函数是标准内置构造器 Function的一个实例,因此函数是一个对象。demo就是这个函数的名字,对象是保存在内存中的,函数名demo则是指向这个对象的指针。console.log('jawil')则是可执行代码。

对于内存,引用,指针不太明白,没有任何认知的童鞋可以去尝试搜索补习一下这些概念,其实都是一些概念,花点时间理解一下就好了。

在浏览器我们也可以检验一下:

1
2
demo instanceof Function
//true

上面只是创建函数的一种方式, JavaScript 中有三种创建形式,分别是:
①声明式

1
function fn(){ }; //这种定义的方式,无论在哪儿定义,别处都可以调用 ;

②函数的字面量或叫直接量或称表达式

1
2
var fn=function () { }; //此时函数作为表达式存在,调用只能在定义的后面;
//解释一下表达式:凡是将数据和运算符等联系起来有值得式子就是表达式。

③以new Function 的形式

1
2
3
4
5
6
7
var fn = new Function (arg1 , arg2 ,arg3 ,…, argN , body)
/*Function 构造函数所有的参数都是字符串类型。除了最后一个参数, 其余的参数都作为生成函数的参数即形参。
这里可以没有参数。最后一个参数, 表示的是要创建函数的函数体。
使用Function构造器生成的Function对象是在函数创建时解析的。这比你使用函数声明或者函数表达式(function)
并在你的代码中调用更为低效,因为使用后者创建的函数是跟其他代码一起解析的。
*/

上面三种创建方式,第三种var fn = new Function (arg1 , arg2 ,arg3 ,…, argN , body)是最直观的,
很容易就联想到fn instanceof Functionfn一眼就看出来就是Function的实例,但是为什么JavaScript还要创造用function来创建一个函数呢?

答案显而易见,用function创建一个函数更优雅,灵活,书写方便,浏览器看到function时候其实已经帮你做了new Function()这一步了,function和new Function()创建的函数都是Function的一个实例,只是方式不一样,其实本质都是一样。就如同创建一个对象一样,我们可以var obj=new Object(),当然我们也可以var obj={};

我觉得function创建函数只是new Function()创建函数的一个语法糖,不对之处还望指出,反正我是这么理解的。

JavaScript函数的new关键字到底是干什么的?

JS 的 new 到底是干什么的?

什么是构造函数?

我们看看ECMAScript规范怎么定义构造函数(constructor)的。

摘录来自ECMAScript 5.1规范的4.3.4小节:

创建和初始化对象的函数对象。

注:构造器的“prototype”属性值是一个原型对象,它用来实现继承和共享属性。

构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象

这些抽象的东西其实不好讲,不好写,讲一个抽象的概念,又引出好几个抽象的概念,情何以堪,实例对象原型prototype下一节讲,了解概念尽量多结合栗子加深理解,理解构造函数,首先就要理解上面函数的一个概念和定义。

这种抽象的东西不是很好记忆,我们通过一个示例来说明可能更好了解。

1
2
3
function Person(name){
this.name=name;
}

在javascript中,你可以把上面的代码看做一个函数,一个类,一个方法,都没有问题。

其实,在JavaScript中,首先,它是函数,任何一个函数你都可以把它看做是构造函数,它没有明显的特征。那什么时候它就明显了呢?实例化的时候。

1
var jawil=new Person('微醺岁月');

当这一句代码结束,你就可以肯定的认为 Person 函数是一个构造函数,因为它 new 了”微醺岁月”。
那么,”微醺岁月” 是什么?”微醺岁月”是一个人,一个实实在在的人,是被构造函数初始化出来的。所以 var jawil 就变成了一个实例。

什么是实例对象,什么是原型对象?

原型

我们看看ECMAScript规范怎么定义构造函数(constructor)的。

摘录来自ECMAScript 5.1规范的4.3.5小节:

为其他对象提供共享属性的对象。

当构造器创建一个对象,为了解决对象的属性引用,该对象会隐式引用构造器的“prototype”属性。通过程序表达式 constructor.prototype 可以引用到构造器的“prototype”属性,并且添加到对象原型里的属性,会通过继承与所有共享此原型的对象共享。另外,可使用 Object.create 内置函数,通过明确指定原型来创建一个新对象。

首先说一下,只有函数才有prototype(原型)属性。为什么只有函数才有prototype属性?ECMAScript规范就这么定的。

但是不是所有的函数都有prototype属性呢?答案是否定的,这可不一定。我们看个简单的栗子:

用 Function.prototype.bind 创建的函数对象没有 prototype 属性。

那么prototype(原型)到底有啥作用呢?

prototype对象是实现面向对象的一个重要机制。每个函数也是一个对象,它们对应的类就是Function,每个函数对象都具有一个子对象prototype。Prototype 表示了该函数的原型,prototype表示了一个类的属性的集合。当通过new来生成一个类的对象时,prototype对象的属性就会成为实例化对象的属性。

这些概念简单了解一下,这不是本人要讲的重点,这里一笔带过,不太懂的可以自己去查相关资料补习一下基础。

__proto__

引用《JavaScript权威指南》的一段描述:

Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.

翻译出来就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。好啦,既然有这么一个原型对象,那么对象怎么和它对应的?

对象proto属性的值就是它所对应的原型对象:

1
2
3
4
5
var one = {x: 1};
var two = new Object();
one.__proto__ === Object.prototype // true
two.__proto__ === Object.prototype // true
one.toString === one.__proto__.toString // true

上面的代码应该已经足够解释清楚__proto__了。

实例对象

把实例和对象对比来看,或许更容易理解。

实例对象和对象的区别,从定义上来讲:

1、实例是类的具象化产品,
2、而对象是一个具有多种属性的内容结构。
3、实例都是对象,而对象(比如说Object.prototype)不全是实例。

实例是相对而言,这话怎么理解了,我们看下面两个小栗子比如说:

1
2
var a=new Array();
a instanceof Array//true

我们可以说a是Array数组的一个实例;

1
2
3
4
5
6
function Array(){
[native code]
}
Array instanceof Function//true

我们知道Array也是一个函数,虽然他是一个构造函数,只要是函数,从上面的知识点可以知道,Array是Function的一个实例。

通俗的理解这几个的关系:

JavaScript引擎是个工厂。
最初,工厂做了一个最原始的产品原型。
这个原型叫Object.prototype,本质上就是一组无序key-value存储({})

之后,工厂在Object.prototype的基础上,研发出了可以保存一段“指令”并“生产产品”的原型产品,叫函数。
起名为Function.prototype,本质上就是[Function: Empty](空函数)

为了规模化生产,工厂在函数的基础上,生产出了两个构造器:
生产函数的构造器叫Function,生产kv存储的构造器叫Object。

你在工厂定制了一个产品,工厂根据Object.prototype给你做了一个Foo.prototype。
然后工厂发现你定制的产品很不错。就在Function.prototype的基础上做了一个Foo的构造器,叫Foo。

工厂在每个产品上打了个标签proto,以标明这个产品是从哪个原型生产的。
为原型打了个标签constructor,标明哪个构造器可以依照这个原型生产产品。
为构造器打了标签prototype,标明这个构造器可以从哪个原型生产产品。

所以,我觉得先有Function还是Object,就看工厂先造谁了。其实先做哪个都无所谓。因为在你定制之前,他们都做好了。

问题引出

我们知道,Array,Date,Number,String,Boolean,Error甚至Object都是Function的一个实例,那么Function是谁的实例呢?

先看一个简单的小栗子:

1
2
3
4
5
6
7
function Person(){
...一些自定义的code
}
Person.__proto__ === Function.prototype;//true
Person.prototype instanceof Object;//true

再来看看这个:Function
也就是浏览器显示的这个,我们暂且这么类比:

1
2
3
4
5
6
7
8
9
10
11
function Person(){
...一些自定义的code
}
function Function(){
[native code]//系统帮我们写的code
}

我们再来看看,先暂时忽略后面的:

Person.__proto__=== Function.prototype;//true

Person函数是Function的一个实例。

Function.__proto__=== Function.prototype;//true

上面说了,Person函数是Function的一个实例,这没有争议,那么这行代码是否可以说Function函数对象是由Function构造函数创建的一个实例?
因为我们普遍的认知就是:实例对象(A)的proto属性指向它的构造函数(B)的原型对象(prototype)。

大白话就是:A(儿子)继承了B(父母)的一些特性(prototype)才有了A。所以问题就来了,当A===B的时候,该怎么理解了?这就是今天问题的引出了,下面就要探讨这个问题了。

再来看:
Person.__proto__=== Person.prototype;//false

这个显而易见可以看出,Person函数不是由Person的实例,因为Person是Function的一个实例。

那么问题来了:

Function构造函数的prototype属性和proto属性都指向同一个原型,是否可以说Function对象是由Function构造函数创建的一个实例?

Function.prototypeFunction.__proto__都指向Function.prototype,这就是鸡和蛋的问题怎么出现的一样。

在这之前,我一直有个误解就是,认为所有对象就是Object的实例,现在想起来真是Too young,Too simple.

Object.prototype 是对象吗?

当然是。

An object is a collection of properties and has a single prototype object. The prototype may be the null value.

这是object的定义,Object.prototype显然是符合这个定义的。但是,Object.prototype并不是Object的实例。 这也很好理解Object.prototype.__proto__是null。

就如同刚才上面区分实例和对象一样,实例都是对象,而对象(比如说Object.prototype)不全是实例。

这已经某种程度上解开了鸡和蛋的问题:Object.prototype是对象,但它不是通过Object函数创建的。Object.prototype谁创建的,它是v8引擎(假设用的都是chrome浏览器)按照ECMAScript规范创造的一个对象。我只能这么给你解释。

未完待续,好累,歇一会儿~

关于这个问题也困扰了我很久,功力不够,无法详细回答,但是经过一番查找和探究,在知乎上看到了这篇回答,引用一下,与大家共同学习。

在JavaScript中,Function构造函数本身也算是Function类型的实例吗?Function构造函数的prototype属性和proto属性都指向同一个原型,是否可以说Function对象是由Function构造函数创建的一个实例?

Yes and No.

Yes 的部分:
按照JS中“实例”的定义,a 是 b 的实例即 a instanceof b 为 true,默认判断条件就是 b.prototype 在 a 的原型链上。而 Function instanceof Function 为 true,本质上即 Object.getPrototypeOf(Function) === Function.prototype,正符合此定义。

No 的部分:

Function 是 built-in 的对象,也就是并不存在“Function对象由Function构造函数创建”这样显然会造成鸡生蛋蛋生鸡的问题。实际上,当你直接写一个函数时(如 function f() {}x => x),也不存在调用 Function 构造器,只有在你显式调用 Function 构造器时(如 new Function('x', 'return x') )才有。

注意,本质上,a instanceof b 只是一个运算,即满足某种条件就返回 true/false,当我们说 a 是 b 的实例时,也只是表示他们符合某种关系。JS 是一门强大的动态语言,你甚至可以在运行时改变这种关系,比如修改对象的原型从而改变 instanceof 运算的结果。此外,ES6+ 已允许通过 Symbol.hasInstance 来自定义 instanceof 运算。

我知道很多 JS 学习者会迷恋于对象和函数之间的 instanceof 关系,并希望探究到底谁更本源?我当初也在这个问题上浪费了很多时间。但这是一个伪问题。参见:JavaScript 里 Function 也算一种基本类型?以上。

收获

对JavaScript的原型和原型链相比以前有了一个更深刻的认识,同时也对函数,构造函数,实例对象的一些概念有了一个更具体的认知,以前对这些概念都是模模糊糊,没有一个明确的概念,导致在理解一些问题上出现盲点,比如说:function和Function的问题,现在总是认知清楚了,也了解到没有十全十美的语言,任何语言也有它的一些缺陷和漏洞,比如说Function对象是由Function构造函数创建的一个实例?typeof null的返回值是Object的问题,历史的车轮滚滚向前,语言也是向前发展,但愿JavaScript发展越来越好,越来越完善,一统天下😄。

最后感觉还是有疑问,很纠结,越陷越深,慢慢回答自己的问题

结论:先有 Object.prototype(原型链顶端),Function.prototype 继承 Object.prototype 而产生,最后,Function 和 Object 和其它构造函数继承 Function.prototype 而产生。

  1. 先有 Object.prototype,再有 Object,那么先有 Object.prototype 里面的这个 Object 代表的是什么呢?

  2. Function.proto=== Function.prototype;
    Function 构造函数的 prototype 属性和proto属性都指向同一个原型,是否可以说 Function 对象是由 Function 构造函数创建的一个实例?

  3. Object instanceof Function // true
    Function instanceof Object // true
    Object 本身是构造函数,继承了 Function.prototype;Function 也是对象,继承了 Object.prototype。感觉成了鸡和蛋的问题了。

  4. 比如说:
    function Person(){}
    Person.prototype 是一个对象,为什么 Function.prototype 却是一个函数呢,当然函数也是一个对象,为什么要这么设计呢?

参考文章

JavaScript 的语言设计有哪些缺陷?
JS 的 new 到底是干什么的?
proto和prototype来深入理解JS对象和原型链
在JavaScript中,Function构造函数本身也算是Function类型的实例吗?
JS中先有Object还是先有Function?
JavaScript 世界万物诞生记

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少,买房就差你们打赏之外那部分钱

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 什么是函数(function)?
  2. 2. JavaScript函数的new关键字到底是干什么的?
  3. 3. 什么是构造函数?
  4. 4. 什么是实例对象,什么是原型对象?
    1. 4.1. 原型
    2. 4.2. __proto__
    3. 4.3. 实例对象
  5. 5. 问题引出
  6. 6. 那么问题来了:
  7. 7. 收获
  8. 8. 最后感觉还是有疑问,很纠结,越陷越深,慢慢回答自己的问题
  9. 9. 参考文章
,