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。

Function

在了解Function Definitions之前我们需要知道函数对象(Function Object)。我们都知道Function本质上也是一个对象,所以普通对象有的方法Function对象都有,此外Function对象还有自己的内部方法。所有Function对象都有一个[[Call]]的内部方法,有了这个方法Function对象才能被用作函数调用,即你***()时内部调用就是[[Call]]方法,当然不是所有有[[Call]]方法的Function对象都可以进行***()调用,常见的Map、Set等方法虽然有[[Call]]方法,但是你不能进行Map()和Set(),这时候就用到了Function对象的另一个内部方法[[Construct]],当Function作为构造函数调用时,就会使用[[Construct]]方法。

注意:不是所有Function对象都有[[Construct]]方法。只有当Function作为构造函数调用时,才会有[[Construct]]方法,比如ArrowFunction和GeneratorFunction只有[[Call]]方法没有[[Construct]]方法。

[[Call]]

先说[[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指向什么。

[[Construct]]

[[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)。

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

Function Definitions包含了FunctionDeclaration和FunctionExpression,有一些早期错误检测添加到Function Definitions中,其中在function中的let、const和var声明的变量规则参考上一篇文章var、let、const声明的区别,另外有一些附加的早期错误:

function中的参数被认为是var声明,因此:

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

函数体以及函数参数不能直接出现super

function foo(super){}   //SyntaxError

function foo(){ super();} //SyntaxError

FunctionDeclaration

FunctionDeclaration分为带变量名的函数声明以及匿名函数声明,匿名函数声明只能在export中可用,其它任何地方使用匿名函数声明都报错。
在进行评估脚本和函数的时候会对包含在其中的函数声明进行InstantiateFunctionObject方法,即初始化函数对象。注:该方法是在执行脚本和函数代码之前进行的。
InstantiateFunctionObject方法简单来说做了三步:1.FunctionCreate 2.makeConstructor 3. SetFunctionName。分开说

  1. FunctionCreate创建了一个Function对象F,包括初始化内部插槽的值,比如上面提到的[[Call]],[[Construct]]方法的定义,原型对象[[Prototype]]的值,这里指的是Function.prototype,F的length属性,指function的参数个数。
  2. makeConstructor(F),这句话不是指创建构造器,这里指定义了F的prototype属性的值,以及prototype中constructor的值,普通函数prototype相当于object.create(Object.prototype),constructor===F。
  3. SetFunctionName,定义了F的name属性,默认是声明的函数名,匿名函数是default。
function foo(a,b){};

foo.__proto__ === Function.prototype;
foo.length === 2;
foo.prototype.__proto__ === Object.prototype;
foo.prototype.constructor === foo;
foo.name === 'foo';

FunctionExpression

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

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。
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。

javascript中Function、ArrowFunction和GeneratorFunction介绍的更多相关文章

  1. Javascript中Function,Object,Prototypes,__proto__等概念详解

    http://anykoro.sinaapp.com/2012/01/31/javascript%E4%B8%ADfunctionobjectprototypes__proto__%E7%AD%89% ...

  2. 转载 javascript中(function($){...})(jQuery)写法是什么意思

    javascript中(function($){...})(jQuery)写法是什么意思   这里实际上是匿名函数function(arg){...}这就定义了一个匿名函数,参数为arg 而调用函数 ...

  3. javascript中 (function(){})();如何理解?

    javascript中 (function(){})();如何理解? javascript中: (function(){})()是匿名函数,主要利用函数内的变量作用域,避免产生全局变量,影响整体页面环 ...

  4. 全面理解Javascript中Function对象的属性和方法

    http://www.cnblogs.com/liontone/p/3970420.html 函数是 JavaScript 中的基本数据类型,在函数这个对象上定义了一些属性和方法,下面我们逐一来介绍这 ...

  5. 【转】javascript 中that的含义示例介绍

    var that = this;,这代表什么意思呢?this代表的是当前对象,var that=this就是将当前的this对象复制一份到that变量中,下面为大家介绍这样做有什么意义 你可能会发现别 ...

  6. JavaScript中Function原型及其prototype属性的简单应用

    大家都知道在JavaScript中是没有类的概念的,但是却是有对象的概念的.有的人可能理解对象和类有些迷糊,这里简单的概括一下他们之间的区别: 类:抽象的概念,例如人,动物,汽车等都可以抽象成一个类 ...

  7. JavaScript中Function函数与Object对象的关系

    函数对象和其他内部对象的关系 除了函数对象,还有很多内部对象,比如:Object.Array.Date.RegExp.Math.Error.这些名称实际上表示一个 类型,可以通过new操作符返回一个对 ...

  8. JavaScript中function的多义性

    JavaScript 中的 function 有多重意义.它可能是一个构造器(constructor),承担起对象模板的作用: 可能是对象的方法(method),负责向对象发送消息.还可能是函数,没错 ...

  9. JavaScript中Function的拓展

    Function 是什么东西,就是JavaScript中的顶级类,系统级别的类.我们平时写的函数方法例如下. function Animal() { } Animal就是Function的实例,但是在 ...

随机推荐

  1. mysql--使用left join条件查询时加where条件的问题

    场景:为一个活动添加指导文件,每一个活动会对应多种指导文件类型,进入每一个活动的设置指导文件页面后所呈现的指导文件类型相同,查询时,使用指导文件类型表LEFT JOIN指导文件表,要求指导文件类型全部 ...

  2. spark 独立应用编程之 Java 编程

    文章更新于:2020-04-03 按照惯例,文件附上链接放在文首. 文件名:apache-maven-3.6.3-bin.tar.gz 文件大小:9.1 MB 下载链接:https://www.lan ...

  3. Google GMS介绍

    Google GMS介绍GMS全称为GoogleMobile Service.GMS目前提供有Search.Search by Voice.Gmail.Contact Sync.Calendar Sy ...

  4. 多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用

    如果大家读过dapper源码,你会发现这内部有很多方法都用到了yield关键词,那yield到底是用来干嘛的,能不能拿掉,拿掉与不拿掉有多大的差别,首先上一段dapper中精简后的Query方法,先让 ...

  5. k8s pod yaml参数说明

  6. Java创建线程的三种形式的区别以及优缺点

    1.实现Runnable,Callable Callable接口里定义的方法有返回值,可以声明抛出异常. 继承Callable接口实现线程 class ThreadCall implements Ca ...

  7. Java面向对象的总结

    面向对象的程序设计 1.面向对象 核心:以类的方式组织代码,以对象的方式封装数据 比喻:也就是说类是没有数据的,给了数据之后的类就是对象 封装 继承 多态 2.方法 a.一个方法只有一个返回值,只有一 ...

  8. AJ学IOS 之BLOCK的妙用_利用block实现链式编程

    AJ分享,必须精品 一:场景 我们有个对象人,他有两个方法,一个是学习study,一个是跑步run, 这个人有个怪癖,跑完步之后必须学习,为了实现这个方法并且能调用方便,我们让跑步和学习都回返回自己这 ...

  9. 概率专题_概率/ 数学_基础题_ABEI

    上周三讲了概率和概率dp.如果没有涉及其他综合算法,概率这种题主要是思维,先把这部分的东西写完 给个题目链接:https://vjudge.net/contest/365300#problem Hea ...

  10. Three.js如何选中外部模型

    1.问题 three.js中模型选中使用的是射线法,根据摄像机角度,鼠标点击位置和模型选中的distance参数判断来选中模型.对于原生的矢量模型完全没有问题,但是当遇到导入的外部模型,如obj.st ...