zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

了不起的回调函数

2023-02-25 18:19:06 时间

关于js的回调函数,在各大平台已经被写烂了,我也看了很多别的大神写的帖子,我也在想怎么可以比较明白的将这个东西讲明白,今天我就尝试一下,认真看完,相信是有一些用处的。

想搞明白回调函数之前,先看懂我下面说的这段话, 有几个概念需要搞明白js中的同步和异步,或者叫阻塞和延迟,这就是为什么同步的函数有概率卡死,说直白一些,同步就是代码由上而下执行,中间如果有问题,那就等着,直到问题解决掉代码才会接着执行,但是我们在写js的过程中,其实很少有这种情况,原因是js本身就是一个异步编程语言,所谓的异步就是你慢没事,我跳过你,你啥时候好了,你再执行,这句话反映到代码上就是延迟式编程也就是异步编程,但是js怎么知道你慢还是他快呢?这是个很棘手的问题,所以js将我们常见的一些代码写法归类,归类为两种任务列表,一种叫做宏任务,一种叫微任务,比如常见的setTimeout、setInterval、Ajax请求等就是宏任务,常见的Promise、async/await等就是微任务,但是就是有了分类之后,怎么按照顺序执行呢?js做法很聪明,他将宏任务排列起来,专业一点叫做任务队列,存储到栈中,栈的特点就是先进后出,所以,最先被收录的宏任务,是最后被执行的,然后一个一个的执行,但是怎么知道是不是执行完了呢?js提供的了一种机制来解决这种执行不彻底的问题,这个东西叫做事件循环,eventLoop,这个eventLoop就像巡逻队一样,一遍遍的在当前的执行上下文中进行搜索,(这里的执行上下文,常见的有两种,一种是函数执行上下文,一种是全局执行上下文)找到了宏任务就执行,微任务是什么执行的呢?你可以理解为随时执行,他可以在同步函数执行之后立即执行,也可以在上一个宏任务执行结束,下一个宏任务执行之前,中间有微任务就会执行,微任务不执行结束,下一个宏任务是不会执行的,所以你可以理解为只要执行下一个宏任务的时候,已经没有微任务可以执行了,上面这段话提供的信息和今天要说的回调函数有很大的关系,但是因为不是讲事件循环和宏微任务的,所以不展开说,下面说为什么一定要有回调函数

回调函数:正常的函数是由外往内传递参数进行使用参数,回调函数是拿到参数之后反过来调用外部函数的一个过程,再说的简单一点,就是一个函数调用另一个函数,另一个函数的参数是他的父函数的形参,如果你觉得有点绕,我们开始写代码

  • 代码演示:
setTimeout(() => {
	let p = 0;
}, 1)

想将p的值打出来,拿出来用,这个时候我们可以直接使用全局变量进行接收,进而使用全局变量 比如:

let _x = 5

setTimeout(() => {
	let p = 666;
	_x = p
	console.log(_x) // 666 // 这里其实被赋值了,但是因为是后执行的,所以即使改了,也不会被外部使用
}, 1)

console.log(_x) // 5

这里的_x有没有被替换呢?当然是被替换了,可以验证,我们在setTimeout打印出来的就是666,但是这样的话,我们就没办法将p的值拿出来进行使用了,如果你写js时间久了,一眼就可以看出来,这样外部的_x一定是不会变的,原因是setTimeout是延迟执行的,也就是说我们打印_x的时候,setTimeout还没有执行呢,所以当然_x不会被改变,这是一种潜意识,写的多了的一种想当然的理解,当然这种理解是对的,只是从专业上来说setTimeout是宏任务,会被执行队列按照时间执行,这也侧面说明了js的代码不会由上而下执行的,他是异步的,理解了我最开始写的那段话,这里就比较好理解了,下面我们接着改

let _x = 5

setTimeout(() => {
	let p = 666;
	_x = p
}, 1)

setTimeout(()=>{
	console.log(_x) // 666
},1000)

这样改了之后 _x就是被赋值了,原因是他也是宏任务,但是他的延迟更久,所以等到他被执行的时候,_x已经被赋值了,这样是不是好理解了,但是如果我们的代码写成这样,那就是我们常说的?一样的代码,按照这个思路,是不是我们想要进行使用p的话值,都要写setTimeout,还要设置不同的时间?简直是无法维护的,这个时候就需要另一种写法了,比如下面的

let getP = (p)=>{
	console.log(p) // 666
}
setTimeout(() => {
	let p = 666;
	getP(p)
}, 1)

这样会好一些,但是不太好,比如我如果想要不同的函数使用怎么办呢?岂不是无法写代码了?这种只是一种比较直接暴力的写法,但是属于写死了,那么有没有可能将函数作为一个参数进行使用呢?比如下面的代码:

let getP = (p) => {
	console.log(p) // 666
}
setTimeout((callback) => {
	let p = 666;
	callback(p)
}, 1, getP)

这样写的话,就可以将函数作为参数,那么不管什么函数需要用,都是可以直接作为参数进行传递调用的,这种写法就是回调函数的写法,他可以解决我们上面说的问题

当然,这个只是其中一个场景,很多场景都可以使用回调函数进行,比如一些文件操作的,希望文件上传结束进行执行的一些操作,可以使用回调函数,请求之后的操作也可以使用回调函数js中回调函数应用是非常广的,也是非常好用的一种写法,还是很值得我们深究一下的,