JavaScript:回调模式(Callback Pattern) (转载)
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),这会让类库的方法更加简单的去构建,扩展和定制。
JavaScript:回调模式(Callback Pattern) (转载)的更多相关文章
- JavaScript:回调模式(Callback Pattern)
函数就是对象,所以他们可以作为一个参数传递给其它函数: 当你将introduceBugs()作为一个参数传递给writeCode(),然后在某个时间点,writeCode()有可能执行(调用)intr ...
- JavaScript学习笔记(十二) 回调模式(Callback Pattern)
函数就是对象,所以他们可以作为一个参数传递给其它函数: 当你将introduceBugs()作为一个参数传递给writeCode(),然后在某个时间点,writeCode()有可能执行(调用)intr ...
- js回调函数(callback)(转载)
学习jquery时,对回调函数感觉很困惑,在晚上找了半天,忽然发现这篇文章很浅显,基本说明了问题.故转载 原文: 自学jquery的时候,看到一英文词(Callback),顿时背部隐隐冒冷汗.迅速go ...
- javascript中的回调函数(callback) (转载)
代码如下: app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(er ...
- js作为参数,并且返回值;js的回调模式 callback
有这样一个情景,当我们弹出一个 prompt的时候,要求用户在文本框输入一个文字,然后点击确认,就可以拿到返回值 var temp=prompt("请输入您的名字"); a ...
- (" use strict")Javascript 严格模式详解
Javascript 严格模式详解 转载别人的博客内容,浏览了一遍,没有全部吸收,先保存一下链接 http://www.ruanyifeng.com/blog/2013/01/javascript_s ...
- React Render Callback Pattern(渲染回调模式)
React Render Callback Pattern,渲染回调模式,其实是将this.props.children当做函数来调用. 例如: 要根据user参数确定渲染Loading还是Profi ...
- 理解javascript中的回调函数(callback)【转】
在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...
- 理解javascript中的回调函数(callback)
以下内容来源于:http://www.jb51.net/article/54641.htm 最近在看 express,满眼看去,到处是以函数作为参数的回调函数的使用.如果这个概念理解不了,nodejs ...
随机推荐
- PHP学习10——Cookie和Session技术
主要内容: Cookie技术 创建cookie 查看cookie 读取cookie 用cookie记录访问时间和次数 删除cookie cookie的生命周期 Session技术 session工作原 ...
- 01.MD5加密
namespace _01.MD5加密 { class Program { static void Main(string[] args) { //MD5加密就是给想要的密码或者其它字符加密 //如果 ...
- 阿里Java开发电话面试经历--惨败
近期准备跳槽,想试试知名大企业--阿里.经过boss直聘上一些内部人员的内推,有幸获得了一次电话面试的机会.(虽然在面试开始之前就大概知道结果是如何,但是也总得试试自己个有多水,哈哈哈...) 跟大家 ...
- HttpResponse Entity的处理(将字符数组转为JSON)
1.问题背景 调用高德的IP地址查询接口,获取的返回值为字符串数组(如下); 因为这里只是纯字符串,并不是真正的数组,无法直接取值,所以想到看能不能转为数组或者JSON再进行取值. 2.解决: 通过在 ...
- swagger2配置和使用
1.导入swagger2 <dependency> <groupId>io.springfox</groupId> <artifactId>spring ...
- Binder or AIDL的最简单实践
1.前言: 在Android开发中多进程的合理使用+进程间通信的IPC是一个比较难的点.特别是Android特有的Binder机制,非常复杂,对于应用层开发的初级开发工程师强求深入理解Binder机制 ...
- vcfc之zk+postsql+keystore(cassandra)框架分析
vcfc框架总结: 1 一. bus和keystore是如何协调处理的,什么样的问题是处理不了的? 1. 如果在备重启的过程中,主处理了某个时间1,备机如果同步数据呢? 二 .数据可靠性和一致性分析 ...
- 洛谷P2196 挖地雷(dp)
题意 题目链接 Sol 早年NOIP的题锅好多啊.. 这题连有向边还是无向边都没说(害的我wa了一遍) 直接\(f[i]\)表示到第\(i\)个点的贡献 转移的时候枚举从哪个点转移而来 然后我就用一个 ...
- redis 安装与php扩展
php-redis扩展下载地址:https://pecl.php.net/package/redis/2.2.7/windows 注意: php_igbinary-5.5-vc11-ts-x86- ...
- 删除SVN版本信息 .svn文件夹
环境:MyEclipse.Windows 问题描述: 在MyEclipse中当我们需要将一个文件夹(包含若干文件或嵌套文件夹)拷贝到另一个文件夹时,此时文件内容虽然拷贝过去了,但其下面的 .svn文件 ...