原文链接:5 Best Practices to Write Quality Arrow Functions, by Dmitri Pavlutin

箭头函数值得流行。它语法简洁,函数体里的 this 是词法环境提供的,非常适合用作回调函数。

本篇文章,你讲学习到 5 个 编写高质量箭头函数的最佳实践。

1. 箭头函数名推理

JS 中的箭头函数是 匿名的(anonymous):即函数的 name 属性是个空字符串 ''

( number => number + 1 ).name; // => ''
复制代码

匿名函数在调试会话(debug session)或调用栈分析(call stack analysis)时被标记为 anonymous。不幸的是,匿名函数并不能为调试程序带来有用的线索。

下面展示了执行匿名函数代码时的一个调试会话界面:

右侧的调用栈中(call stack)中包含两个被标记为 anonymous 的函数。这类调用栈信息无法提供任何有用的线索。

幸运的是,箭头函数名推理(function name inference,ES2015 提供的一个特性)可以在某些条件下检测到函数名称。名称推理的原理是 JS 可以从其语法位置确定箭头函数名称:例如,从保存函数对象的变量名称中获得。

我们来看看,箭头函数名推理的例子:

const increaseNumber = number => number + 1;

increaseNumber.name; // => 'increaseNumber'
复制代码

increaseNumber 是保存箭头函数对象的一个变量,JS 认为 'increaseNumber' 可以作为箭头函数的名字,原始箭头函数就使用了这个名字。

“一个最佳实践是使用函数名推理来命名箭头函数。”

现在再来看看使用了名称推理的调试会话界面:

因为箭头函数具有名称,所以调用堆栈能提供正在执行的代码的更多信息:

  • handleButtonClick 表明发生了一个点击事件
  • increaseCounter 增加了计数器变量的值

2. 尽可能使用行内形式

行内函数(inline function)是指仅包含一个表达式的函数。我喜欢箭头函数的行内书写方式。

举个例子:

const array = [1, 2, 3];

array.map((number) => { 
  return number * 2;
});
复制代码

当箭头函数里只有一个表达式的时候,我们不使用这种长形式(logn form),而是把括号 {}return 删掉。

const array = [1, 2, 3];

array.map(number => number * 2);
复制代码

这就是我的建议:

“当箭头函数只包含一个表达式的时候,一种好的做法是使用箭头函数的行内形式。”

3. 胖箭头和比较运算符

比较运算符 ><<=>= 跟胖箭头 =>(定义箭头函数时使用的)很像。

当这些比较运算符跟行内箭头函数一起使用的时候,会有点混乱。

举个例子:

const negativeToZero = number => number <= 0 ? 0 : number;
复制代码

一行里的 => 和 <= 容器产生误导。

为了清楚地将胖箭头与比较运算符区分开,第一种选择是将表达式包装在一对括号中:

const negativeToZero = number => (number <= 0 ? 0 : number);
复制代码

第二个选择是故意使用箭头函数的长写法形式:

const negativeToZero = number => {
  return number <= 0 ? 0 : number;
};
复制代码

重构之后的代码可以消除胖箭头与比较运算符之间的混淆。

如果箭头函数中包含使用 ><<=>= 运算符的表达式,一个好的办法是将表达式包装在一对括号中,或者使用箭头函数的长写法形式。

4. 正确返回对象字面量

行内箭头函数中的对象字面量会触发语法错误:

const array = [1, 2, 3];

// throws SyntaxError!
array.map(number => { 'number': number });
复制代码

JS 把花括号当做代码块,而非对象字面量了。

用一对括号包装对象字面量就能解决问题:

const array = [1, 2, 3];

// Works!
array.map(number => ({ 'number': number }));
复制代码

如果对象字面量中包含很多属性,还可以使用换行符,同时还能保持箭头函数的行内形式:

const array = [1, 2, 3];

// Works!
array.map(number => ({
  'number': number
  'propA': 'value A',
  'propB': 'value B'
}));
复制代码

我的建议:

“在行内箭头函数中使用时,将对象字面量包装在一对括号中。”

5. 避免过多的嵌套

箭头函数的语法很短、很好。 但副作用是,当过多嵌套箭头函数使用时,代码可能就变得晦涩难懂了。

让我们考虑以下情况——单击按钮,将发起一个对服务器的请求,响应准备就绪后,将返回数据记录在控制台:

myButton.addEventListener('click', () => {
  fetch('/items.json')
    .then(response => response.json())
    .then(json => {
      json.forEach(item => {
        console.log(item.name);
      });
    });
});
复制代码

箭头函数是 3 级嵌套的。为了看懂这样的代码,需要花些时间和精力。

为了提高嵌套函数的可读性,第一种方法是将每个箭头函数保存在一个变量中。该变量应简明地描述函数的功能。

const readItemsJson = json => {
  json.forEach(item => console.log(item.name));
};

const handleButtonClick = () => {
  fetch('/items.json')
    .then(response => response.json())
    .then(readItemsJson);
};

myButton.addEventListener('click', handleButtonClick);
复制代码

重构之后,我们使用了两个变量 readItemsJsonhandleButtonClick 保存箭头函数,嵌套级别从 3 减少到 2。现在,可以更轻松地了解代码功能了。

进一步,你还可以可以 async/await 语法重构整个函数,这也是解决函数嵌套的一个好方法:

const handleButtonClick = async () => {
  const response = await fetch('/items.json');
  const json = await response.json();
  json.forEach(item => console.log(item.name));
};

myButton.addEventListener('click', handleButtonClick);
复制代码

我的建议:

通过将箭头函数保存在变量中的方式,可以避免代码的过多嵌套;同时,使用 async/await 语法,也可以避免代码的过多嵌套。

总结

JS 中的箭头函数是匿名的。为了使调试高效,一个好的实践是使用变量来保存箭头函数。如此,就能使用箭头函数名推理特性了。

当函数主体只有一个表达式时,使用行内箭头函数非常方便。

比较运算符 ><<=>= 跟胖箭头 => 很像,使用行内箭头函数时,可以将表达式包装在括号中帮助区分。

行内箭头函数会把对象字面量语法 { prop: 'value' } 外的 {} 认作是代码块。因此,在行内箭头函数中使用对象字面量时,需要使用括号包装一下:() => ({ prop: 'value' })

最后,过多的箭头函数嵌套会掩盖代码意图。一种办法是将箭头函数提取到变量中。或者尝试使用更好的特性,例如 async/await 语法。

(正文完)


广告时间(长期有效)

我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个爱猫人士并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。

(完)