ECMAScript规范中对Function的文档描述,我认为是ECMAScript规范中最复杂也是最很差理解的一部分,它涉及到了各方面。光对Function就分了Function Definitions、Arrow Function Definitions、Method Definitions、Generator Function Definitions、Class Definitions、Async Function Definitions、Async Arrow Function Definitions这几块。我准备花三章来介绍Function。这篇文章主要是理解ArrowFunction和GeneratorFunction,固然还包括最基本最普通的Function Definitions。segmentfault
在了解Function Definitions以前咱们须要知道函数对象(Function Object)。咱们都知道Function本质上也是一个对象,因此普通对象有的方法Function对象都有,此外Function对象还有本身的内部方法。全部Function对象都有一个[[Call]]的内部方法,有了这个方法Function对象才能被用做函数调用,即你***()时内部调用就是[[Call]]方法,固然不是全部有[[Call]]方法的Function对象均可以进行***()调用,常见的Map、Set等方法虽然有[[Call]]方法,可是你不能进行Map()和Set(),这时候就用到了Function对象的另外一个内部方法[[Construct]],当Function做为构造函数调用时,就会使用[[Construct]]方法。app
注意:不是全部Function对象都有[[Construct]]方法。只有当Function做为构造函数调用时,才会有[[Construct]]方法,好比ArrowFunction和GeneratorFunction只有[[Call]]方法没有[[Construct]]方法。函数
先说[[Call]]方法,看到这个名字很容易让人想起Function.prototype中的call方法,没错Function.prototype.call以及Function.prototype.apply都是显示的调用了[[Call]]方法,之因此说显示调用是相比于***()调用,call和apply要简单直接的多。[[Call]]方法接受两个参数,一个是thisArgument,另外一个是argumentsList,thisArgument即表示了function中的this对象,argumentsList表明了function中的参数列表,看!和Function.prototype.apply的调用方式是如此的类似。学习
function foo(){} foo(1,2) //当执行foo()方法时,实际上内部是以下调用 foo.[[Call]](undefined,« 1, 2 ») //« »表示ECMAScript的List规范类型 //注意,若是你是隐式调用function,那么thisArgument是undefined,不是常说的全局对象window, //只是在[[Call]]内部执行时检测到若是thisArgument是undefined或null, //且在非严格模式下才会变成全局对象,即foo(1,2)你能够认为等价与下面的: foo.call(null,1,2) foo.apply(undefined,[1,2]) //------------------- var a={ foo:function(){} } a.foo(1,2) //等价与==> foo.[[Call]](a,« 1, 2 ») //等价与==> foo.call(a,1,2) //等价与==> foo.apply(a,[1,2])
这里有个建议,之后你遇到this指向问题的时候,你把function转成call或者apply模式,你就能清楚的明白this指向什么。this
[[Construct]]内部方法主要有new关键字调用Function时才会执行[[Construct]]方法。[[Construct]]方法主要接受两个参数一个是argumentsList, 还有一个是newTarget。newTarget正常调用下指向调用的function对象。好比foo(),newTarget就是foo,你能够在函数内部用new.target访问到。构造函数中的this对象与newTarget有关,若是newTarget.prototype存在,且是Object对象,则this就是ObjectCreate(newTarget.prototype),ObjectCreate是Object.create内部调用的方法,若是newTarget.prototype不存在或者不是Object对象,this至关于ObjectCreate(Object.prototype)。spa
function Foo(){} var fooInstance = new Foo(1,2) //等价与==> var fooInstance = Foo.[[Construct]](« 1, 2 »,Foo); fooInstance instanceof Foo //true Object.create(Foo.prototype) instanceof Foo //true //注意若是构造函数有本身的return返回,那么状况有所不一样。 //返回的是Object,则构造函数的实例就是返回的对象 //返回的不是Object,至关于默认没有返回 function Foo(){ return {a:1}} var fooInstance = new Foo(1,2) fooInstance instanceof Foo //false,注意不是true,fooInstance不是Foo的实例 Object.create(Foo.prototype) instanceof Foo //true //只要Foo.prototype存在且是对象,那么Object.create(Foo.prototype)永远是Foo的一个实例
Function Definitions包含了FunctionDeclaration和FunctionExpression,有一些早期错误检测添加到Function Definitions中,其中在function中的let、const和var声明的变量规则参考上一篇文章var、let、const声明的区别,另外有一些附加的早期错误:prototype
function中的参数被认为是var声明,所以:code
function foo(a,b){ let a = 1; //SyntaxError,重复声明a } foo();
若是函数体是严格模式而参数列表不是简单参数列表,则语法错误:对象
//不是简单参数指的是包含解构赋值 function foo(a=1,...c){ 'use strict' //SyntaxError } //若是'use strict'在函数体外定义则没有错误 'use strict' function foo(a=1,...c){} //ok
函数体以及函数参数不能直接出现superblog
function foo(super){} //SyntaxError function foo(){ super();} //SyntaxError
FunctionDeclaration分为带变量名的函数声明以及匿名函数声明,匿名函数声明只能在export中可用,其它任何地方使用匿名函数声明都报错。
在进行评估脚本和函数的时候会对包含在其中的函数声明进行InstantiateFunctionObject方法,即初始化函数对象。注:该方法是在执行脚本和函数代码以前进行的。
InstantiateFunctionObject方法简单来讲作了三步:1.FunctionCreate 2.makeConstructor 3. SetFunctionName。分开说
function foo(a,b){}; foo.__proto__ === Function.prototype; foo.length === 2; foo.prototype.__proto__ === Object.prototype; foo.prototype.constructor === foo; foo.name === 'foo';
FunctionExpression也分为两类,有变量名的函数表达式和匿名函数表达式。函数表达式在执行时也会建立Function对象,步骤和函数声明类似。其中匿名函数表达式不会定义属性name,即不会执行第三步中的SetFunctionName。有变量名的函数表达式与函数声明以及匿名函数表达式的区别在于做用域链,咱们都知道一旦函数表达式中定义了变量名,咱们就能够在函数体内经过该变量名调用函数自身。可问题来了,该函数变量名是定义在哪里呢?函数外仍是在函数内呢?
var func = function foo(){}; foo(); //Uncaught ReferenceError: foo is not defined //显然没有在函数外定义函数表达式的变量名,那么是定义在函数内的? //我提到过在全局做用域和函数做用域中,var、function声明的变量,let和const不能重复声明。 var func = function foo(){ let foo = 1; //ok,可见函数表达式的变量名也不是在函数内声明的。 }; foo();
看到这可能有人会认为函数表达式的变量名可能容许let和const进行覆盖。其实不是,有变量名的函数表达式在建立Function对象的时候,建立了一个匿名做用域,在该做用域中定义了函数表达式的变量名。按上面这个例子,foo函数的外部做用域并非全局做用域,而是一个匿名做用域,匿名做用域的外部做用域才是真正的全局做用域。匿名函数表达式和函数声明都不会建立匿名做用域。
ArrowFunction(箭头函数)是ES6新增的一种新语法,主要是用来简化function的写法,更准确的说是简化匿名函数表达式的一种写法。所以匿名函数表达式的规则也适用于ArrowFunction,不过二者仍是有区别的,ArrowFunction中没有规定不能直接出现super,也就是说在ArrowFunction中能够用super方法,其次ArrowFunction内部没有[[Construct]]方法,所以不能做为构造器调用,因此在建立Function对象时不执行makeConstructor方法。最重要一点就是ArrowFunction没有本地的this对象。
咱们上面提道全部Function对象都有[[Call]]内部方法,接受this对象和参数列表两个字段。此外Function对象还有一个[[ThisMode]]内部属性,用来判断是ArrowFunction仍是非ArrowFunction,若是是ArrowFunction,那么无论[[Call]]中传来的this是什么都会被丢弃。此外arguments, super和new.target和this也是同样的。我在之前的文章中稍微提到过ArrowFunction中的this对象,我在这从新讲一下:
var name = 'outer arrow'; var obj = { name:'inner arrow', arrow: () => { console.log(this.name) } } obj.arrow(); //outer arrow,不是inner arrow
咱们在ArrowFunction遇到this对象时,你不要把this当作是ArrowFunction的一部分,你从ArrowFunction中拿出this放到ArrowFunction的外部,观察外部的this对象是什么,外部的this对象就是ArrowFunction的this对象。此外还要清楚不论是call仍是apply都是对ArrowFunction无效的,它们最终调用的都是[[Call]]内部方法,固然bind也是无效的。
咱们看一下ArrowFunction中的super应用,仍是改编了MDN中的例子:
var obj1 = { method() { console.log("method 1"); } } var obj2 = { method() { console.log("method 2"); return ()=>{super.method();} } } Object.setPrototypeOf(obj2, obj1); var arrow = obj2.method() //method 2 arrow(); //method 1
注意:method1和method2其实就是Method Definitions。
若是单看arrow这个函数,它自己是不可能有super的,由于没有任何继承关系,只是一个单一的ArrowFunction,可是你放在obj2中就有了意义,Object.setPrototypeOf(obj2, obj1);这句话把obj1变为obj2的原型对象,obj2继承了obj1的属性和方法,obj2的super对象就是obj1,所以ArrowFunction中的super参照this可知,该super是obj1。
总的一句话归纳ArrowFunction中的this,arguments, super和new.target都是经过原型链来查找的,不是动态建立的。
关于GeneratorFunction我不许备讲怎么用它,我只谈一下它的工做原理。说实话GeneratorFunction用到的状况实在太少了,我本身在作项目的时候基本不会用到GeneratorFunction,但这不妨碍咱们学习GeneratorFunction。
GeneratorFunction也是Function的一种,Function的规则也适用于GeneratorFunction,此外在GeneratorFunction的参数中不能出现yield表达式。
GeneratorFunction与普通的Function同样,都会建立Function对象,可是区别也在这里,上面提到了Function的[[Prototype]]原型值是Function.prototype,可是GeneratorFunction不一样,它的[[Prototype]]原型值是%Generator%,此外Function的prototype属性是ObjectCreate(Object.prototype),可是GeneratorFunction的倒是ObjectCreate(%GeneratorPrototype%)并且prototype中没有constructor属性,不能做为构造器调用。
注:Function.prototype也写做%FunctionPrototype%,Object.prototype也写做%ObjectPrototype%。%Generator%和%GeneratorPrototype%没有全局名称,不能直接访问。
执行函数时,内部调用了[[Call]]方法,可是和Function不一样的是GeneratorFunction返回的不是函数的执行结果,而是一个对象,这个对象是GeneratorFunction的一个实例,这跟[[Construct]]方法很像。
function* gen(){} gen(); //返回的实际上是Object.create(gen.prototype)对象。
你能够比较gen()和Object.create(gen.prototype)这两个对象,你会发现它们很像,只是Object.create(gen.prototype)缺乏了Generator对象的一些内部状态。能够说虽然GeneratorFunction没有[[Construct]]方法,不能做为构造器调用,可是你能够认为GeneratorFunction自己就是一个构造器。
此外在建立GeneratorFunction对象时,还作了一些其余操做,咱们在之前的文章中提到了执行上下文,GeneratorFunction对象有个[[GeneratorContext]]内部插槽,当评估GeneratorFunction定义的代码时,GeneratorFunction对象把当前正在运行的执行上下文存在了[[GeneratorContext]]中,并挂起了该正在运行的执行上下文,所以对GeneratorFunction中代码的评估被暂停了,从而执行其它代码,当你调用GeneratorFunction对象的next方法时,他把[[GeneratorContext]]中保存的执行上下文取出放到执行上下文栈顶部,成为正在运行的执行上下文,此时GeneratorFunction中暂定评估的代码又从新开始执行,直到执行完毕或者遇到yield表达式。当遇到yield表达式时,它又把正在运行的执行上下文从栈中移除,暂停对GeneratorFunction代码的执行,等待下次next方法调用以后继续执行。
简单来讲GeneratorFunction的实现原理实际上是运行的执行上下文之间不停来回切换。
GeneratorFunction基本就提到这里了,最后附上ECMAScript关于GeneratorFunction的一张关系图片:
关于Function的简单介绍就暂时告一段落,在下一篇文章中我会来简单介绍Promise和AsyncFunction。