【一句话攻略】彻底理解JS中的回调(Callback)函数

2024-01-31 05:59

本文主要是介绍【一句话攻略】彻底理解JS中的回调(Callback)函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作为JS的核心,回调函数和异步执行是紧密相关的,也是必须跨过去的一道个门槛。

那么究竟什么是回调函数(Callback),其实回调函数并不复杂,明白两个重点即可:

1. 函数可以作为一个参数在另一个函数中被调用。

2. JS是异步编程语言,这就是说JS代码的执行顺序并不是从上至下按部就班完成的。大多数语言都是同步编程语言,比如现在我们有3行代码,那么系统一定是一行一行按顺序向下执行的,第一行执行完了,执行第二行,紧跟着最后执行第三行,你可能会说这不是废话吗?且慢,在JS里则不尽然,比如有3行代码,并不是排在最前面的代码就是最先执行完毕的,很有可能是最后一行语句最先执行完,然后排在最前面的那行反而是最后执行完毕的,所以我们说JS是异步编程语言。

下面以node.js为例,举一个例子保证你在3步之内搞清楚究竟什么叫回调函数:

STEP 1:

var fs = require("fs");
var cfunction f(x) {console.log(x)
}function writeFile() {fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {if (!err) {console.log("文件写入完毕!")c = 1}});
}c = 0
writeFile()
f(c)

以上代码不难理解,就是设置一个全局变量c = 0,然后执行writeFile函数(也就是写入一个文件input.txt),这个函数里面有一行c = 1,函数执行完毕之后再跳出来调用f()函数,f()函数很简单,就是把打印一个变量,仅此而已。

按照 “正常” 逻辑,首先c=0,然后调用writeFile函数,该函数里面有一句c = 1,最后再调用f(c),又因为调用writeFile()是在f(c)之前,所以c=1这条语句肯定是会被执行到,那么结果应该是打印1,但是万万想不到,结果竟然是0,明明我们在writeFile函数里我们重新对c进行了赋值,为什么结果还是0呢?

因为程序运行到writeFile()这一行的时候,是一个比较耗时的IO操作,JS碰到这种操作并不会停在原地一直等待直到函数执行完毕,而是直接运行下一条代码(即f(c)),而此时 c = 1这一行代码其实并没有被执行到,所以打印出来的结果还是0 ! 

那你肯定会说,要解决这个问题还不容易,我们把调用f(c)也放进writeFile函数里面不就行了呗!这样就能保证c = 1之后再调用f(c)了吧?没错,就这么简单:

STEP 2:

var fs = require("fs");
var cfunction f(x) {console.log(x)
}function writeFile() { fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {if (!err) {console.log("文件写入完毕!")c = 1f(c)}});
}c = 0
writeFile() 

这个代码的逻辑不需要多说了吧,因为实在太简单了,就是把f(c)放进了writeFile()里面,那么c=1必然会被执行到,然后才执行f(c),不用多说,结果肯定是显示为1。但是改成这样并不完美,因为这么做就相当于将f()"焊死"在writeFile()里了,如果此处我最终想调用的函数不是f()而是别的其他函数咋整?难不成要写几个不同的writeFile(),而他们之间的区别仅仅是最后调用的那个函数不同?这样也太笨了吧,于是今天的主角:“关键字” callback 登场了。(准确地说callback并不真的是Javascript里的关键字,但是鉴于大家都约定成俗把callback这个单词作为回调函数的默认选择了,这里姑且就不严谨地称它为"关键字"吧)

STEP 3:

var fs = require("fs");function f(x) {console.log(x)
}function writeFile(callback) { //callback,表示这个参数不是一个普通变量,而是一个函数fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {if (!err) {console.log("文件写入完毕!")c = 1callback(c) // 因为我们传进来的函数名是f(),所以此行相当于调用一次f(c)}});
}
var c = 0
writeFile(f) // 函数f作为一个参数传进writeFile函数

经过改造后的代码出现了两次callback,第一个callback出现在writeFile的形参里,起定义的作用,表示这个参数并不是一个普通变量,而是一个函数,也就是前面所说的重点1,即所谓的“以函数为参数”。 第二个callback出现在c = 1下面,表示此处“执行”从形参传递进来的那个函数。这样一来,writeFile()函数在执行完毕之后到底调用哪个函数就变“活”了,如果我们想writeFile()函数执行完之后并不是像第二个例子那样只能调用f(),而是还有别的函数比如说x() y() z(),那么只需要写成 writeFile(x),writeFile(y)... 就行了。

