当前位置: 首页 > news >正文

JavaScript中的回调函数(callback)

前言

callback,大家都知道是回调函数的意思。如果让你举些callback的例子,我相信你可以举出一堆。但callback的概念你知道吗?你自己在实际应用中能不能合理利用回调实现功能? 我们在平时的学习中容易犯不去深究的病,功能实现了也就不再去追其原由,对一些概念模模糊糊。如果对callback没有一个清楚的理解,估计你在学习Node.js后会崩溃,因为callback是Node.js三大核心之一。

一、回调函数

回调函数的概念

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.

以上是Google的解释,非常清晰简明,小编令人窒息的四级英语水平都能看懂。
下面给一个回调的例子

function doSomething(msg, callback){//callback只是一个参数名而已,可以叫任意名
    alert(msg);
    if(typeof callback == "function"){ 
        callback();
    }
} 
doSomething("回调函数", function(){
    alert("匿名函数实现回调!");
}); 

我们再来看几个经典的回调函数代码,我保证你一定用过他们:

回调函数例子.png

从上面的例子,我们可以看出回调与同步、异步并没有直接的联系,回调只是一种实现方式,既可以有同步回调,也可以有异步回调,还可以有事件处理回调和延迟函数回调,这些在我们工作中有很多的使用场景。


二、把使用this对象的函数作为回调函数(陷阱)

当回调函数是一个使用this对象的函数时,我们必须改变执行回调函数的调用对象来保证this对象的上下文。在讲这个问题之前,我们必须先了解JS于this的指向。我们这里不详细谈这个问题,我直接说下自己学习过程总结的判断this指向的两条经验(既然是自己的经验,那就不一定对,希望大家指正):
(1)this的指向是在函数执行的时候确定的,在函数定义的时候是确定不了,实际上this的最终指向的是那个调用它的对象
(2)调用执行函数时,“.”前面是什么,this就是什么。前面没有对象,就是window了。

回到使用this对象的函数作为回调函数这个问题,我们首先来看看下面这段代码:

//定义一个拥有一些属性和一个方法的对象 
//我们接着将会把方法作为回调函数传递给另一个函数
var clientData = {
    id: 096545,
    fullName: "Not Set",
    //setUsrName是一个在clientData对象中的方法
    setUserName: function (firstName, lastName){
        this.fullName = firstName + " " + lastName;
    }
} 

function getUserInput(firstName, lastName, callback){
    //code .....

    //调用回调函数存储
    callback(firstName, lastName);
}

getUserInput("Barack","Obama",clientData.setUserName);
console.log(clientData.fullName);  //Not Set
console.log(window.fullName);  //Barack Obama
//结论:谁是主调函数,this就是指向谁

在上面的代码中,当clientData.setUsername被执行时,this.fullName并没有设置clientData对象中的fullName属性。相反,它将设置window对象中的fullName属性,这是因为callback中的this指向window的缘故。

使用Call和Apply函数来改变this指向

