关于javascript中单线程、异步、回调、setTimeout 、Webapi的一些东西。
额 一直都对于js中的回调函数和异步操作这两块儿认知不够清晰,经过多方查证研究各种谷歌度娘后,终于有了一个觉得比较靠谱的答案,现在记录下来,主要还是看了YouTube上一个歪果仁的演讲,明白了很多。
首先:需要搞清楚回调跟异步没有毛线关系,不是只要看到有回调就说明这是一个异步操作。你在写代码的时候可以用回调也可以不用回调,同理当你觉得要用回调的时候你可以用同步回调也可以用异步回调。
先看一下回调函数的定义吧:
In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback
以上解释来自维基百科。逐字逐句翻译过来就是:‘在计算机编程中,一个回调函数指的是作为一个参数传递给别的代码并期望它在一定时间后执行的可执行代码。它可能在同步回调中直接执行,也可能在异步回调中等待一段时间后执行’
哎妈呀,前两天看维基百科的解释还没这么拗口呢呀,最近有做了修改了可能。
其实从中我们就能知道回调有两种形式一种是同步的synchronous callback 另一种是异步的asynchronous callback
OK,我们分别来写一个栗子:
同步回调的🌰
// 定义一个简单函数a 将来做为回调函数传递给别人使用
function a() {
console.log('人家只会打印而已')
}
// 定义一个函数b 它接受一个回调函数作为自己的参数
function b(cb) {
console.log('我不仅自己会打印,还能调用别人的打印')
cb()
}
// 调用函数b
b(a);
输出结果:我不仅自己会打印,还能调用别人的打印
人家只会打印而已
这就是一个最简单的回调,同步的回调,没有任何异步的东西在里面,js引擎执行代码规规矩矩的执行函数b的调用
执行函数b里面第一行console的代码,接着执行传进来的函数a,全程只有js引擎一个线程。
那我们来看一个异步的回调:
console.log('aaa')
setTimeout(function say(){console.log('bbb')},1000)
console.log('ccc')
运行结果:
aaa
ccc
bbb
有一周js编程经验的人都知道ccc会在bbb之前被打印输出出来,那么在ccc被打印的时候bbb干嘛去了?这期间到底发生了什么?这就要引出一个js执行栈一个事件循环♻️以及一个可能有点冷门的概念--webapi.
切入正题:js是单线程的但是浏览器可不是,浏览器是多线程的,一般来说浏览器中的常驻线程就有:
- GUI渲染线程
- JavaScript引擎线程
- 定时触发器线程
- 事件触发线程
- 异步http请求线程
当我们的js引擎在执行到 setTimeout(function say(){console.log('bbb')},1000) 这行代码的时候,其实是由浏览器另外起了一个定时器触发线程,由这个线程来计时⌛️,而不是由js线程来计时的,js会继续执行下面的同步代码它不会一边执行下面的代码一边在心里默念200毫秒了。。。500毫秒了。。800毫秒了。。还有200毫秒我就该回去执行刚才那个傻逼写的定时函数里面代码了。。。。它不会这样做的,它只会一心一意的执行下面的代码。而负责给定时器计时的是刚才那个定时器线程,它会在1秒之后将会调函数放入事件队列中,是的它的工作也很简单,setTimeout指定的时间到了就把setTimeout里面的函数放入事件队列中,仅此而已。然后事件循环系统会监听js的执行栈的状态和事件队列中的状态,当它发现js执行栈中没有任务在执行了的时候,它便会将事件队列中排在第一个位置的任务放入js执行栈中,所以此时当ccc打印完毕之后它发现诶执行栈空了,而事件队列中有个任务在排队呢,我要把它放入js执行栈中,于是console.log('bbb')被放入了js执行栈中执行,打印。
所以我想说的是setTimeout这个ApI其实根本不在js引擎中,V8中没有它,而它能执行是因为浏览器中不只有V8,它还有其他线程。同样的原理,当你使用xhr来发送一个ajax请求的时候,你写好一个请求,写好请求成功的回调函数success: function() {// handle data } 然后xhr.send() 就不管了,继续写你下面的同步代码,你知道,当请求成功返回的时候,你定义的success回调函数中的代码会正常的运行,这是为什么呢?这似乎比setTimeout更加难一点,因为setTimeout 好歹有个明确的时间,而xhr发的网络请求根据当时的网络情况可能1秒后返回,可能5秒后返回也可能永远不会返回。。。但是你知道只要它返回了你写好的回调函数中的代码就会得到执行对吗?所以有没有想过,这样一个问题:是谁在替你等待着xhr的结果并在它返回后立即给你来执行?js是单线程的,它同一时间只能做一件事,所以当它执行xhr.send()方法后面的代码的时候是不会同时回头看一眼xhr返回了没的,做这个工作的是浏览器中的另一个线程--异步http请求线程,它负责等待请求的返回,待请求返回之后将你写好的回调函数放入事件队列中,然后同样的,event loop系统在js执行栈空闲的时候将回调函数放入js执行栈执行。
所以 setTimeout xhr 这些其实属于另一个叫做WEBAPI的范畴,它们在浏览器中都有专门的线程来配合实现。click事件也在其中,事件触发线程就是负责这块儿的。不要问我webapi都有哪些?因为我也不知道到底有多少个,前端用到的又有几个,不过你可以去MDN上看一眼,会亮瞎你的,因为很多很多。。。。。
歪果仁小哥哥的演讲视频地址在我的博客上有: (需科学上网)
博客地址:xiaoyu.work
网友评论