我相信你已经看明白上面的代码,因为实在并不高深,那么我们现在开始用一句话攻略做一个总结:

在大多数编程语言中,函数的形参总是从外向内传递参数,但在JS中,如果形参碰到“关键字” callback 则完全相反,它表示从内向外反向调用某个外部函数。

PS: 此处并不一定非要写为“callback”,你可以任意写成abc, iloveyou...等等随你高兴。callback只是一种约定俗成的写法,它明确地告诉代码阅读者:此处是一个回调函数。

有时候,我们会看到一些函数的形参列表里直接嵌套一个函数的情况,其本质上仍然是回调函数,因为没有了函数名,所以也称匿名函数。

如本例如果要写成这种风格的话就是长成这样了:

var fs = require("fs");function writeFile(callback) { fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err) {if (!err) {console.log("文件写入完毕!")c = 1callback(c) }});
}
var c = 0
writeFile(function (x) {console.log(x)
})

writeFile()函数不变,只是在调用它的时候,直接将函数体嵌在参数列表里了,其作用跟上一个例子完全一样。其实在本例中,fs.writeFile函数后面也有一个匿名回调函数 function (err) {},这个函数表示当文件写入完毕后,就回调它,如果在写入过程中出现了错误,则通过变量err携带出来。我相信有了前面的铺垫,您已经肯定能理解它的含义了,事实上这种写法在JS里是出现频率最高的主流风格。

【补充】在JS里,当然也并非所有操作都是异步的,比如for循环,无论这个for循环需要耗时多长,系统也一定会等它转完之后才会执行下面的语句。我所了解的会产生异步执行的操作大概有以下几种:

定时器、建立网络连接、读取网络流数据、向文件写入数据、Ajax提交、请求数据库服务,等等。

如果至此你还是没弄清楚到底什么是回调函数,那么绝不是您的问题,而一定是我的表达有不足之处,欢迎留言探讨!

 

这篇关于【一句话攻略】彻底理解JS中的回调(Callback)函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/662768

相关文章

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

kotlin的函数forEach示例详解

《kotlin的函数forEach示例详解》在Kotlin中,forEach是一个高阶函数,用于遍历集合中的每个元素并对其执行指定的操作,它的核心特点是简洁、函数式,适用于需要遍历集合且无需返回值的场... 目录一、基本用法1️⃣ 遍历集合2️⃣ 遍历数组3️⃣ 遍历 Map二、与 for 循环的区别三、高

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

MySQL中COALESCE函数示例详解

《MySQL中COALESCE函数示例详解》COALESCE是一个功能强大且常用的SQL函数,主要用来处理NULL值和实现灵活的值选择策略,能够使查询逻辑更清晰、简洁,:本文主要介绍MySQL中C... 目录语法示例1. 替换 NULL 值2. 用于字段默认值3. 多列优先级4. 结合聚合函数注意事项总结C

Java8需要知道的4个函数式接口简单教程

《Java8需要知道的4个函数式接口简单教程》:本文主要介绍Java8中引入的函数式接口,包括Consumer、Supplier、Predicate和Function,以及它们的用法和特点,文中... 目录什么是函数是接口?Consumer接口定义核心特点注意事项常见用法1.基本用法2.结合andThen链

MySQL 日期时间格式化函数 DATE_FORMAT() 的使用示例详解

《MySQL日期时间格式化函数DATE_FORMAT()的使用示例详解》`DATE_FORMAT()`是MySQL中用于格式化日期时间的函数,本文详细介绍了其语法、格式化字符串的含义以及常见日期... 目录一、DATE_FORMAT()语法二、格式化字符串详解三、常见日期时间格式组合四、业务场景五、总结一、

golang panic 函数用法示例详解

《golangpanic函数用法示例详解》在Go语言中,panic用于触发不可恢复的错误,终止函数执行并逐层向上触发defer,最终若未被recover捕获,程序会崩溃,recover用于在def... 目录1. panic 的作用2. 基本用法3. recover 的使用规则4. 错误处理建议5. 常见错

Node.js net模块的使用示例

《Node.jsnet模块的使用示例》本文主要介绍了Node.jsnet模块的使用示例,net模块支持TCP通信,处理TCP连接和数据传输,具有一定的参考价值,感兴趣的可以了解一下... 目录简介引入 net 模块核心概念TCP (传输控制协议)Socket服务器TCP 服务器创建基本服务器服务器配置选项服