潜心修炼一段时间的我又回来了
每天不能不写业务,但也不能只写业务。所以选择了一个自己还在学校期间没有学明白的内容进行了巩固,同步异步问题。学习一个知识之前必先给自己一个问题三连。为什么要有这个? 这个怎么用? 这个怎么回事?
为什么要有同步异步?
首先JS是一个单线程的语言。单线程的含义类似于从头走到尾,谁也别管谁,前面堵车我就停(官方:单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。),没办法开多个线程。
疑问来了?为什么JS不设计成多线程的可以开多个线程同时操作。JS是可以去操作DOM的。假设JS设计成一个多线程语言。你的主线程在给DOM的innerHtml做一个赋值操作,你的另一个线程把这个DOM结构删除了。。。。这肯定不可以。(多线程可以互不干预的操作一段内存空间)。所以干脆设计成一个单线程,哪怕后期HTML5出现的web worker也是不允许操作DOM结构的,可以完成一些分布式的计算。对于dom结构我们必须顺序操纵,坚决不允许出现对同一个dom同时进行操作。
但是浏览器加载一些需要网络请求的比如图片资源、ajax。或者轮训的内容。由于是单线程,需要等待这些内容访问完才可以执行下面的代码。那么你发个ajax请求或者请求个图片资源,那么这段时间就什么也做不了,这种效果对程序是一种阻塞,在等待时间明明可以做些别的事情却选择了无意义的等待。(同步阻塞)这个时候异步就出现了,在涉及需要等待的操作,我们选择让程序继续运行,在等待时间结束的时候,通知一下我们的程序内容执行完毕,你可以操作这些资源了,这段等待时间并不影响你程序的继续执行,只是在未来的某个时间段(不确定),有一个操作一定会执行,这就是异步。(异步非阻塞),这就是为什么要有同步异步。
基础的内容我们讲解完毕,下面开始真正的干货
(第一次了解这个的,请先补习一下定时器与promise的知识)
xxxxxxxxx(一堆不确定的代码)
setTimeout(()=>{console.log(111)},500);
请问打印出111会在什么时候? 答案是500ms的时候打印
错!答案是500ms或者500ms以后的某个时间段。
首先遇见定时器后,会将定时器内的函数进行注册,也就是放入Event Table 。然后在500ms后将Event Table内注册的函数放入 Event queue。若主线程(我也就一个线程)中的call stack(调用堆栈,也就是线程中函数的一个调用栈)为空就将Event queue按顺序的放入call stack中进行执行。如果call stack并不为空, Event queue内的事件并不会进入call stack中,也就不会执行。
怎么突然来了这么多乱七八糟,画风和刚才不一样呀。难度上来了。
首先对于异步事件,我们在执行到这行代码的时候会进行一个注册,将你要在未来某个时间段要执行的函数注册一下,放在Event table中。这个Event table中可以有很多事件,比如你一次发了好多ajax请求,那么他们就全部注册了。在未来的时间到了,就会把注册的事件放入Event queue(任务队列)这个任务队列就是马上要执行的内容。
任务队列什么时候可以执行?在主线程的call stack为空的时候,会把任务队列的第一个事件放入call stack中执行,这里面涉及一个queue(队列)的特点就是先进先出。在注册后先放入Event queue的事件就会更早的离开Event queue进入主线程执行。这个时候是不是觉得自己明白点了? 唉别高兴的太早了。
来吧 聊一聊宏任务与微任务吧
setTimeout(()=>{console.log(111)},0)
let promise = new Promise((resolve,reject)=>{
console.log(222);
resolve(3333)
});
let promise2 = new Promise((resolve,reject)=>{
console.log(555);
resolve(6666)
});
setTimeout(()=>{
console.log(4444);
},0)
promise.then(res=>{
console.log(res);
});
promise2.then(res=>{
console.log(res);
});
你说这会打印出个什么? 按理论先进先出,222 555 111 4444 333 666 你看对不对。
事实证明:222 555 333 666 111 4444
这是为什么? 首先注册的事件也有不同形态,宏任务与微任务。
常见的宏任务:setTimeout、setInterval(定时器类)
常见的微任务:promise process.nextTick(一个node环境的方法)。
这两个任务的执行规则是什么?首先在call stack中的内容执行完毕清空后,会在Event queue检查一下哪些是宏任务哪些是微任务,然后执行所有的微任务,然后执行一个宏任务,之后再次执行所有的微任务。也就是说在主线程任务执行完毕后会把任务队列中的微任务全部执行,然后再执行一个宏任务,这个宏任务执行完再次检查队列内部的微任务,有就全部执行没有就再执行一个宏任务。
为什么我一行独立的异步的代码也写不出来
在这个概念理解清楚之后,我大约明白一些了,可是我就是一行真正的脱离了定时器、事件、ajax的异步的代码也写不出来,我一直在想如何实现消息通知异步的消息通知?观察者模式吗?那也没办法模拟出来,10分钟之后我要做一个任务。想了很久。
在有一天需求评审中,我们要做一个定时提醒的需求,这个时候后台的任务分配中选择了几天来制作定时任务。这个时候我理解了。java的定时提醒是要开一个新的线程去不断轮巡时间,可以设置的间隔但是间隔越小,越消耗机器的性能。后来查阅资料了解,JS是单线程,但是浏览器不是,只是执行JS代码的引擎是个单线程的所以JS的代码没办法开启多个线程,但是浏览器还有定时器线程、事件触发线程、异步http请求线程、GUI线程。
所以在执行定时器、事件、ajax这些异步事件的时候是另外三个线程在执行代码,并不是JS引擎在做事情,在这些线程达到某一特定事件把任务放入JS引擎的线程中,同时GUI线程(渲染界面HTMl的线程)与JS线程是互斥的,在JS引擎执行时GUI线程会被冻结、挂起。
最后最后 JS是单线程但是浏览器是多线程。你的异步任务是浏览器开启对应的线程来执行的,最后放入JS引擎中进行执行。
未完待续
今天基本理解了JS的异步任务是什么意思。下一节(可能是下周或者大下周)
我就要针对于Promise 与async/await 以及回调地狱来进行梳理与学习了。下次还是这个主题我们不见不散。
网友评论