我们可以使用Call或者Apply函数来解决上面你的问题。到目前为止,我们知道了每个Javascript中的函数都有两个方法:Call 和 Apply。这些方法被用来设置函数内部的this对象以及给此函数传递变量
这里我们演示Apply函数实现,Call函数类似。(call接收的第一个参数为被用来在函数内部当做this的对象传递给函数的参数被挨个传递Apply函数的第一个参数也是在函数内部作为this的对象,然而最后一个参数确是传递给函数的值的数组

Apply函数:

//注意到我们增加了新的参数作为回调对象,叫做“callbackObj”
function getUserInput(firstName, lastName, callback ,callbackObj){
         //code .....
        callback.apply(callbackObj, [firstName, lastName]);
}

getUserInput("Barack", "Obama", clientData.setUserName, clientData);
console.log(clientData.fullName); //Barack Obama

使用Apply函数正确设置了this对象,我们现在正确的执行了callback并在clientData对象中正确设置了fullName属性


三、回调函数是实现异步编程的利器

在程序运行中,当某些请求过程漫长,我们有时没必要选择等待请求完成继续处理下一个任务,这时使用回调函数进行异步处理可以大大提高程序执行效率。例如:AJAX请求。若是使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。实际开发中,经常在javascript中使用异步调用!
下面有个使用AJAX加载XML文件的示例,并且使用了call()函数,在请求对象(requested object)上下文中调用回调函数。

function fn(url, callback){
    var httpRequest;    //创建XHR
    httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() :   //针对IE进行功能性检测
    window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : undefined;
  
    httpRequest.onreadystatechange = function(){
        if(httpRequest.readystate === 4 && httpRequest.status === 200){  //状态判断
           callback.call(httpRequest.responseXML); 
        }
    };
    httpRequest.open("GET", url);
    httpRequest.send();
}
 
fn("text.xml", function(){    //调用函数
    console.log(this);          //此语句后输出
});
 
console.log("this will run before the above callback.");  //此语句先输出

我们请求异步处理,意味着我们开始请求时,就告诉它们完成之时调用我们的函数。在实际情况中,onreadystatechange事件处理程序还得考虑请求失败的情况,这里我们是假设xml文件存在并且能被浏览器成功加载。这个例子中,异步函数分配给了onreadystatechange事件,因此不会立刻执行。
最终,第二个console.log语句先执行,因为回调函数直到请求完成才执行。

在Javascript编程中回调函数经常以几种方式被使用,尤其是在现代web应用开发以及库和框架中:

  • 异步调用(例如读取文件,进行HTTP请求,动态加载js文件,加载iframe资源后,图片加载完成执行回调等等)
  • 事件监听器/处理器
  • setTimeout和setInterval方法
  • 一般情况:精简代码

4.“回调地狱”问题以及解决方案

这么多回调嵌套,我还没遇到过。下面内容是直接从网上copy过来,大家看下,还是很好理解的。
在执行异步代码时,无论以什么顺序简单的执行代码,经常情况会变成许多层级的回调函数堆积以致代码变成下面的情形。这些杂乱无章的代码叫做回调地狱,因为回调太多而使看懂代码变得非常困难。我从node-mongodb-native,一个适用于Node.js的MongoDB驱动中拿来了一个例子。这段位于下方的代码将会充分说明回调地狱:

var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
   p_client.open(function(err, p_client) {
       p_client.dropDatabase(function(err, done) {
           p_client.createCollection('test_custom_key', function(err, collection) {
               collection.insert({'a':1}, function(err, docs) {
                   collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
                       cursor.toArray(function(err, items) {
                           test.assertEquals(1, items.length);
 
                           // Let's close the db
                           p_client.close();
                       });
                   });
               });
           });
       });
   });

你应该不想在你的代码中遇到这样的问题,当你当你遇到了-你将会是不是的遇到这种情况-这里有关于这个问题的两种解决方案。

  • 给你的函数命名并传递它们的名字作为回调函数,而不是主函数的参数中定义匿名函数。
  • 模块化:将你的代码分隔到模块中,这样你就可以导出一块代码来完成特定的工作。然后你可以在你的巨型应用中导入模块。

相关文章:

  • com教程(使用VS2015编写ATL示例)
  • 控制台console使用MFC库函数,Cout输出CString的方法
  • PDFsharp使用介绍
  • 详解开源免费且稳定实用的.NET PDF打印组件itextSharp
  • 机器人是什么
  • C++中L和_T()之区别
  • 关于 wcout 输出中文的问题
  • 非静态成员必须与特定对象相对
  • 压缩文件修复
  • 循环冗余校验(CRC)算法入门引导
  • C++ getline函数用法详解
  • cout后面输出时加endl和不加endl的区别
  • iostream
  • ChromeNativeMessaging 原生消息通信
  • IT前端开发和后端开发
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • ERLANG 网工修炼笔记 ---- UDP
  • exif信息对照
  • iOS 颜色设置看我就够了
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • Java 最常见的 200+ 面试题:面试必备
  • JavaScript 一些 DOM 的知识点
  • spring学习第二天
  • 复杂数据处理
  • 诡异!React stopPropagation失灵
  • 计算机在识别图像时“看到”了什么?
  • 前端技术周刊 2019-02-11 Serverless
  • 如何设计一个比特币钱包服务
  • 移动端唤起键盘时取消position:fixed定位
  • gunicorn工作原理
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​queue --- 一个同步的队列类​
  • #HarmonyOS:软件安装window和mac预览Hello World
  • #pragma multi_compile #pragma shader_feature
  • #stm32驱动外设模块总结w5500模块
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (4)Elastix图像配准:3D图像
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (JS基础)String 类型
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (十五)使用Nexus创建Maven私服
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • ****Linux下Mysql的安装和配置
  • .net core 控制台应用程序读取配置文件app.config
  • .Net 代码性能 - (1)
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • @EnableWebMvc介绍和使用详细demo
  • @property括号内属性讲解
  • [20171101]rman to destination.txt