JavaScript:回调模式(Callback Pattern)

函数就是对象,所以他们可以作为一个参数传递给其它函数;

当你将introduceBugs()作为一个参数传递给writeCode(),然后在某个时间点,writeCode()有可能执行(调用)introduceBugs();

这种情况下,introduceBugs()被称为回调函数(callback function)或简称为回调(callback:):

[javascript] view plaincopyprint?

  1. function writeCode(callback) {
  2. // do something...
  3. callback();
  4. // ...
  5. }
  6. function introduceBugs() {
  7. // ... make bugs
  8. }
  9. writeCode(introduceBugs);
function writeCode(callback) {
    // do something...
    callback();
    // ...
}
function introduceBugs() {
    // ... make bugs
}
writeCode(introduceBugs);

注意introduceBugs()作为一参数传递给writeCode()是没有使用括号的;

使用括号会立即执行函数,然而在这种情况下,我们希望的是只传递一个指向函数的引用,让writeCode()在适当的时候去执行;

一个回调的例子(A Callback Example)

我们先从一个不使用回调的例子开始,然后在后面重构它;

假如,你有一个通用的函数,它会做一些复杂的工作并且返回一个包含很多数据的集合;

这个通用的函数可能被调用,并且它的工作就是去抓取一个页面的DOM树,返回一个数组里面包含着你感兴趣的页面元素的数组,比如findNodes();

[javascript] view plaincopyprint?

  1. var findNodes = function() {
  2. var i = 100000,
  3. // big, heavy loop
  4. nodes = [],
  5. // stores the result
  6. found; // the next node found
  7. while (i) {
  8. i -= 1;
  9. // complex logic here...
  10. nodes.push(found);
  11. }
  12. return nodes;
  13. };
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()的函数,见名知意,它的作用是从页面中隐藏节点:

[javascript] view plaincopyprint?

  1. var hide = function(nodes) {
  2. var i = 0,
  3. max = nodes.length;
  4. for (; i < max; i += 1) {
  5. nodes[i].style.display = "none";
  6. }
  7. };
  8. // executing the functions
  9. 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()实现了隐藏的逻辑,那么它将不再是一个通用的函数,因为查询和修改的逻辑产生了耦合;

加入回调模式——传递你隐藏节点的逻辑作为一个回调函数并且代理它的执行:

[javascript] view plaincopyprint?

  1. // refactored findNodes() to accept a callback
  2. var findNodes = function(callback) {
  3. var i = 100000,
  4. nodes = [],
  5. found;
  6. // check if callback is callable
  7. if (typeof callback !== "function") {
  8. callback = false;
  9. }
  10. while (i) {
  11. i -= 1;
  12. // complex logic here...
  13. // now callback:
  14. if (callback) {
  15. callback(found);
  16. }
  17. nodes.push(found);
  18. }
  19. return nodes;
  20. };
// 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()函数的实现也可以更加简单,因为它不需要去遍历节点数组:

[javascript] view plaincopyprint?

  1. // a callback function
  2. var hide = function(node) {
  3. node.style.display = "none";
  4. };
  5. // find the nodes and hide them as you go
  6. 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()去显示节点:

[javascript] view plaincopyprint?

  1. // passing an anonymous callback
  2. findNodes(function (node) {
  3. node.style.display = "block";
  4. });
// passing an anonymous callback
findNodes(function (node) {
    node.style.display = "block";
});

回调和作用域(Callbacks and Scope)

在前面这个例子中,回调函数执行的部分可能像:

[javascript] view plaincopyprint?

  1. callback(parameters);
callback(parameters);

虽然这样很简单并且在很多情况下都已经足够了;

但经常有一些场景,回调函数不是匿名函数或者全局函数,而是一个对象的一个方法;

如果回调函数使用this去访问函数属于的对象,这就会产生意想不到的错误。

假如有一个parint()的回调函数,它是myapp对象的一个方法:

