新版JavaScript的箭头函数

据统计,箭头函数(arrow functions)是新版JS(ES6)最受欢迎的功能之一,并且已经得到各大主流现代浏览的支持。

本文看看箭头函数到底是什么,怎么用法,最常见的使用情景(use cases),还有它的不足或使用陷阱。

本文略译自《ES6 Arrow Functions: Fat and Concise Syntax in JavaScript》。

arrow-functions

什么是箭头函数?

「箭头函数」其实只是「函数表达式」(function expressions)的精简写法,所以熟悉函数表达式的使用的,对「箭头函数」意义和使用场景就不陌生。箭头函数使用了一个新语法符号=>,看着像一个“胖”箭头,所以也被一些人戏称为“胖箭头函数”。

箭头函数的设计思想是让「函数表达式的定义」更加的精简,便于某些代码的编写和阅读,这种精简写法包括不写函数名,不写function关键字和return关键字,某些情况连花括号{}也可省略。函数的常规属性,例如作用域(scope),this都做了特殊处理(如何处理?)。

使用箭头函数

箭头函数有好一些写法,MDN有一个详细列表。作为介绍,这里会讲几种最常见的写法。为了便于理解,我们将ES5版的函数表达式,改用ES6版箭头函数来定义,对比更生动具体。

多个参数的基本写法

我们写一个简单的乘法函数,有两个输入参数:

// (param1, param2, paramN) => expression

// ES5
var multiplyES5 = function(x, y) {
  return x \* y;
};

// ES6
const multiplyES6 = (x, y) => { return x \* y };

我们可以看到,同一个函数,箭头函数的写法省了将近一半的字(更少的行,和字符)。

如果函数的计算任务非常简单,只有一条语句,那花括号也可以省略:

const multiplyES6 = (x, y) => x * y;

一个参数的基本写法

如果函数只有一个参数,那么参数和括号也是可选的,例如:Parentheses are optional when only one parameter is present

//ES5
var phraseSplitterEs5 = function phraseSplitter(phrase) {
  return phrase.split(' ');
};

//ES6
const phraseSplitterEs6 = phrase => phrase.split(" ");

console.log(phraseSplitterEs6("ES6 Awesomeness")); // ["ES6", "Awesomeness"]

无参数

如果函数没有参数,为了标识,参数表达的括号必须写上:

//ES5
var docLogEs5 = function docLog() {
    console.log(document);
};

//ES6
var docLogEs6 = () => { console.log(document); };
docLogEs6(); // #document... <html> ….

创建对象实例的写法

上面几种是最简单的写法,针对简单计算任务,例如返回简单的计算结果,而箭头函数也可以用来创建对象——通过对象字面量。唯一需要注意的是,对象字面量必须用括号(())括起来,不然无法辨别箭头函数定义的是对象字面量,还是一个代码块,因为它们都使用了花括号({})。

//ES5
var setNameIdsEs5 = function setNameIds(id, name) {
  return {
    id: id,
    name: name
  };
};

// ES6
var setNameIdsEs6 = (id, name) => ({ id: id, name: name });

console.log(setNameIdsEs6 (4, "Kyle"));   // Object {id: 4, name: "Kyle"}

箭头函数的使用场景

了解了箭头函数的基本写法,我们看看箭头函数一般用在哪里。

应该说任何使用「函数表达式」——完成一项功态的任务较小的计算任务的——场景都适用箭头函数,用其表达调优,尤其是函数任务量较简小的。

高阶函数

一种常见「函数表达式用例」就是高阶函数。而高阶函数常见例子就是数组的叠代处理高阶extra,例如 映射转换(map)或者数组归并(reduce),看如下一个简单的**“手机价格”数组**:

const smartPhones = [
  { name:'iphone', price:649 },
  { name:'Galaxy S6', price:576 },
  { name:'Galaxy Note 5', price:489 }
];

我们使用数组映射转换(map)方法将这个数组分拆为一个手机类型或手机价格的独立的数组(这里只创建价格数组):

// ES5
var prices = smartPhones.map(function(smartPhone) {
  return smartPhone.price;
});

console.log(prices); // [649, 576, 489]

