『翻译』3个原因让我像躲避瘟疫一样避免使用JS匿名函数

1,969 阅读6分钟

Read the original


前言

无论何时阅读代码,你一定会看到匿名函数。有时它们被称为lambdas,有时被称为匿名函数(anonymous functions),但不管怎样,我认为他们是糟糕的。

如果你不知道什么是匿名函数,这里有一段引用:

匿名函数是一个可以在运行时动态声明的函数。之所以称为匿名函数是因为:他们没有像普通函数一样被赋予名字(name)。 ——Helen Emerson, Helephant.com

它们看起来有点像这个:

function() { ... code ... }

OR

(args) => { ... code ... }

下面我会举一些例子给你,实际上,只有在非用不可的情况下才会使用匿名函数。它们不是你的首选,并且你要知道为什么。一旦你这么做了,你的代码会更简洁、容易去阅读,BUG也更容易被捕获,让我们看看这3个避免使用它们的原因吧!

堆栈跟踪

你终于写完了代码,无论你有多擅长编码,当你运行代码时,总会有一些报错。有些错误很容易被捕获,但有时并不是这样。

如果错误很容易被捕获到,好的,那说明你知道他们错在哪!为此,我们使用所谓的堆栈跟踪,如果你对堆栈跟踪一点都不了解,Google给了我们很好的介绍

假设我们有一个很简单的项目:

function start () {
  (function middle () {
    (function end () {
      console.lg('test');
    })()
  })()
}

但看起来我们做了一些愚蠢得难以置信的事,就像拼错console.log。在我们这个小项目中,这没什么大不了的。但也许这是一个巨型项目中的一个片段,有大量的模块与它相互依赖。最重要的是,让我们假装你没有烦这种愚蠢的错误。那个初级开发者在他准备离开度假的前一天,把它推到了仓库中!

现在,我们将去跟踪错误。根据我们精确的命名函数,我们得到的堆栈跟踪像下面这样:

幸好你给函数命名了,初级开发者!现在,我们能轻松的跟踪到这个BUG。

但是...一旦我们修复了它,又会出现新的BUG。这时,就要请出更高级的开发者了。他们知道如何使用匿名函数,并大量的使用在他们的代码中。结果是,他们发现了一个BUG,我们去跟踪BUG。

他们的代码:

(function () {
 (function () {
   (function () {
     console.lg('test');
    })();
  })();
})();

多令人惊讶,这个高级开发者也忘了怎么拼写console.log!这是一个偶然吗?!告诉你一个悲痛的消息,他们没有给函数命名。

控制台会展示什么给我们?

没事...我们还有行号提示? 在这个例子中,我们好像有7行代码。如果我们在处理一个巨大的代码库?如果每行有10k的代码?如果行号相差很远?如果代码被压缩了、没有map文件、行号提示几乎是没用的?

我认为你可以非常容易的回答这些问题。答案是:你会度过糟糕的一天

可读性

看吧,我知道你不服气。你仍然爱着匿名函数,而且你永远也不会有BUG。抱歉,我忘了告诉你如何写出完美的代码。让我们来看看这点!

检查下面两个不同的代码示例:

function initiate (arguments) {
  return new Promise((resolve, reject) => {
    try {
      if (arguments) {
         return resolve(true);
      }
      return resolve(false);
    } catch (e) {
      reject(e);
    }
  });
}

initiate(true)
  .then(res => {
        if (res) {
          doSomethingElse();
        } else {
          doSomething();
        }
  ).catch(e => {
            logError(e.message);
            restartApp();
          }
  );

这是一个人为的例子,但我认为你能get到那个点。我们有一个方法,它返回一个Promise对象,我们用这个对象去管理可能返回的不同请求。

你或许认为这个代码阅读起来不太难,但我认为它能写的更好。

如果我们摆脱所有匿名函数,结果会如何?

function initiate (arguments) {
  return new Promise(checkForArguments);
}
function checkForArguments (resolve, reject) {
  try {
    if (arguments) {
     return resolve(true);   
    }
    return resolve(false);
  } catch (e) {
    reject(e);
  }
}
function evaluateRes (res) {
  if (res) {
    doSomethingElse();
  } else {
    doSomething();
  }
}
function handleError (e) {
  logError(e.message);
  restartApp();
}
initiate(true)
  .then(evaluateRes)
  .catch(handleError);

好了,让我们来屡清一下思路:这段代码比之前的要长,但我认为它比之前的可读性高得多!我们使用了非常棒的命名函数而不是匿名函数。只要我们看到命名函数,它的名字就会暗示我们接下来会发生什么。它消除了我们在阅读代码时的心理障碍。

这也有利于独立问题。不仅仅是创建方法,传递参数,运行逻辑,在第二个例子中,传递参数给thencatch,我们可以很清楚的知道函数中的每一处都发生了什么。

时间不多了,否则我要让你信服这些代码可以更加易读。也许你任然不相信,那尝试看看第三个论据...

可重用性

你有注意到最后一个例子吗?所有函数都是可以互相调用的!

当你使用匿名函数时,它们很难遍布你的程序。重用代码可以降低能耗,你也不用一遍遍写重复的代码了。还有大家都知道的一点,代码越少,引入BUG的几率越小,而且用户可以加载更少的资源。共赢的局面!

相反的,命名函数可以在它的整个作用域中使用,而不需要像变量一样四处传递。你写的代码会很自然的更具重用性,因为,你可以重用他们!

匿名函数好吗?

是的。我必须承认,有时它们也是最好的选择!

const stuff = [
  { hide: true, name: 'justin' },
  { hide: false, name: 'lauren' },
  { hide: false, name: 'max' },
];
const filteredStuff = stuff.filter(s => !s.hide);

这个匿名函数s => !s.hide小而简洁,它不会污染其它地方,也不会用在其它地方。它会在stuff.filter的堆栈跟踪中显示。如果你需要重用它,最好重用整个语句:

function filterByHide (array) {
  return array.filter(item => !item.hide);
}

有时你需要包裹你所有的代码在一个匿名函数中,以保证全局作用域不被污染。

(() => {
 ... your code here ...
})();

顶级匿名函数不会影响到堆栈跟踪。代码重用是不会造成伤害的,因为代码重用的全部目的就是保持方法被包含。

我相信还有其他地方适合使用匿名函数,在评论中分享一下吧!

感谢阅读,离开这里后,停止写匿名函数吧!

喜欢本文的朋友可以关注我的微信公众号,不定期推送一些好文。

本文由Rockjins Blog翻译,转载请与译者联系。否则将追究法律责任。