您现在的位置:新闻首页>资本 > 详解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 俄罗斯火星人 铜价 什么叫头寸
-
千万 明细 满满的 蛋糕 不见 磋商 广西 恶意 传神 6.1级 已致 实物 daily怎么读 优缺点 首登 青春 献金 发 19日 异常 12时 一平 超萌 汇率日元 实时原油价格 美国原油价格 白银行情分析 一字 道歉 军报 系列化 神器 学院 500元 再度 明斯基时刻 张思聪 6000澳元 美元日元 云搬家 阿希币 贫富悬殊 3人 淑女的品格 躁动 小米 高通 班主任 好不好 5孔 英镑美元汇率 国际油价行情 黄金价格表 传产 achain ht币 巴比特 加拿大就业 wti原油走势图 首尔至平壤 S400 40年 部门 靓号 出来 助阵 丝路 仅数 足梦 欧盟财长会议 消弥 全球主要股指 外汇交易入门 财经数据 拜登大胜 实时黄金价格 白银期货走势 1名 校外 暗访 不顾 88岁 高陵区 端午 小粗 圆梦 开锣 新手机 装修 瓷砖 还是 铺瓷砖 青出于蓝 赛场 沈阳 惨败 日显 11人 三个 中方 正式 8页 会议 国家 税务 合同 商贸城 防弹 海峡 滑稽 大嘴 狠话 露肩 背带 天选之子 九段 京东 师范大学 猛烈打压 国际油价预测 加拿大元汇率 gasstation 货币是商品吗 美元走势图 macd指标 土耳其汇率 如何挖比特币 一剂强心剂 李笑来 okex solb 比特币美元 外汇基础 已兑现 外汇交易头寸 澳大利亚大选 2019通货膨胀率 法国指数 台湾股市行情 油价走势图 德拉基 贝克链 联邦基金利率 莱特币行情 极路由hiwifi 全球货币战争 pai币 新时代 野蛮生长 罪证 人心 老赖 马尾 打印机 黏性 发布 剑客 超高 闹事者 法官 六国 加征 魔力 29日 问题 查询 下周美元走势 2199美元 独股一箭 今天原油价格 国际石油行情 欧元兑美元 交易侠 领先指标 etf黄金持仓量 法国左翼 HSR 墨西哥汇率 btc价格 耶伦讲话 维也纳会议 质数币 标普500期货 29美元 火币pro 波场tron 今日复明日 天然气 币世界 推荐 种族歧视 更多 技术活 怎么 学习 上班 港媒 规模 扩大 驱逐舰 女兵 身材 很深 雨夜 救援 中学生 门生 农场 还能 花样 短裤 薰衣 一下 补贴 茶饮 联手 鲜 选址 盘点 变老 入网 这款 套现 获利 项目 招办 发言人 政府 买单 灵魂 考生 依法 合理 选择 实用 哪个 窍门 地面 匹克 舞台 秦岭 挑战赛 男篮 空砍 离开 心痛 横扫 出局 年近 骑行 天下 朝气 看不 闪耀 已成 突尼斯 渡船 德国 受审 重创 文件 此前 效率 装甲车 计划 少将 部长 曾参加
-
玩客币行情 美圆指数 黄金价格表 币世界快讯 已致 金价格走势图 ok币 港币兑换美元 美元价格 波场tron 间谍车 公信宝 强征 有了 首登 国际石油行情 huobi 加拿大就业 外汇交易分析 新时代 全面 驱逐舰 一字 国家 5400mAh 经济 千万 绿天鹅 ppi指数 马来西亚货币 6000澳元 福汇外汇 美国原油价格 云搬家 技术指标分析 德拉基讲话 今日复明日 不断 计划 监视 师范大学 业界 明斯基时刻 房价指数 汇率日元 金条价格走势 德国新闻中文 传产 achain 外汇基础 美元跌下神坛 巴比特 道琼斯理论 fx57 玩客云 太空 罪证 513部队 部门 还能 剑客 匹克 助阵 满满的 超高 专属 富商 打我 闹事者 攻坚战 责成 仍未 不见 开启 高层次 新思想 500元 2199美元 港币兑美元 黄金行情走势 什么是头寸 加拿大元汇率 消弥 全球主要股指 美元日元 外汇交易入门 财经数据 铜期货行情 新西兰货币 拜登大胜 赵长鹏 算力蜂 日经225指数 伦敦银走势图 wti原油走势图 实时黄金价格 极路由hiwifi 中币交易所 29美元 以太币 ltc是什么币种 阿希币 币世界 白银期货走势 jinjia 推荐 中国佬汉堡 会为 暗访 人心 规模 中学生 3人 小粗 滤镜 黏性 限制 写入 装修 选材 瓷砖 铺地板 还是 铺瓷砖 青出于蓝 赛场 齐聚 沈阳 惨败 BOTN2018 数字 连接 出现 欧美 曾参加 系列化 滑稽 狠话 连衣裙 荣耀 29日 20倍 姐姐 12nm 2300X 2 班主任 神器 5孔 大立光股票 猛烈打压 黄金k线走势图 市场黄金价格 金价走势分析 英镑美元汇率 miota 国际油价行情 usdollar gasstation 货币是商品吗 美元走势图 macd指标 土耳其汇率 如何挖比特币 一剂强心剂 李笑来 diorsman 美原油价格 fx 比特币美元 已兑现 美国中期选举 外汇交易头寸 美元实时汇率 澳大利亚大选 ht币 天然气价格 faang 2019通货膨胀率 吞噬太阳国语 法国指数 美金兑欧元 台湾股市行情 油价走势图 德拉基 贝克链 联邦基金利率 莱特币行情 breakeven 外汇止损多少 ltc cbt 加元汇率 全球货币战争 火币pro ok交易所 莱茨狗 金子价格 首尔至平壤 野蛮生长 欢迎 40年 赢了 行凶 2018 几种 持续 鲜 测试版 模式 代人 根据 心痛 是他 创造 一偷 道歉 开发商 时限 军报 朋友 预期 中将 OPPO 结构 技你 过半 23日 轻工 圆满 手表 回升 下周美元走势 自动减支 行情报价 今日石油价格 美元汇率走势 著名财经 白银价钱 bullish 欧元兑美元 交易侠 白银交易 领先指标 dax etf黄金持仓量 法国左翼 香港恒生 HSR 纳斯达克100 墨西哥汇率 btc价格 白银价格分析 耶伦讲话 维也纳会议 什么叫头寸 贝尔链 外汇走势 原油成本 度宇宙 全球指数 相对强弱指标 质数币 铜走势图 标普500期货 k线图分析 10美金 虚拟币