代码逻辑很简单,叠代映射原对象数组元素的价格(smartPhone.price)属性作为新数组(prices)的成员。改用箭头函数则表达更简洁:

// ES6
const prices = smartPhones.map(smartPhone => smartPhone.price);
console.log(prices); // [649, 576, 489]

再看一个数组过滤的例子(找出是3的倍数的所有值):

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

// ES5
var divisibleByThrreeES5 = array.filter(function (v){
  return v % 3 === 0;
});

// ES6
const divisibleByThrreeES6 = array.filter(v => v % 3 === 0);

console.log(divisibleByThrreeES6); // [3, 6, 9, 12, 15]

异步回调处理

异步代码常常有大量微小函数(每一个都有一对function return ) ,尤其是promise(包括定义和链式多步处理)。以下是MSDN文档中的一个promise链式多步处理的一个例子:

// ES5
aAsync().then(function() {
  returnbAsync();
}).then(function() {
  returncAsync();
}).done(function() {
  finish();
});

使用箭头函数将变得更简单易读(虽然有争议):

// ES6
aAsync().then(() => bAsync()).then(() => cAsync()).done(() => finish);

作为异步编程的演练场,箭头函数给NodeJS 开发带来同样的便利。

箭头函数对多步嵌套的异步回调的额外补益

相信有很多人有过多层嵌套函数表达式「丢this」的经验,在过去ES5,为了维护函数对象上下文,我们必须有手动修正this(函数的父对象/父环境)——通用函数对象API( .bind method)或手动创建函数闭包(using var self = this;)。

新版箭头函数自动为我们维护了函数对象父环境,所以不必手动了。看Jack Franklin的一个展示例子

当然,如果我们希望灵活的使用函数对象,动态修改函数的this,可以继续使用函数表达式,否则使用箭头函数吧。

箭头函数的使用陷阱

新的箭头函数写法给开发带来了便利,然而,任何新技术引进新功能同时,新技术也会引入新缺陷或陷阱,需要额外的注意。

Kyle Simpson,一名JS开发者,技术作者(网络畅销书《You-Dont-Know-JS》),画了一张流程图说明在决定具体使用箭头函数的形式上有太多分叉路,这让人很困惑;其他人则表示,箭头函数省掉很多字,代价反而让代码更难读。functionreturn语句对于多层嵌套的函数任务,有提高可读性效果。

开发者主观差异会对任务东西产生分歧,箭头函数当不例外。为简单起见,以下几点是你在使用箭头函数时应该注意的地方:

关于this

前面说了,箭头函数的this与普通函数不同,它不能被修改(通用函数对象API),事实上似乎没必要修改(针对箭头函数设计的计算任务),如果要动态修改this,改用函数表达式。

对象构造器

箭头函数不能用作类对象的构造器,如果你new 一个箭头函数,运行时会报错。想创建类对象,使用 ES6 classes

Generators

Arrow functions are designed to be lightweight and can’t be used as generators. Using the yield keyword in ES6 will throw an error. Use ES6 generators instead.

Arguments object

箭头函数没有隐含的 arguments 对象,不像一般的函数对象。 arguments 对象是一个类数组(array-like )对象,目的是为了让函数支持动态的输入参数。箭头函数没有这个对象。

箭头函数有多常用?

新版箭头函数被认为是ES6使用最多的新技术,那箭头函数适用哪些地方呢? Lars Schöning 总结了他的团队经验,提出使用箭头函数的策略:

  • 第一,全局静态的通用函数,和父类功能扩展,使用声明式函数;
  • 第二,类对象构造器,使用class;
  • 第三,其它地方都可以使用 箭头函数。

(第四)就像 let and const 成为局部变量新的默认定义方式一样,箭头函数也成为默认的函数定义方式,除非真必要使用函数表达式(例如动态this的需要)或声明式函数。

为了证明箭头函数的实用性, Kevin Smith多个流行的代码库/应用框架的函数表达的使用式进行统计,发现55%的函数表达可改用箭头函数。

箭头函数早放入开发者的工具箱,它便捷,强大,很多开发者喜爱它,你不能错过!

裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.