JavaScript中的闭包/回调函数有哪些用例?

时间:2010-04-12 13:34:35

标签: javascript callback closures

我正在听Crockford关于JavaScript闭包的讨论,并且我确信信息隐藏的好处,但我不清楚何时使用回调函数。

这主要是一个真实的陈述,一个人可以使用或不使用回调来完成相同的功能。

作为编写代码的人,在确定何时使用回调/闭包时,我应该记住哪些启发式或提示?

我不是在寻找一揽子声明'闭包制作更安全的代码',而是在回调是正确的想法时,列出一些实际示例或经验法则。

Crockford的演讲: http://www.yuiblog.com/blog/2010/04/08/video-crockonjs-5/

3 个答案:

答案 0 :(得分:32)

首先:

  • 回调:作为参数传递给另一个函数的函数,通常由于事件发生而被调用。
  • 关闭:保留范围。即在另一个函数中声明函数时,外部函数的作用域可以在内部函数中访问。

回调也可以是闭包,但并非总是如此。

这是一个回调:

someProcess(myCallback);

function myCallback() {
    alert('Done...');
}

function someProcess(callback) {
    // does stuff...
    // ...
    callback();
}

关闭:

function foo(msg) {

    function bar() {
        // I can access foo's scope
        // (i.e. bar can access everything that foo can access)
        alert(msg);
    }

    return bar;

}

foo('hello')(); // alerts "hello"

闭包的一个常见用法是提供信息隐藏,这有助于为语言带来某种封装。请查看the module pattern以查看此操作。

另一种常见用法是将事件处理程序绑定到元素。 E.g。

var myElements = [ /* DOM Collection */ ];

for (var i = 0; i < 100; ++i) {
    myElements[i].onclick = function() {
        alert( 'You clicked on: ' + i );
    };
}

那不行。单击元素时,变量i99。为了使其正常工作,我们使用闭包来捕获i

的值
function getHandler(n) {
    return function() {
        alert( 'You clicked on: ' + n );
    };
}

for (var i = 0; i < 100; ++i) {
    myElements[i].onclick = getHandler(i);
}

答案 1 :(得分:14)

假设您需要一个可用于返回创建新DOM元素时使用的唯一“id”值的函数。现在,在类似Java的东西中,您可以使用内部专用计数器创建一个类,然后使用一个方法将计数器附加到某个前缀字符串。好吧,在Javascript:

var getId = (function() {
  var counter = 0;
  return function() {
    return "prefix" + counter++;
  };
})();

现在,变量“getId”被另一个函数绑定到创建的函数,并以这样的方式创建,即它具有在调用之间使用的持久变量。同样,如果我想拥有一系列“getId”函数(例如,我可能添加的每种DOM元素都有一个),我可以这样做:

var getIdFunc = function(prefix) {
  var counter = 0;
  return function() {
    return prefix + counter++;
  };
};
var getId = {
  'div': getIdFunc('div'),
  'span': getIdFunc('span'),
  'dl': getIdFunc('dl'),
  // ...
};

现在,我可以致电getId.div()为新的<div>获取新的“id”值。该函数是通过调用一个函数创建的,该函数提供了一个隐藏在闭包中的两个值:前缀字符串(作为参数传入)和计数器(在闭包范围内声明的var )。

一旦你习惯了它,这个设施是如此灵活和有用,你会感到很痛苦,无法回到没有它的环境。

哦,如果您尝试这样做,这里有一个提示可以帮助您远离StackOverflow:这是一个随时出现的问题:

for (var i = 0; i < 10; ++i) {
  var id = "foo" + i;
  var element = document.getElementById(id);
  element.onclick = function() {
    alert("hello from element " + i);
  };
}

这里有什么问题?那么,该函数引用的“i”变量是该循环运行的范围中的“i”。你会注意到,这个变量会在循环中递增(duhh,对吗?)。好吧,创建和分配为事件处理程序的那些小函数中的每一个都将在闭包范围中共享相同的单个变量“i”。哎呀!解决方案是做这样的事情:

for (var i = 0; i < 10; ++i) {
  var id = "foo" + i;
  var element = document.getElementById(id);
  element.onclick = (function(iCopy) {
    return function() {
      alert("hello from element " + iCopy);
    };
  })(i);
}

我们将外部“i”的副本复制到它自己的闭包范围中,所以现在每个事件处理程序都有自己的!

总结一下:一旦习惯,利用闭包的技术会出现所有的怪胎时间。这不是一个无错误编程的新仙境的免费门票;别误会我的意思。然而,它是一个非常有用和灵活的范例。

答案 2 :(得分:5)

This writeup from Mozilla may answer why use closures and when

另外,see this set of examples (especially "What can be done with Closures?" section that has the following exmples)

  • 示例1:带函数引用的setTimeout
  • 示例2:将函数与对象实例方法相关联
  • 示例3:封装相关功能

我觉得这可以追溯到Crockford,但闭包的经典用法是模拟私有实例或静态变量(JavaScipt缺乏)