函数的函数角色和对象角色
数据类型分为原始值和对象类型,函数属于对象类型的值。他是一种特殊的对象。那么,所有的对象都有__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 xxx
19
处理时按照优先级来
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
下面是执行的过程
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!