JavaScript:回调模式(Callback Pattern) 函数就是对象,因此他们能够做为一个参数传递给其它函数; 当你将introduceBugs()做为一个参数传递给writeCode(),而后在某个时间点,writeCode()有可能执行(调用)introduceBugs(); 这种状况下,introduceBugs()被称为回调函数(callback function)或简称为回调(callback:): function writeCode(callback) { // do something... callback(); // ... } function introduceBugs() { // ... make bugs } writeCode(introduceBugs); function writeCode(callback) { // do something... callback(); // ... } function introduceBugs() { // ... make bugs } writeCode(introduceBugs); 注意introduceBugs()做为一参数传递给writeCode()是没有使用括号的; 使用括号会当即执行函数,然而在这种状况下,咱们但愿的是只传递一个指向函数的引用,让writeCode()在适当的时候去执行; 一个回调的例子(A Callback Example) 咱们先从一个不使用回调的例子开始,而后在后面重构它; 假如,你有一个通用的函数,它会作一些复杂的工做而且返回一个包含不少数据的集合; 这个通用的函数可能被调用,而且它的工做就是去抓取一个页面的DOM树,返回一个数组里面包含着你感兴趣的页面元素的数组,好比findNodes(); var findNodes = function() { var i = 100000, // big, heavy loop nodes = [], // stores the result found; // the next node found while (i) { i -= 1; // complex logic here... nodes.push(found); } return nodes; }; var findNodes = function() { var i = 100000, // big, heavy loop nodes = [], // stores the result found; // the next node found while (i) { i -= 1; // complex logic here... nodes.push(found); } return nodes; }; 将这个函数保持通用性并让它返回一个DOM节点(node)的数组是个好主意,但没有对实际的元素作任何事情; 修改节点的逻辑可能在不一样的函数中,好比一个叫hide()的函数,见名知意,它的做用是从页面中隐藏节点: var hide = function(nodes) { var i = 0, max = nodes.length; for (; i < max; i += 1) { nodes[i].style.display = "none"; } }; // executing the functions hide(findNodes()); var hide = function(nodes) { var i = 0, max = nodes.length; for (; i < max; i += 1) { nodes[i].style.display = "none"; } }; // executing the functions hide(findNodes()); 这种实现是没有效率的,由于hide()不得再也不遍历一次findNodes()返回的的数组; 若是你能避免这个遍历而且让节点在findNodes()中一被选中就隐藏起来会更有效率; 可是如何你在findNodes()实现了隐藏的逻辑,那么它将再也不是一个通用的函数,由于查询和修改的逻辑产生了耦合; 加入回调模式——传递你隐藏节点的逻辑做为一个回调函数而且代理它的执行: // refactored findNodes() to accept a callback var findNodes = function(callback) { var i = 100000, nodes = [], found; // check if callback is callable if (typeof callback !== "function") { callback = false; } while (i) { i -= 1; // complex logic here... // now callback: if (callback) { callback(found); } nodes.push(found); } return nodes; }; // refactored findNodes() to accept a callback var findNodes = function(callback) { var i = 100000, nodes = [], found; // check if callback is callable if (typeof callback !== "function") { callback = false; } while (i) { i -= 1; // complex logic here... // now callback: if (callback) { callback(found); } nodes.push(found); } return nodes; }; 这样的实现是简单明确的,惟一增长的工做就是findNodes()检查了可选的回调函数是否有被提供,若是有,就执行它; 回调函数是可选的,因此重构后的findNodes()仍然能像之前同样被使用,而且不会破坏依赖于旧的API的遗留代码。 hide()函数的实现也能够更加简单,由于它不须要去遍历节点数组: // a callback function var hide = function(node) { node.style.display = "none"; }; // find the nodes and hide them as you go findNodes(hide); // a callback function var hide = function(node) { node.style.display = "none"; }; // find the nodes and hide them as you go findNodes(hide); 回调函数能够是一个在代码中已经存在的函数,也能够是一个匿名函数(当你调用主函数的时候才会建立); 好比,怎样使用相同的通用函数findNodes()去显示节点: // passing an anonymous callback findNodes(function (node) { node.style.display = "block"; }); // passing an anonymous callback findNodes(function (node) { node.style.display = "block"; }); 回调和做用域(Callbacks and Scope) 在前面这个例子中,回调函数执行的部分可能像: callback(parameters); callback(parameters); 虽然这样很简单而且在不少状况下都已经足够了; 但常常有一些场景,回调函数不是匿名函数或者全局函数,而是一个对象的一个方法; 若是回调函数使用this去访问函数属于的对象,这就会产生意想不到的错误。 假若有一个parint()的回调函数,它是myapp对象的一个方法: var myapp = {}; myapp.color = "green"; myapp.paint = function(node) { node.style.color = this.color; }; var myapp = {}; myapp.color = "green"; myapp.paint = function(node) { node.style.color = this.color; }; findNodes()函数作了相似下面的事: var findNodes = function(callback) { // ... if (typeof callback === "function") { callback(found); } // ... }; var findNodes = function(callback) { // ... if (typeof callback === "function") { callback(found); } // ... }; 若是你调用了findNodes(myapp.paint),它并不能按照预期的那样工做,由于this.color将会是undefined; 这里this将会指向全局对象,由于findNodes()是一个全局函数; 若是findNodes()是一个叫作dom对象的方法,那么在回调函数中的this将会指向dom而不是指望的myapp; 解决这个问题的方法就是传递一个回调函数,此外再传递这个回调函数属于的对象做为一个参数: findNodes(myapp.paint, myapp); findNodes(myapp.paint, myapp); 紧跟着,咱们须要去修改findNodes()去绑定(bind)传递进来的对象: var findNodes = function(callback, callback_obj) { //... if (typeof callback === "function") { callback.call(callback_obj, found); } // ... }; var findNodes = function(callback, callback_obj) { //... if (typeof callback === "function") { callback.call(callback_obj, found); } // ... }; 对于传递一个对象和一个被用来回调的方法,另外一个可选的方法就是将方法做为字符串传递,那么你就不会重复对象两次; 换言之: findNodes(myapp.paint, myapp); findNodes(myapp.paint, myapp); 会变成: findNodes("paint", myapp); findNodes("paint", myapp); 那么findNodes()可能会作一些事,就像下面几行: var findNodes = function(callback, callback_obj) { if (typeof callback === "string") { callback = callback_obj[callback]; } //... if (typeof callback === "function") { callback.call(callback_obj, found); } // ... }; var findNodes = function(callback, callback_obj) { if (typeof callback === "string") { callback = callback_obj[callback]; } //... if (typeof callback === "function") { callback.call(callback_obj, found); } // ... }; 匿名的事件监听器(Asynchronous Event Listeners) 回调模式在平常中被常用,好比,当你附加一个事件监听器给页面上的某个元素时,你实际上提供了一个指向了回调函数的引用,而且在事件发生时被调用; 这里有个例子,怎么将console.log()做为一个回调函数监听文档的click事件: document.addEventListener("click", console.log, false); document.addEventListener("click", console.log, false); 绝大部分客户端浏览器都是事件驱动的(event-driven); 当一个页面加载完成,会触发load事件,而后用户能够经过和页面交互触发各类各样的事件,好比:click, keypress, mouseover, mousemove等等; 由于回调模式,JavaScript特别适合事件驱动编程,能让你的程序异步的工做,换言之,就是不受顺序限制。 “Don’t call us, we’ll call you” 在好莱坞中是句名言,在好莱坞对于一部电影中的一个角色每每有很候选人,剧组人员不可能一直答复全部候选人打来的电话; 在异步的事件驱动的JavaScript,有个类似的情景,你提供一个回调函数用于在正确的时候被调用(to be called),而不是电话号码; 你甚至可能提供比实际请求还要多的回调函数,由于某些事件可能不会发生; 好比:若是用户不点击“购买”按钮,那么你用于验证表单格式的函数永远不会被调用。 Timeouts 另外一个使用回调模式的例子就是使用浏览器的window对象的setTimeout()和setInterval()方法,这些方法也能够接受和执行回调函数: var thePlotThickens = function () { console.log('500ms later...'); }; setTimeout(thePlotThickens, 500); var thePlotThickens = function () { console.log('500ms later...'); }; setTimeout(thePlotThickens, 500); 再次注意一下,thePlotThickens是如何被做为一个参数传递的,没有使用括号; 由于你不想它当即执行;传递字符串"thePlotThickens()"取代函数的引用和eval()相似,是很差的模式。 类库中的回调(Callbacks in Libraries) 回调是一种简单而强大的模式,当你在设计类库的时候会派的上用场; 在软件类库中的代码应该尽量的通用和复用,回调能够帮助咱们解决这种泛化; 你不须要预测和实现你能够想到的全部功能,由于它们会使类库膨胀,而且大部分用户都不会须要这么多功能; 取而代之的是,集中精力在核心的功能并提供以回调函数形式的“钩子”(hook),这会让类库的方法更加简单的去构建,扩展和定制。