好程序员web前端分享详细了解JavaScript函数

  1. 云栖社区>
  2. 博客>
  3. 正文

好程序员web前端分享详细了解JavaScript函数

好程序员 2019-04-10 17:53:26 浏览363
展开阅读全文

好程序员web前端分享详细了解JavaScript函数,如果你曾经接触过JavaScript编程,你一定不会陌生如何定义并且调用一个函数。但是你知道在JavaScript中有多少种定义函数的方法吗?如果想要在Test262中编写和维护这些方法的测试,那可真是一个很大的挑战,尤其当一些新特性和现有函数语法相关,或者扩展了函数的API时。但是,想要断言新提出或被提案的语法、API有效时,测试所有既存变式又是非常必要的。 下面会针对JavaScript中已经存在的函数定义方式进行一个概述。本文不包含Class声明和表达式,因为这些方式创建的对象是“不可调用的”,本文旨在那些可生成“可调用”对象的函数定义方式。也就是说,我们不会研究那些复杂的参数列表(包含默认参数、结构赋值或者尾后逗号),因为那足够另起文章介绍了。

以前的方式

函数声明以及函数表达式

最为出名以及应用最广的同样也是这些旧方式:函数声明和函数表达式。前者设计(1995)和出现在第一版的规范(1997)(pdf)中。后者则是出现在第三版中(1999)(pdf)。仔细研究,你会从它们当中提取出三种不同的方式。

// 函数声明
function BindingIdentifier() {}

// 命名函数表达式
// (BindingIdentifier在函数外部是访问不到的)
(function BindingIdentifier() {});

// 匿名函数表达式
(function() {});
值得注意的是,匿名函数表达式仍然可能有名字,Mike Pennisi在什么是函数名称?中有深度解释。

Function构造函数

当研究一门语言的"function API"的时候,也就到了这门语言的底层。在这门语言的设计之初,函数声明方式可以被理解为是Function构造函数API的最直接实现。Function构造函数提供了一种定义函数的方式:通过指明Function的参数,其中最后一个参数就是函数的函数体(必须要说明的是,这是一种动态代码方式,可能存在安全问题)。在大多数情况下,这种方式是不合适的,所以用的人很少,但是在第一版的ECMAScript中,这种方式就出现了。

new Function('x', 'y', 'return x ** y;');
新的方式

自从ES2015发布以来,几种新的定义函数方式被引入进来,这些方式的变式更是非常繁多。

另类匿名函数

这是一种新式的匿名函数。如果你曾经接触过ES的模块化,那么你很有可能已经接触过这种定义函数的方式了。尽管这种方式看起来和匿名函数的定义方式很像,但是他确实有自己的名字:default

// 另类匿名函数声明
export default function() {}
顺便一提,这个名字并不是专属的标识,并没有进行绑定。

方法定义

下面这些方式定义的函数表达式、匿名函数或者命名函数,都是某个对象的属性。注意这些并不是新的语法,只是应用上面提及的那些语法,写在了某个对象的初始化器中。这种方式最早引入在ES3中。

let object = {
propertyName: function() {},
};
let object = {
// (BindingIdentifier不能再函数外部调用)
propertyName: function BindingIdentifier() {},
};
下面是存取器属性,引入在ES5。

let object = {
get propertyName() {},
set propertyName(value) {},
};
在ES2015中,JavaScript中提供了一种定义方法的简洁语法,不管是直接命名的方式还是计算属性名的方式,都可以使用,而且,存取器同样适用。

let object = {
propertyName() {},
["computedName"]() {},
get ["computedAccessorName"]() {},
set "computedAccessorName" {},
};
你也可以把这些定义属性或者方法的新方式应用在创建类时。

// 类声明
class C {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set "computedAccessorName" {}
}

// 类表达式
let C = class {
methodName() {}
["computedName"]() {}
get ["computedAccessorName"]() {}
set "computedAccessorName" {}
};
...在定义静态方法时,同样可以使用。

// 类声明
class C {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set "computedAccessorName" {}
}

// 类表达式
let C = class {
static methodName() {}
static ["computedName"]() {}
static get ["computedAccessorName"]() {}
static set "computedAccessorName" {}
};
箭头函数

箭头函数首次出现在ES2015中,尽管起初饱受争议,但是现在已经被广泛应用了。箭头函数的定义根据是否简写有两种不同的语法:赋值表达式(在箭头后面没有花括号)和函数体(当函数包含零个或者多个表达式时)。语法规定,当函数只有一个参数时,可以不用小括号括起来,但是当没有参数或者多余一个参数时,就必须用小括号括起来了。(这种语法就决定了箭头函数会有多种定义形式)

// 零参数, 赋值表达式
(() => 2 ** 2);

// 一个参数, 可以省略小括号, 赋值表达式
(x => x ** 2);

// 一个参数, 可以省略小括号, 函数体
(x => { return x ** 2; });

