[译]ES6 的箭头函数真那么好用吗

1,940 阅读5分钟
原文链接: igeekbar.com

想当年90年代末我学习JavaScript的时候,我们都是用function关键字来编写第一个Hello World函数:

function helloWorld() {
  return ‘Hello World!’;
}

然而现在几乎所有年轻开发者都这么写:

const helloWorld = () => 'Hello World!';

这是在ES2015(又称ES6)标准中新添加的函数表达式。它看上去真的很棒,1行就完成了以往3行的代码。它使用了ES2015最热的特性之一——箭头函数。

当我刚看到这种写法的时候,我是这个表情:

凭借着近20年的开发经验和使用ES2015标准做了很多项目之后,我最终选择用这种方式来定义“Hello World”函数:

function helloWorld() {
  return ‘Hello World!’;
}

你可能会质疑我:为什么不用箭头函数?一行就可以搞定你干嘛用三行?多写那几个字符多麻烦!

我非常喜欢箭头函数,真的喜欢。但是当我在定义一个全局的或者通用的函数的时候,我还是会使用以前的定义方式。

Martin的这句话可以解释为什么:

“……我们读代码的时间和写代码的时间比率是10:1。这意味着我们大部分时间都在阅读老代码,以便于之后新代码的编写。因为读代码占得比重太大了,因此我们希望在阅读代码的时候能够更加轻松,即便在编写代码的时候需要费点劲。“ ——Robert C. Martin《整洁的代码:快速软件开发指南》

与箭头函数相比,用function来定义函数有以下两个优点:

1、意图明确

2、声明顺序即执行顺序

意图明确

当你阅读成百上千行代码的时候,快速地明白代码作者的意图是非常重要的,越快越好,越简单越好。看看下面这个例子:

const maxNumberOfItemsInCart = ...;

在你读到这一行的时候你根本不知道这三个点代表什么。它可能是:

const maxNumberOfItemsInCart = 100;

也可能是:

const maxNumberOfItemsInCart = (statusPoints) => statusPoints * 10;

如果你使用function来表示的话那么就不会产生上述疑惑了。比较一下下面这两段代码:

const maxNumberOfItemsInCart = 100;
function maxNumberOfItemsInCart(statusPoints) {
  return statusPoints * 10;
}

你会发现代码作者的意图非常明确, 当你看到第一行的时候就知道作者要做些什么

你可能会说使用带有代码提示和颜色区分的编辑器可以解决这个问题,或者你是一个可以快速理解代码的人,或者你觉得这根本不算问题。

当然,箭头函数的简洁的确令人向往。事实上,如果可读性是我选用function的唯一理由,我其实完全可以说服我自己适应这一变化。然而这并不是我选择function的唯一理由。

声明顺序即执行顺序

最理想的情况下,我希望我声明代码的顺序就正好是它们执行的顺序。然而ES6中的函数声明方法并没有实现我的想法。因为const关键字声明的任何值在被执行之前都是不可获取的。这意味着:你不声明一个const常量的话你就不能使用它。

下面这段代码会报错:

sayHelloTo(‘Bill’);
const sayHelloTo = (name) => `Hello ${name}`;

因为当JavaScript引擎在读取这段代码的时候,它会 绑定”sayHelloTo”函数而不会初始化它

换句话说,JavaScript绑定了“sayHelloTo“——读取它并在内存中创建一个空间来存储它的值,但是会拒绝对于该空间的访问直到这段代码被执行。它从被绑定到被执行的这段时间称为 暂时死区(temporal dead zone, TDZ)。

如果你不用Babel将其转码而是用浏览器直接执行的话,下面这段代码也会报错:

if(thing) { 
  console.log(thing);
}
const thing = 'awesome thing';

上面这段代码中,如果你把const改为var,那么就不会报任何错误,因为用var声明的变量在被绑定的时候会被初始化为 undefined,而const声明的变量在绑定的时候不会被初始化。

用function来声明函数就不会遇到TDZ问题。下面这段代码便是有效的:

sayHelloTo(‘Bill’);
function sayHelloTo(name) {
  return `Hello ${name}`;
}

因为这个函数在被绑定的时候就初始化了,这时候还没有任何代码被执行。因此,在使用function定义函数的情况下,在程序刚一开始执行的时候,不论你在它的作用范围内的哪个地方都可以调用它。

上边我讨论的这些问题使得我们在写代码的时候必须遵循从上到下的规定。我们必须从最底层的函数开始一点一点往下写。而我在思考的时候并不是这样。我会先关注大致的思路,然后才是细节。

绝大多数代码都是人来编写的。因此大多数人的理解顺序应和代码执行顺序大致相同。

事实上,在代码文件顶部对我们的API进行一些简单的说明可以很好地加快他人理解你代码的速度。使用function就可以实现这一点。

看看下面这个购物车模块的例子:

export {
          createCart,
       addItemToCart,
  removeItemFromCart,
        cartSubTotal,
           cartTotal,
            saveCart,
           clearCart,
}
function createCart(customerId) {...}
function isValidCustomer(customerId) {...}
function addItemToCart(item, cart) {...}
function isValidCart(cart) {...}
function isValidItem(item) {...}
...

如果使用ES6的写法就会是下面这个样子:

...
const _isValidCustomer = (customerId) => ...
const _isValidCart = (cart) => ...
const _isValidItem = (item) => ...
const createCart = (customerId) => ...
const addItemToCart = (item, cart) => ...
...
export {
          createCart,
       addItemToCart,
  removeItemFromCart,
        cartSubTotal,
           cartTotal,
            saveCart,
           clearCart,
}

试想,如果这是一个大得多的模块,包含了N多细碎的内部函数,你会喜欢哪种定义方式?

有些人可能会说在你声明一个东西之前就使用它是不自然的,而且会有很多隐患。但是哪个方法更好只是一种观点,而不是事实,每个人都有每个人自己的思维方式。我的观点是:

我会努力让编译器、打包工具和压缩工具将代码整理为最佳状态——便于人理解的状态。

那么箭头函数就不能用了吗?不是的

我说了这么多function比箭头函数好的地方,难道我就不用箭头函数吗?不是的。我通常在定义一些小函数,尤其是回调函数的时候会使用箭头函数。回调用箭头函数来写简直是太爽了!

下面是一个 回调函数的 例子:

const goodSingers = singers.filter((singer) => singer.name !== 'Justin Bieber');
function tonyMontana() {
  return getTheMoney().then((money) => power)
                      .then((power) => women);
}

结语

ES6的很多新特性都非常吸引人,然而不是因为它新我们就一定要使用它们。在评价一个新的技术的时候最重要的是 适合不适合。如果你觉得用起来非常棒,对你的帮助非常大,那么不要犹豫;如果你用的时候发现和你平常的思路正相反,那么你就需要谨慎地考虑使用它的必要性了。

via medium

本文二维码