[javascript] view plaincopyprint?

  1. var myapp = {};
  2. myapp.color = "green";
  3. myapp.paint = function(node) {
  4. node.style.color = this.color;
  5. };
var myapp = {};
myapp.color = "green";
myapp.paint = function(node) {
    node.style.color = this.color;
};

findNodes()函数做了类似下面的事:

[javascript] view plaincopyprint?

  1. var findNodes = function(callback) {
  2. // ...
  3. if (typeof callback === "function") {
  4. callback(found);
  5. }
  6. // ...
  7. };
var findNodes = function(callback) {
    // ...
    if (typeof callback === "function") {
        callback(found);
    }
    // ...
};

如果你调用了findNodes(myapp.paint),它并不能按照预期的那样工作,因为this.color将会是undefined;

这里this将会指向全局对象,因为findNodes()是一个全局函数;

如果findNodes()是一个叫做dom对象的方法,那么在回调函数中的this将会指向dom而不是期望的myapp;

解决这个问题的方法就是传递一个回调函数,此外再传递这个回调函数属于的对象作为一个参数:

[javascript] view plaincopyprint?

  1. findNodes(myapp.paint, myapp);
findNodes(myapp.paint, myapp);

紧跟着,我们需要去修改findNodes()去绑定(bind)传递进来的对象:

[javascript] view plaincopyprint?

  1. var findNodes = function(callback, callback_obj) {
  2. //...
  3. if (typeof callback === "function") {
  4. callback.call(callback_obj, found);
  5. }
  6. // ...
  7. };
var findNodes = function(callback, callback_obj) {
    //...
    if (typeof callback === "function") {
        callback.call(callback_obj, found);
    }
    // ...
};

对于传递一个对象和一个被用来回调的方法,另一个可选的方法就是将方法作为字符串传递,那么你就不会重复对象两次;

换言之:

[javascript] view plaincopyprint?

  1. findNodes(myapp.paint, myapp);
findNodes(myapp.paint, myapp);

会变成:

[javascript] view plaincopyprint?

  1. findNodes("paint", myapp);
findNodes("paint", myapp);

那么findNodes()可能会做一些事,就像下面几行:

[javascript] view plaincopyprint?

  1. var findNodes = function(callback, callback_obj) {
  2. if (typeof callback === "string") {
  3. callback = callback_obj[callback];
  4. }
  5. //...
  6. if (typeof callback === "function") {
  7. callback.call(callback_obj, found);
  8. }
  9. // ...
  10. };
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事件:

[javascript] view plaincopyprint?

  1. 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()方法,这些方法也可以接受和执行回调函数:

[javascript] view plaincopyprint?

  1. var thePlotThickens = function () {
  2. console.log(‘500ms later...‘);
  3. };
  4. setTimeout(thePlotThickens, 500);
var thePlotThickens = function () {
    console.log(‘500ms later...‘);
};
setTimeout(thePlotThickens, 500);

再次注意一下,thePlotThickens是如何被作为一个参数传递的,没有使用括号;

因为你不想它立即执行;传递字符串"thePlotThickens()"取代函数的引用和eval()类似,是不好的模式。

类库中的回调(Callbacks in Libraries)

回调是一种简单而强大的模式,当你在设计类库的时候会派的上用场;

在软件类库中的代码应该尽可能的通用和复用,回调可以帮助我们解决这种泛化;

你不需要预测和实现你可以想到的所有功能,因为它们会使类库膨胀,并且大部分用户都不会需要这么多功能;

取而代之的是,集中精力在核心的功能并提供以回调函数形式的“钩子”(hook),这会让类库的方法更加简单的去构建,扩展和定制。

时间: 2024-08-09 22:02:14

JavaScript:回调模式(Callback Pattern)的相关文章