// 多个参数, 赋值表达式
((x, y) => x ** y);
上面的最后一种形式中,参数是用参数列表来表示的,因为它们用小括号括了起来。类似于用小括号来标识参数列表的语法,还有其他形式,诸如({ x }) => x。 如果参数不用小括号括起来,那么只能给参数起一个独一的标识符名称,以在箭头函数中使用。当箭头函数被定义为异步函数或者Generator函数时,这个标识符名称还可以加上await 或者 yield的前缀,但那也已经是在不用括号情形中,考虑足够深远的了。 箭头函数可以并且也经常出现在初始化器或者对象属性的定义中,但是这种情况大部分使用的是上面介绍的赋值表达式形式,举例如下:

let foo = x => x ** 2;

let object = {
propertyName: x => x ** 2
};
Generators

Generator函数的语法是在其他定义函数的方式上加点东西,但箭头函数和存取器方法除外。你可以使用和之前函数声明,函数表达式,函数定义甚至是构造函数等相似的方式。所有方法列举如下:

// Generator 声明
function *BindingIdentifer() {}

// 另类匿名 Generator 声明
export default function *() {}

// Generator 表达式
// (BindingIdentifier只能在函数内部调用)
(function *BindingIdentifier() {});

// 匿名 Generator 表达式
(function *() {});

// 方法定义
let object = {
*methodName() {},
*["computedName"]() {},
};

// 在类声明中定义方法
class C {
*methodName() {}
*["computedName"]() {}
}

// 在类声明中定义静态方法
class C {
static *methodName() {}
static *["computedName"]() {}
}

// 在类表达式中定义方法
let C = class {
*methodName() {}
*["computedName"]() {}
};

// 在类表达式中定义静态方法
let C = class {
static *methodName() {}
static *["computedName"]() {}
};
ES2017

异步函数

经过几年的发展,异步函数将会发布ES2017——第八版EcmaScript语言规范——规范会在2017年6月在正式发布。但其实,很多开发者早已经开始使用异步函数了,这还要归功于Babel的支持。 异步函数语法提供了一个干净的、统一的方式来描述异步操作。当被调用时,异步函数会返回一个Promise对象。当异步执行结束后,这个Promise对象即会被相应执行。当函数中含有await表达式时,异步函数就会暂停执行,这时,await表达式结果就会作为异步函数的返回值。 异步函数的语法并没有太多的不同,只是在我们熟知的那些方式前面加上一个前缀:

// 异步函数声明
async function BindingIdentifier() { /**/ }

// 另类匿名异步函数声明
export default async function() { /**/ }

// 命名异步函数表达式
// (BindingIdentifier只能在函数内部调用)
(async function BindingIdentifier() {});

// 匿名异步函数表达式
(async function() {});

// 异步方法
let object = {
async methodName() {},
async ["computedName"]() {},
};

// 类声明中的异步方法
class C {
async methodName() {}
async ["computedName"]() {}
}

// 类声明中的静态异步方法
class C {
static async methodName() {}
static async ["computedName"]() {}
}

// 类表达式中的异步方法
let C = class {
async methodName() {}
async ["computedName"]() {}
};

// 类表达式中的静态异步方法
let C = class {
static async methodName() {}
static async ["computedName"]() {}
};
异步箭头函数

async 和 await并不是只局限在常规函数的声明或者表达式中,它们同样适用于箭头函数:

// 单一参数,赋值表达式
(async x => x ** 2);

// 单一参数,函数体
(async x => { return x ** 2; });

// 参数列表,赋值表达式
(async (x, y) => x ** y);

// 参数列表,函数体
(async (x, y) => { return x ** y; });
与ES2017结合

异步Generator函数

和ES2017结合,async 和 await将会被扩展支持异步函数。这一特性的发展可以追踪推荐的github仓储。正如你猜想到的,异步Generator函数的语法是结合async、await以及已经存在的Generator函数声明和表达式而来。当异步Generator函数被调用的时候,会返回一个迭代器,这个迭代器的next()方法会返回一个Promise对象来处理迭代器的返回对象,而不是直接返回迭代器的结果对象。 异步Generator函数已经开始在很多地方使用,你很有可能已经碰到过。

// 异步Generator声明
async function BindingIdentifier() { /*/ }

// 另类匿名异步Generator声明
export default async function *() {}

// 异步Generator表达式
// (BindingIdentifier只能在函数内部访问)
(async function *BindingIdentifier() {});

// 匿名异步Generator表达式
(async function *() {});

// 匿名异步Generator方法定义
let object = {
async *propertyName() {},
async *["computedName"]() {},
};

// 类声明中异步Generator原型方法定义
class C {
async *propertyName() {}
async *["computedName"]() {}
}

// 类表达式中异步Generator原型方法定义
let C = class {
async *propertyName() {}
async *["computedName"]() {}
};

// 类声明中异步Generator静态方法定义
class C {
static async *propertyName() {}
static async *["computedName"]() {}
}

// 类表达式中异步Generator静态方法定义
let C = class {
static async *propertyName() {}
static async *["computedName"]() {}
};

网友评论

登录后评论
0/500
评论
好程序员
+ 关注