在JavaScript中避免使用匿名函数
发布在每天学点javascript2014年2月27日view:5313
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

enter image description here

匿名函数,是回调的艺术。但是,最理想的情况不不要再编写匿名函数。首先,什么是一个匿名函数呢?就像下面的这个函数一样:

document.querySelector('.menu').addEventListener('click',function(event){
    //我们现在就位于回调函数中  
    if(!this.classList.contains('active')){
        this.classList.add('active');
        event.preventDefault();
    }
},false);   

不要使用匿名函数的理由有以下几个:

  • 调试起来很麻烦
  • 不能够重用
  • 测试起来很麻烦
  • 无法描述函数的角色
  • 代码缺乏结构
  • 会产生乱七八糟不清楚的代码
  • 无法使用像是jsDoc一样的东西

我们继续。基于上面的这个例子,我们看到了这个例子中绑定了一个click事件并且执行了一个函数来添加一个class。这是为了什么呢?到目前为止,我们仅仅只能猜测这段代码用来切换一个标签或者一个菜单。因此我们为什么要如此依赖于匿名函数而不是写出更好的代码呢?

“但我们还能做什么?”。此时,你可能会摘掉耳机,凑到你正在编写代码的同事的面前然后问他为什么要添加一个类。他可能会因为你打断了他的工作而感到很生气,然后他会暂停碧昂斯的歌曲告诉你答案。这种情形完全可以避免,如果他事先编写了一些很优雅的代码的话:

function toogleMenu (event){
    if(!this.classList.contains('active')){
        this.classList.add('active');
    }
    event.preventDefault();
}
document.querySelector('.menu').addEventListener('click',toogleMenu,false);  

现在,看起来是不是好一点了?如果我们要引入另一个元素,我们完全可以为他绑定同一个函数:

document.querySelector('.menu').addEventListener('click', toggleMenu, false);
document.querySelector('.myclass2').addEventListener('click', toggleMenu, false); 

这样的做法能够防止那些兰度的开发者将整个匿名函数的内容复制粘贴,你只需要将它移动到一个函数中并且再次重构使用它即可。

抽象

一个美妙的词。我们来多使用它几次并且将我们的代码抽象为更多的组件和部分,这将使我们的生活变得更美好。现在我们来抽象我们的选择器怎么样?

var menu = document.querySelector('.menu');
function toggleMenu (event) {
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}
menu.addEventListener('click', toggleMenu, false); // 简直太酷了  

我们鼓励进行这种设置,因为我们将把代码抽象为三个不同的部分,selector,event和method。虽然有些jQuery的链式垃圾已经污染了web – 你不能因为你可以做就去做这样的事情。链式法官法创造出更复杂并且质量更低的代码。链式设置步骤是一个将你的代码抽象为可重用部分时会遇到的问题,它们同事也会弄脏函数。

因此我们现在来重新审视一下我们的代码:

// selector
var menu = document.querySelector('.menu');

// method
function toggleMenu (event) {
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}

// event
menu.addEventListener('click', toggleMenu, false);   

这样的做法会带来诸多好处。假设menu还要绑定一个onchange事件,我们可以简单轻松的扩展我们的代码:

/ selector
var menu = document.querySelector('.menu');

// method
function toggleMenu (event) {
  if (!this.classList.contains('active')) {
    this.classList.add('active');
  }
  event.preventDefault();
}

// events
menu.addEventListener('click', toggleMenu, false);
menu.addEventListener('onchange', toggleMenu, false);  

通过这一个步骤,你可能已经明白了一些如何操纵DOM的思路了。下面就是一些看起来很典型的代码文件:

(function (window, document, undefined) {

  'use strict';

  /**
   * Selectors
   */
  var menu = document.querySelector('.menu');
  var users = document.querySelectorAll('.user');
  var signout = document.querySelector('.signout');

  /**
   * Methods
   */
  function toggleMenu (event) {
    if (!this.classList.contains('active')) {
      this.classList.add('active');
    }
    event.preventDefault();
  }
  function showUsers (users) {
    for (var i = 0; i < users.length; i++) {
      var self = users[i];
      self.classList.add('visible');
    }
  }
  function signout (users) {
    var xhr = new XMLHttpRequest();
    // TODO: finish signout
  }

  /**
   * Events/APIs/init
   */
  menu.addEventListener('click', toggleMenu, false);
  signout.addEventListener('click', signout, false);
  showUsers(users);


})(window, document);   

这种做法同样也有很多好处,包括缓存你的选择器,你的团队指导你正在编写的代码的准确形式,并且不会用随机的脚本来污染文件的各个角落,同事未来的修改也变得容易了。

你可能注意到了上面的代码包括在一个自调用函数中,这使得你的所有代码与全局作用域隔离,这为你减少了很多麻烦事。

传递参数

你可能注意到了,我们上面的代码并没有传递任何的参数,这是因为addEventListener函数已经做得非常好了,但是还是缺少了一小点功能,我们现在就来看看究竟发生了什么。你可能会编写这样的代码:

element.addEventListener('click', toggleMenu(param1, param2), false);

但是这将会在JavaScript引擎运行的同时立刻执行函数,这是一个坏消息。因此我们可以使用ECMAScript 5中添加的Function.prototype.bind方法来设置参数而无需调用函数。这和.call()以及.apply()很类似只是你不需要调用函数:

element.addEventListener('click', toggleMenu.bind(null, param1, param2), false);

你可以查阅.bind方法的详细信息,如果你的浏览器不支持.bind()方法,我们可以通过下载相应的库来实现这个方法。

如果你不想使用相应的库,并且想要用“复古的办法”,那么你需要将它包裹在一个函数中:

element.addEventListener('click', function () {
  toggleMenu(param1, param2);
}, false);   

上面的这种写法和我们今天所说的话题相悖吗?不,它只是为了给函数传递参数的一个权宜之计并不会为我们带来任何损失。你甚至可以在这个包裹的回调函数中添加event.prentDefault()逻辑来确保你的函数不会违背你的意思来preventDefault()。


本文译自Avoiding anonymous JavaScript functions,原文地址http://toddmotto.com/avoiding-anonymous-javascript-functions/

如果你觉得本文对你有帮助,请点击为我提供赞助

评论
发表评论
4年前

匿名函数是js一大特色,居然说不用,你的标题应该叫隔离应用逻辑,而不是不用匿名函数。

4年前

文章题目吸引我了,不过内容确实没什么意义的感觉,而且上面说的自调也是匿名。只知道匿名不好调试,现在看来还是只有不好调试。。。

4年前

我觉得你这篇文章的内容应该放到一篇讲解关于代码复用的文章里会更好些。

文章的内容不是在说避免使用匿名函数,而是不要把大量的与逻辑相关的代码放到匿名函数中。

4年前

@米粽粽 也许对大牛来说没什么意义,但是对于初学者,没有什么实践经验的人来说也是挺不错的。

4年前

@米粽粽 取其精华,去其糟粕。好好学习,天天向上。

4年前

这篇文章纯属垃圾。po主很勤奋,但浪费精力在这样的文章上毫无意义……

4年前

这其实完全和彻底拒绝 inline style 一样,不合理。

WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
每天学点javascript

javascript进阶级教程,循序渐进掌握javascript

我的收藏