javascript回调函数(模式)原理和示例深入分析

   广大网友读懂了我之前论述的javascript原理这篇文章很容易懂 回调函数来自一种著名的编程范式--函数式编程,在基本层面上,函数式编程指定的了函数的参数.函数式编程虽然现在的使用范围变小了,但它一直被"专业的聪明的"程序员看作是一种难懂的技术,以前是这样,未来也将是如此. 幸运的是,函数式编程已经被阐述的像你我这样的一般人也能理解和使用.函数式编程最主要的技术之一就是回调函数,你很快会阅读到,实现回调函数就像传递一般的参数变量一样简单.这项技术如此的简单,以至于我都怀疑为什么

理解javascript中的回调函数(callback)【转】

在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以"存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值". 因为function是内置对象,我们可以将它作为参数传递给另一个函数,延迟到函数中执行,甚至执行后将它返回.这是在JavaScript中使用回调函数的精髓.本篇文

理解javascript中的回调函数(callback)

以下内容来源于:http://www.jb51.net/article/54641.htm 最近在看 express,满眼看去,到处是以函数作为参数的回调函数的使用.如果这个概念理解不了,nodejs.express 的代码就会看得一塌糊涂.比如: app.use(function(req, res, next) {    var err = new Error('Not Found');    err.status = 404;    next(err);}); app是对象,use是方法,方

JavaScript系列之回调函数callback

JavaScript回调函数的使用是很常见的,引用官方回调函数的定义: A callback is a function that is passed as an argument to another function and is executed after its parent function has completed. 解释得很明确,回调函数就是作为参数传递给另一个函数并在其父函数完成后执行的函数. 听起来似乎有点不好理解,所以还是举例进行说明,介绍回调函数之前先简单说明一下同步和

重新理解javascript回调函数

把函数作为参数传入到另一个函数中.这个函数就是所谓的回调函数 经常遇到这样一种情况,某个项目的A层和B层是由不同的人员协同完成.A层负责功能funA,B层负责funcB.当B层要用到某个模块的数据,于是他对A层人员说,我需要你们提供满足某种需求的数据,你给我提供一个接口. A层的人员说:我给你提供数据,怎么展示和处理则是B的事情. 当然B层不可能为你每个需求都提供一个数据接口,B给A提供一个通过的接口.B得到数据,然后B写函数去展示. 即,你需要和其他人合作,别人提供数据,而你不需要关注别人获取

Javascript编程模式(JavaScript Programming Patterns)Part 1.

JavaScript 为网站添加状态,这些状态可能是校验或者更复杂的行为像拖拽终止功能或者是异步的请求webserver (aka Ajax). 在过去的那些年里, JavaScript libraries变得越来越流行. 如果你面对着很多的工作计划,一个很明确的道理就是在网站变得越来越复杂的情况下每次修改‘轮子“肯定让你不爽.当然我们把类库放到一边,聚焦于 JavaScript的语法,对你最有价值的东西是在你编写 JavaScript你要明确你使用的是那种”编程模式“. 下面主要介绍几个jav

javascript回调函数,闭包作用域,call,apply函数解决this的作用域问题

在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实际上是一种对象,它可以“存储在变量中,通过参数传递给(别一个)函数(function),在函数内部创建,从函数中返回结果值”. 因为function是内置对象,我们可以将它作为参数传递给另一个函数,延迟到函数中执行,甚至执行后将它返回.这是在JavaScript中使用回调函数的精髓.本篇文章的剩余部

JAVA回调机制(CallBack)详解

序言 最近学习java,接触到了回调机制(CallBack).初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义.当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题.但是,对于初学的我来说,缺了一个循序渐进的过程.此处,将我对回调机制的个人理解,按照由浅到深的顺序描述一下,如有不妥之处,望不吝赐教! 开始之前,先想象一个场景:幼稚园的小朋友刚刚学习了10以内的加法. 第1章. 故事的缘起 幼师在黑板上写一个式子 “1

回调函数callback使用例子

代码如下: <!DOCTYPE HTML> <html> <head> <meta charset="GBK" /> <title>回调函数(callback)</title> <script type="text/javascript" src="http://cdn.bootcss.com/jquery/1.9.0/jquery.min.js"></sc