函数的函数角色和对象角色
数据类型分为原始值和对象类型,函数属于对象类型的值。他是一种特殊的对象。那么,所有的对象都有__proto__,函数又是怎么回事呢?
举例理解:
function Fn() {
this.x = 100;
}
Fn.prototype.getX = function getX() {
console.log(this.x);};
let f1 = new Fn;
let f2 = new Fn;
我们dir(Fn),发现函数中有[[scope]] (作用域),代码字符串,和各种属性。
属性中有 prototype ,name(属性名),length:0(形参个数)
看一下Object,Object内置构造函数 每一个对象都是其实例
dir(Object.prototype):
dir(Object):
通过以上结构可以知道,平常应这样使用:实例.hasOwnProperty(),Object.assign()
所以,函数是具备多种角色的:
- 函数
- 普通函数(拥有上下文/作用域)
- 构造函数(作用:类/实例/原型/原型链)
- 普通对象(作用:拥有键值对,进行成员访问)
前面的文章说过,不考虑函数为对象的情况下,__proto__的指向是这样的
考虑函数也是一个实例对象,也拥有自己的__proto__,也应该指向他所属的类的实例原型,这个类就是 Function。
所有的函数(普通函数/构造函数)都是他的一个实例。那么既然是类,Function也应该有自己的prototype
dir(Function):
dir(Function.prototype):
以下是不考虑函数的构造函数的__proto__,仅考虑对象时,__proto__的指向
那么我们知道,所有的构造函数都是Function构造函数的实例,那么所有函数的__proto__应该指向Function.prototype
如下红线:
基于这张图,我们来进行一些打印有趣的代码
Function.prototype===Function.__proto__//true
Fn.call===Function.prototype.call//true 去构造函数的 原型上找方法
Object.apply===Function.prototype.apply//true 去构造函数的 原型上找方法
所有对象(包含函数对象) 都是Object的实例,都应该可以调取Object.prototype上的方法
例如Fn.hasOwnProperty,实际上是依据原型链,找到Fn.__proto__.__proto__.hasOwnProperty
所以
Object.__proto__===Function.prototype//true
Object.__proto__.__proto__===Object.prototype//true
Function 和Object到底谁大?
如果非要钻牛角尖去解释这个问题,那么我们要从不同的角度去理解
-
从
Object构造函数本身是函数的角度去理解的话,Object本身就是一种函数,他一定是Function构造函数的一个实例Object instanceof Function // true -
对于任何一个函数来说,所有函数都是对象,即使是构造函数,也属于对象,那么
Function构造函数又是Object的实例Function instanceof Object // true
所以这个问题是鸡生蛋蛋生鸡的问题。
如果是从是对象的角度理解,那么最终所有的__proto__都指向Object.prototype,所有的值(排除原始值)都是对象数据类型的(函数也是一种特殊的对象),所有的值都是Object的实例,即所谓的万物皆对象,xxx instanceof Object==>true
如果从函数角度理解,函数比较特殊(普通函数/箭头函数/构造函数/内置函数/自定义函数/生成器函数)既是对象又是函数,作为函数来讲,所有函数都是Function的一个实例
到底谁大,那就要看从什么样的身份,什么样的角度,充当什么样的角色相对的去理解。其实不用刻意的钻牛角尖非要区分谁大。知道有这样的特性即可
这里面只有一个比较特殊的:Function.prototype
Object.prototype,Fn.prototype都是一个对象,按理来说Function.prototype也应该是一个对象,但是打印可以看到:
Function.prototype指向的是一个函数,这个函数叫做 匿名空函数,执行后什么事都不干
打印后后发现他也虽然是个函数,但是他没有prototype,跟对象很相似,虽然是个函数,但反而更像对象
举例子加深理解
例1
function C1(name) {
if (name) {
this.name = name;
}
}
function C2(name) {
this.name = name;
}
function C3(name) {
this.name = name || 'join';
}
C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';
alert((new C1().name) + (new C2().name) + (new C3().name));
// "Tomundefinedjoin"
比较简单,第一个没有name就是原型上的name,第二个没传就是undefined,第三个||返回的是后面的join,然后字符串拼接
例2
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();
前四个输出考察函数执行,变量提升,this,函数作为对象角色。
分别输出 2 4 1 1
后面考察函数的多种角色问题,优先级问题
new Foo 和new Foo()都是执行new,区别在于运算符优先级问题
xxx.xxx成员访问 20new xxx()20new xxx19
处理时按照优先级来
new Foo.getName()这句话有两个运算,new Foo和Foo.getName,后者成员访问的优先级更高,所以先运算Foo.getName,其返回值假设为一个函数Fg然后再执行new Fg(),而new Fg()会把Fg函数执行一遍,输出2,至于创造出来什么对象,就不用管了
注意,new Foo.getName()并不是new的Foo.getName()的返回结果 undefined,而是将后面的函数当做一个整体来new,假设Foo.getName为Fg那么其实运行的是new Fg(),我们可以将Foo.getName修改如下,确实验证了说法
所以所以new Foo.getName()的执行最终相当于new (Foo.getName)(),打印2
而 new Foo().getName() 这句代码new Foo()和Foo().getName都是20,按照文档,应该从左往右运算,所以应该先执行new Foo(),执行完的结果得到后再执行结果.getName()
new Foo()时,其中的this不再指向window,而指向实例对象,所以最终返回this,返回的仍然是实例对象,而不是window。因为私有的没有gitName()方法,所以会去其原型上寻找getName()最终输出3
new new Foo().getName()这句话,首先执行new Foo(),假设返回一个实例对象0x003,那么就转化为new 0x003.getName()那么就是和第一个一样,先进行成员访问了,即 new (0x003.getName)() 。 0x003.getName私有上没有getName,所以要去其原型上找,最后是输出3这个函数,假设这个函数是0x004,那么相当于new 0x004(),执行函数,输出3
下面是执行的过程