您现在的位置:新闻首页>资本 > 详解JavaScript中的回调函数并使用它
详解JavaScript中的回调函数并使用它
在JavaScript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用。既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回。
因为函数是第一类对象,我们可以在JavaScript使用回调函数。在下面的文章中,我们将学到关于回调函数的方方面面。回调函数可能是在JavaScript中使用最多的函数式编程技巧,虽然在字面上看起来它们一直一小段JavaScript或者jQuery代码,但是对于许多开发者来说它任然是一个谜。在阅读本文之后你能了解怎样使用回调函数。
回调函数是从一个叫函数式编程的编程范式中衍生出来的概念。简单来说,函数式编程就是使用函数作为变量。函数式编程过去 - 甚至是现在,依旧没有被广泛使用 - 它过去常被看做是那些受过特许训练的,大师级别的程序员的秘传技巧。
幸运的是,函数是编程的技巧现在已经被充分阐明因此像我和你这样的普通人也能去轻松使用它。函数式编程中的一个主要技巧就是回调函数。在后面内容中你会发现实现回调函数其实就和普通函数传参一样简单。这个技巧是如此的简单以致于我常常感到很奇怪为什么它经常被包含在讲述JavaScript高级技巧的章节中。
什么是回调或者高阶函数
一个回调函数,也被称为高阶函数,是一个被作为参数传递给另一个函数(在这里我们把另一个函数叫做otherFunction)的函数,回调函数在otherFunction中被调用。一个回调函数本质上是一种编程模式(为一个常见问题创建的解决方案),因此,使用回调函数也叫做回调模式。
下面是一个在jQuery中使用回调函数简单普遍的例子:
//注意到click方法中是一个函数而不是一个变量 //它就是回调函数 $("#btn_1").click(function() { alert("Btn 1 Clicked"); });
正如你在前面的例子中看到的,我们将一个函数作为参数传递给了click方法。click方法会调用(或者执行)我们传递给它的函数。这是JavaScript中回调函数的典型用法,它在jQuery中广泛被使用。
下面是另一个JavaScript中典型的回调函数的例子:
var friends = ["Mike", "Stacy", "Andy", "Rick"]; friends.forEach(function (eachName, index){ console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick });
再一次,注意到我们讲一个匿名函数(没有名字的函数)作为参数传递给了forEach方法。
到目前为止,我们将匿名函数作为参数传递给了另一个函数或方法。在我们看更多的实际例子和编写我们自己的回调函数之前,先来理解回调函数是怎样运作的。
回调函数是怎样运作的?
因为函数在JavaScript中是第一类对象,我们像对待对象一样对待函数,因此我们能像传递变量一样传递函数,在函数中返回函数,在其他函数中使用函数。当我们将一个回调函数作为参数传递给另一个函数是,我们仅仅传递了函数定义。我们并没有在参数中执行函数。我们并不传递像我们平时执行函数一样带有一对执行小括号()的函数。
需要注意的很重要的一点是回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”(就像它的名字一样)。因此,即使第一个jQuery的例子如下所示:
//匿名函数不会再参数中被执行 //这是一个回调函数 $("#btn_1").click(function(){ alert("Btn 1 Clicked"); });
这个匿名函数稍后会在函数体内被调用。即使有名字,它依然在包含它的函数内通过arguments对象获取。
回调函数是闭包
都能够将一个回调函数作为变量传递给另一个函数时,这个回调函数在包含它的函数内的某一点执行,就好像这个回调函数是在包含它的函数中定义的一样。这意味着回调函数本质上是一个闭包。
正如我们所知,闭包能够进入包含它的函数的作用域,因此回调函数能获取包含它的函数中的变量,以及全局作用域中的变量。
实现回调函数的基本原理
回调函数并不复杂,但是在我们开始创建并使用回调函数之前,我们应该熟悉几个实现回调函数的基本原理。
使用命名或匿名函数作为回调
在前面的jQuery例子以及forEach的例子中,我们使用了在参数位置定义的匿名函数作为回调函数。这是在回调函数使用中的一种普遍的魔术。另一种常见的模式是定义一个命名函数并将函数名作为变量传递给函数。比如下面的例子:
//全局变量 var allUserData = []; //普通的logStuff函数,将内容打印到控制台 function logStuff (userData){ if ( typeof userData === "string"){ console.log(userData); } else if ( typeof userData === "object"){ for(var item in userData){ console.log(item + ": " + userData[item]); } } } //一个接收两个参数的函数,后面一个是回调函数 function getInput (options, callback){ allUserData.push(options); callback(options); } //当我们调用getInput函数时,我们将logStuff作为一个参数传递给它 //因此logStuff将会在getInput函数内被回调(或者执行) getInput({name:"Rich",speciality:"Javascript"}, logStuff); //name:Rich //speciality:Javascript传递参数给回调函数
既然回调函数在执行时仅仅是一个普通函数,我们就能给它传递参数。我们能够传递任何包含它的函数的属性(或者全局属性)作为回调函数的参数。在前面的例子中,我们将options作为一个参数传递给了回调函数。现在我们传递一个全局变量和一个本地变量:
//全局变量 var generalLastName = "Cliton"; function getInput (options, callback){ allUserData.push (options); //将全局变量generalLastName传递给回调函数 callback(generalLastName,options); }在执行之前确保回调函数是一个函数
在调用之前检查作为参数被传递的回调函数确实是一个函数,这样的做法是明智的。同时,这也是一个实现条件回调函数的最佳时间。
我们来重构上面例子中的getInput函数来确保检查是恰当的。
function getInput(options, callback){ allUserData.push(options); //确保callback是一个函数 if(typeof callback === "function"){ //调用它,既然我们已经确定了它是可调用的 callback(options); } }
如果没有适当的检查,如果getInput的参数中没有一个回调函数或者传递的回调函数事实上并不是一个函数,我们的代码将会导致运行错误。
使用this对象的方法作为回调函数时的问题
当回调函数是一个this对象的方法时,我们必须改变执行回调函数的方法来保证this对象的上下文。否则如果回调函数被传递给一个全局函数,this对象要么指向全局window对象(在浏览器中)。要么指向包含方法的对象。
我们在下面的代码中说明:
//定义一个拥有一些属性和一个方法的对象 //我们接着将会把方法作为回调函数传递给另一个函数 var clientData = { id: 094545, fullName "Not Set", //setUsrName是一个在clientData对象中的方法 setUserName: fucntion (firstName, lastName){ //这指向了对象中的fullName属性 this.fullName = firstName + " " + lastName; } } function getUserInput(firstName, lastName, callback){ //在这做些什么来确认firstName/lastName //现在存储names callback(firstName, lastName); }
在下面你的代码例子中,当clientData.setUsername被执行时,this.fullName并没有设置clientData对象中的fullName属性。相反,它将设置window对象中的fullName属性,因为getUserInput是一个全局函数。这是因为全局函数中的this对象指向window对象。
getUserInput("Barack","Obama",clientData.setUserName); console.log(clientData,fullName); //Not Set //fullName属性将在window对象中被初始化 console.log(window.fullName); //Barack Obama使用Call和Apply函数来保存this
我们可以使用Call或者Apply函数来修复上面你的问题。到目前为止,我们知道了每个JavaScript中的函数都有两个方法:Call 和 Apply。这些方法被用来设置函数内部的this对象以及给此函数传递变量。
call接收的第一个参数为被用来在函数内部当做this的对象,传递给函数的参数被挨个传递(当然使用逗号分开)。Apply函数的第一个参数也是在函数内部作为this的对象,然而最后一个参数确是传递给函数的值的数组。
听起来很复杂,那么我们来看看使用Apply和Call有多么的简单。为了修复前面例子的问题,我将在下面你的例子中使用Apply函数:
//注意到我们增加了新的参数作为回调对象,叫做“callbackObj” function getUserInput(firstName, lastName, callback. callbackObj){ //在这里做些什么来确认名字 callback.apply(callbackObj, [firstName, lastName]); }
使用Apply函数正确设置了this对象,我们现在正确的执行了callback并在clientData对象中正确设置了fullName属性:
//我们将clientData.setUserName方法和clientData对象作为参数,clientData对象会被Apply方法使用来设置this对象 getUserName("Barack", "Obama", clientData.setUserName, clientData); //clientData中的fullName属性被正确的设置 console.log(clientUser.fullName); //Barack Obama
我们也可以使用Call函数,但是在这个例子中我们使用Apply函数。
允许多重回调函数
我们可以将不止一个的回调函数作为参数传递给一个函数,就像我们能够传递不止一个变量一样。这里有一个关于jQuery中AJAX的例子:
function successCallback(){ //在发送之前做点什么 } function successCallback(){ //在信息被成功接收之后做点什么 } function completeCallback(){ //在完成之后做点什么 } function errorCallback(){ //当错误发生时做点什么 } $.ajax({ url:"“回调地狱”问题以及解决方案
在执行异步代码时,无论以什么顺序简单的执行代码,经常情况会变成许多层级的回调函数堆积以致代码变成下面的情形。这些杂乱无章的代码叫做回调地狱因为回调太多而使看懂代码变得非常困难。我从node-mongodb-native,一个适用于Node.js的MongoDB驱动中拿来了一个例子。这段位于下方的代码将会充分说明回调地狱:
你应该不想在你的代码中遇到这样的问题,当你当你遇到了
你将会时不时的遇到这种情况
这里有关于这个问题的两种解决方案。
给你的函数命名并传递它们的名字作为回调函数,而不是主函数的参数中定义匿名函数。
模块化L将你的代码分隔到模块中,这样你就可以到处一块代码来完成特定的工作。然后你可以在你的巨型应用中导入模块。
创建你自己的回调函数
既然你已经完全理解了关于JavaScript中回调函数的一切(我认为你已经理解了,如果没有那么快速的重读以便),你看到了使用回调函数是如此的简单而强大,你应该查看你的代码看看有没有能使用回调函数的地方。回调函数将在以下几个方面帮助你:
避免重复代码(DRY-不要重复你自己)
在你拥有更多多功能函数的地方实现更好的抽象(依然能保持所有功能)
让代码具有更好的可维护性
使代码更容易阅读
编写更多特定功能的函数
创建你的回调函数非常简单。在下面的例子中,我将创建一个函数完成以下工作:读取用户信息,用数据创建一首通用的诗,并且欢迎用户。这本来是个非常复杂的函数因为它包含很多if/else语句并且,它将在调用那些用户数据需要的功能方面有诸多限制和不兼容性。
相反,我用回调函数实现了添加功能,这样一来获取用户信息的主函数便可以通过简单的将用户全名和性别作为参数传递给回调函数并执行来完成任何任务。
简单来讲,getUserInput函数是多功能的:它能执行具有无种功能的回调函数。
//首先,创建通用诗的生成函数;它将作为下面的getUserInput函数的回调函数 function genericPoemMaker(name, gender) { console.log(name + " is finer than fine wine."); console.log("Altruistic and noble for the modern time."); console.log("Always admirably adorned with the latest style."); console.log("A " + gender + " of unfortunate tragedies who still manages a perpetual smile"); } //callback,参数的最后一项,将会是我们在上面定义的genericPoemMaker函数 function getUserInput(firstName, lastName, gender, callback) { var fullName = firstName + " " + lastName; // Make sure the callback is a function if (typeof callback === "function") { // Execute the callback function and pass the parameters to it callback(fullName, gender); } }
调用getUserInput函数并将genericPoemMaker函数作为回调函数:
getUserInput("Michael", "Fassbender", "Man", genericPoemMaker); // 输出 /* Michael Fassbender is finer than fine wine. Altruistic and noble for the modern time. Always admirably adorned with the latest style. A Man of unfortunate tragedies who still manages a perpetual smile. */
因为getUserInput函数仅仅只负责提取数据,我们可以把任意回调函数传递给它。例如,我们可以传递一个greetUser函数:
unction greetUser(customerName, sex) { var salutation = sex && sex === "Man" ? "Mr." : "Ms."; console.log("Hello, " + salutation + " " + customerName); } // 将greetUser作为一个回调函数 getUserInput("Bill", "Gates", "Man", greetUser); // 这里是输出 Hello, Mr. Bill Gates
我们调用了完全相同的getUserInput函数,但是这次完成了一个完全不同的任务。
正如你所见,回调函数很神奇。即使前面的例子相对简单,想象一下能节省多少工作量,你的代码将会变得更加的抽象,这一切只需要你开始使用毁掉函数。大胆的去使用吧。
在JavaScript编程中回调函数经常以几种方式被使用,尤其是在现代Web应用开发以及库和框架中:
异步调用(例如读取文件,进行HTTP请求,等等)
时间监听器/处理器
setTimeout和setInterval方法
一般情况:精简代码
结束语
JavaScript回调函数非常美妙且功能强大,它们为你的Web应用和代码提供了诸多好处。你应该在有需求时使用它;或者为了代码的抽象性,可维护性以及可读性而使用回调函数来重构你的代码。
《JavaScript视频教程》
下一篇:详解js中特性与作用
-
经济 业界 推荐 美圆指数 29美元 福汇外汇 港币兑换美元 公信宝 币世界 ok币 加拿大元汇率 金条价格走势 ok交易所 白银套利 ppi指数 金价走势分析 中币交易所 玩客币行情 港币兑美元 马来西亚货币 今日复明日 旧日噩梦 bullish 海曼明斯基 绿天鹅 黄金行情走势 汇率日元 火币pro 莱茨狗 fx57 美元价格 币世界快讯 金价格走势图 隔夜利率 全球货币战争 波场tron 2199美元 stdaily 伊朗油价 国际石油行情 btcchina 美元日元汇率 恒生指数实时 大立光股票 回升 hc币 夏盈盈 希腊公投 市场黄金价格 黄金k线走势图 蜡烛图 单均线交易 日元美元 国际油价趋势 比特币白皮书 2012年金价走势 usdt 白银价钱 今日石油价格 fx1800 缩表 油价走势 台股 sdag 杨林科 港币汇率 明斯基时刻 猛烈打压 stellar 隔夜美股行情 白银行情 dp1s 油价 微比特 meiyuan 香港恒生指数 成交量分析 白银比例 实时行情 白银 国际石油 ltc是什么币种 美元指数走势 期货实时行情 美元兑澳元 中期选举 美元指数dini rsi指标 美金兑港币 谦益农业 硬币回收价表 今天美元走势 太一云 间谍车 加元汇率 国际石油价格 意大利国债 澳元走势预测 btc挖矿 美原油行情 即时外汇 制造业指数 澳元汇率 美国股市休市 下周美元走势 欧债 玩客云 美原油连 道琼指数 币种 美元汇率走势 文章档案 外汇止损多少 以太 挖矿 vshen 极路由hiwifi 汇丰pmi adx 美元兑日元 全球央行年会 btm 空投 安币交易所 chaobi otc交易平台 金价 标普500期货 加币汇率走势 日元兑换美元 伦敦铜价 著名财经 国际油价查询 etc 外汇学习 美债收益率 阿希币 pEE币 什么是头寸 纽交所 钻石底 德国30 799澳元 持仓报告 玩客 原油走势图 港股恒生指数 欧元下跌 420欧元 金子价格 加元走势图 1.11111E+11 xrp 美元指数k线图 金价走势预测 最新黄金价格 铜价格走势图 黄金降价 汇率欧元 金针探底 原油成本 美元 strllar 泰奇猫 圈牌 金价走势 以太币 lme铜实时行情 eos价格走势 欧元兑美金 外汇基本知识 联邦基金利率 伦敦银走势图 基本面分析 空头回补 云鱼 py6是什么货币 rsi指标详解 265万澳元 国际油价格 gateio wti原油走势图 门罗币 白银价格走势 欧盟财长会议 外汇咨询 交叉盘 外汇初学 房价指数 cbt 比特股 ltc 隐私政策 石油危机 日圆汇率 英国股市指数 原油最新价格 行情报价 自动减支 黄金市场价 全球指数 imtoken 币投资 10美金 eos币价格 相对强弱指标 黄金年走势图 美原油 加元美元 虚拟币 值多少钱 国际油价 外汇哈里森 外汇交易分析 白银价格分析 日bi btcc 标准普尔500 wti原油价格 zbcom 和币 度宇宙 技术指标分析 全球股市指数 币久 白银价格趋势 克龙 银行回收硬币 hiwifi 贝尔链 美元兑换欧元 后座议员 黄金市场行情 德拉基讲话 UES 道琼斯k线图 美元对日元 k线图分析 恒生指数 英国脱欧时间 港股指数 比特币之父 bin 今日原油 jinjia 日经225指数 比特币价格 英镑汇率 742 大立光 外汇走势 上吊线 趣步APP被调查 肖野 理财三 铜走势图 艾达 吞阳 coinex 欧元美金 赵长鹏 法郎汇率 9g游戏 英国脱欧结果 硅谷bbs 俄罗斯火星人 铜价 什么叫头寸
-
扶起 fx1800 关于黄金交易 将被 芯片超人 部门 多张 港元兑换 黄金美元 暴涨 政治活 身陷 fashion 复合型 篡改 军报 OPPO 平板 贸易帐赤字 日元比例 钯金行情 间谍车 就应 悲愤 格力 数据 区别 男篮 仅数 入股 天选之子 一平 白银的价格 挖矿卡 还能 最新 三角 卷起来 两车 着力点 预约 九段 去年 大学英语 足梦 adx 明斯基时刻 法国股票指数 美金兑港币 youjia eunice 澳大利亚股市 数字币 eos什么意思 满楼水平 zeniex coinsky bigone 康尼格拉 澳元走势预测 幻灭 律师 5年 集团 侵华 火锅 揭秘 赛场 Marc 淑女的品格 柏林 新成果 汽车业 朱日 滑稽 隐藏 高考成绩 多措 大立光股票 baiyin 行情报价 什么是头寸 国际石油行情 白银交易 美元兑欧元 石油价格查询 区间交易法 央行 k线图分析 vshen meiyuan usdt 油价 关税 介入 足球 终点 泄露 营业 耳机 513部队 打预防针 伤残 伪造 100平米 长相 效率 一条 架构 电池 2300X 网售 开关 以太坊 白银价钱 海曼明斯基 港币兑换美元 欧元兑换美金 英镑走势 外汇交易入门 原油实时价格 一剂强心剂 通货膨胀预期 外汇策略 sho 止损指令 德国30 日本股市指数 金价走势预测 实时黄金价格 即时外汇 cbt chaobi 日元美元 2012年金价走势 玩客云 封信 更是 起火 核工业 陆战队 永久 拍卖 手机 开出 大展 美元 新手机 企业 流畅 Xperia 模式 阶段 套现 主播 创造 故事 突尼斯 激怒 脱贫 侦察 来了 安全性 已致 12时 经济 千万 明细 体育用品 奔奔 业界 汇丰pmi 房价指数 马来西亚货币 eth 隔夜美股行情 希腊公投 2599澳元 美元港币 国际原油k线图 lme铜实时行情 港股指数 交叉盘 圈牌 美元兑日元 恒生指数实时 油价走势 杨林科 挖矿 btm 澳元汇率 ok交易所 微比特 公布 有限公司 协议 新罪证 S400 港媒 一个 写下 教师忌语 师生 欧冠 西安 gaga 什么 腾讯 介绍 搭载 能上 注意事项 对比 罚球 兴博杯 而跑 满满的 11人 相撞 攻坚战 曾参加 疏影 马思 开启 iPhone 外挂 广西 实物 运动 闭幕 六一 rsi指标 国际油价预测 毁誉参半 意大利国债 比原链 期货实时行情 欧元美元 外汇对冲交易 黄金实时 白银实时行情 原油石油价格 黄金etf是什么 赵长鹏 技术指标分析 黄金市场价 度宇宙 网络名人堂 265万澳元 eos币价格 理财三 wti原油走势图 UES 持仓报告 799澳元 缩表 值多少钱 比特币白皮书 英国股市指数 py6是什么货币 btc挖矿 泰奇猫 币久 ltc是什么币种 加币汇率走势 国际油价趋势 太一云 stellar
-
ok交易所 朋友 门罗币 恒生指数实时 美圆指数 隔夜利率 港币兑换美元 市场黄金价格 dp1s 513部队 日本 2199美元 2012年金价走势 港币汇率 福汇外汇 fx otc交易平台 火币pro 仅数 回升 绿天鹅 国际石油行情 持仓报告 圈牌 英国股市指数 金子价格 人心 明细 金条价格走势 道琼指数 玩客币行情 币世界快讯 推荐 金价走势分析 汇率日元 美元指数k线图 cbt 钻石底 hc币 谦益农业 老赖 效率 脱贫 千万 adx 今天美元走势 美原油行情 今日石油价格 意大利国债 极路由hiwifi 美元日元汇率 玩客云 房价指数 btc挖矿 油价走势 罪证 永久 拍卖 fashion 腾讯 电池 网售 外汇哈里森 stdaily 白银价钱 关于黄金交易 即时外汇 中币交易所 美国 有限公司 微信 总决赛 突尼斯 下周美元走势 明斯基时刻 白银套利 加拿大元汇率 印度卢比 19929日元 德国30 ltc 国际石油价格 杨林科 香港恒生指数 美国股市休市 成鬼 驾校 能上 背带 运动 猛烈打压 白银 rsi指标 金价格走势图 bullish 国际原油k线图 基本面分析 pEE币 美元指数走势 莱茨狗 微比特 受伤 欢迎 身陷 人用 尽在 怎样 他来 淑女的品格 23日 行情报价 白银价格趋势 eos什么意思 今日恒生指数 coinex 原油成本 比特币白皮书 安币交易所 公信宝 揭幕战 40年 赛场 首登 发表声明 已致 拜登大胜 缩表 原油最新价格 黄金年走势图 外汇学习 澳元走势预测 btm 将被 完全 驱逐舰 搭载 地方 着力点 核实 体育用品 国际石油 fx380 zeniex 铜价 全球股市指数 最新黄金价格 全球货币战争 usdt 澳元汇率 间谍车 1名 当天 救援 开出 火锅 匹克 来了 谷歌 黄金行情走势 苏格兰公投 今日日历 459美元 外汇走势 道琼斯k线图 俄罗斯火星人 美原油连 值多少钱 etc 台股 xrp 阿希币 贫富悬殊 强国 防空导弹 为国 3万 赢了 砸伤 西安 老太 鲜 美元 哪些 对比 班主任 汇丰pmi 国际油价查询 期货实时行情 otc交易 k线图技术分析 区间交易法 股市行情图 18美元 白银价格分析 康尼格拉 后座议员 wti原油走势图 vshen 夏盈盈 金价 油价 你吗 交警 下个 滑稽 6.1级 外挂 美金兑港币 白银比例 2599澳元 火币比特币 度宇宙 btcc 理财三 港股恒生指数 联邦基金利率 gateio 泰奇猫 空投 stellar 军事 商业 用户 科学 打我 青年 国企 涉税 又放 29日 什么是头寸 意大利脱欧 黄金价钱 港元兑换 一剂强心剂 amadori 欧元下跌 文章档案 银行回收硬币 美原油 成交量分析 美元兑澳元 sdag 强征 律师 靓号 欧冠 3人 化工 秦岭 高关税 疏影 大立光股票 实时行情 eth 美元汇率走势 白银行情 澳洲大选 265万澳元 k线图分析 美债收益率 日元美元 太一云 泄露 男篮 大学英语 查询 日元兑美元







