当今社会,技术的作用已经越来越明显,未来社会中,谁掌握了先进的技术,谁就可以呼风唤雨。
前情回顾
上篇文章主要分享了异步I/O的阻塞,非阻塞问题
,因为它们会对系统的性能有所影响。今天主要聊一下Node异步I/O中的事件循环
和JS中的事件循环
。
JS的并发模型和事件循环
JavaScript有个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务,这个模型和其他语言截然不同。
想要理解这个模型,需要先理解这几个概念堆(heap)
,栈(stack)
,队列
。在JS中,堆内存的作用在于提供引用类型的存储空间
。栈内存的作用有两个:1,存放基本数据类型
。2,提供代码的运行环境
。提供运行环境其实是函数的调用形成了一个多帧组成的栈。如图(这个图有点秀,不要在意这些细节):
事件循环模型
看下面的代码:
代码语言:javascript复制function foo(b){
let a = 10;
return a b;
}
function bar(x){
return foo(x*y)
}
console.log(bar(7))
当调用bar()
的时候,第一帧被压入栈中,包含了bar的参数和局部变量。当bar调用foo时,第二帧创建并压入栈中,放在第一帧上面,帧中包含foo的参数和局部变量。当foo执行完成后,第二帧就被弹出。当bar执行完成后,第一帧也被弹出,栈就清空了。
需要注意的是,一个JavaScript运行时包含了一个带处理消息的消息队列
。每个消息都关联一个用于处理这个消息的回调函数。这个可以理解为上图底部的message
。
在js事件循环
的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。
函数的处理会一直进行到执行栈再次为空为止,然后事件循环队列会处理队列中的下一个消息。
事件循环的实现代码大致如下:
代码语言:javascript复制while(queue.waitForMessage()){
queue.processNextMessage()
}
queue.processNextMessage()
会同步等待消息的到达。每个消息完整的执行完成后,其他消息才会被执行。
那么,消息是什么
?这里可以理解为事件的回调函数
。在浏览器中,每个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加到消息队列
。如果没有事件监听器,这个事件就会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。
再说一下setTimeout
,setTimeout函数接受两个参数:待加入队列的消息(即回调函数)和一个时间值。这个时间值代表的是这个消息(回调函数)被实际加到消息队列的最小延迟时间
。如果队列中没有其他消息且执行栈为空,在这段时间过去后,消息会马上处理。但是如果有其他消息,setTimeout
必须等到其他消息处理完成。所以,setTimeout的第二个参数仅仅代码最少延迟时间,并非确切的时间。
const s = new Date().getSeconds();
setTimeout(function(){
console.log('run after ' (new Date().getSeconds()-s) 'seconds');
},500)
while(true){
if(new Date().getSeconds()-s>=2){
console.log('looped for 2 seconds')
break;
}
}
// looped for 2 seconds
// run after 2 seconds
可以看到执行代码后先输出了 looped for 2 seconds
,然后输出run after 2 seconds
。因为代码执行到setTimeout,发现他是一个消息,将它加入到了队列中,队列清空后,才又开始处理这个消息。
Node异步IO中的事件循环
Node自身的执行模型也叫事件循环
。在进程启动时,Node会创建一个类似while(true)的循环,每执行一次循环体的过程被称为Tick
。Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。有关联的回调函数就执行它们。然后开始下个循环,如果没有事件,就退出进程。
那么在每次Tick的过程中,如何判断是否还有事件需要处理呢?这里就引入了观察者,个人的理解就是观察者模式
,并且有可能Vue的实现也是借鉴了Node的这个理念。
非I/O的异步API
在面试的时候有时会问到异步的问题,最多的是promise
相关的问题。涉及到的有setTimeout
,process.nextTick
,setImmediate
相关的宏任务
与微任务
的问题。这几个实际上是Node里的几个非I/O的异步API
。
setTimeout()
和setInterval()
与浏览器中的API是一致的,分别用于单次和多次定时执行任务。它们的实现原理与异步I/O比较类似,只是不需要I/O线程池的参与。调用setTimeout()或者setInterval()创建的定时器会被插入到定时器观察者内部的一个红黑树中。每次Tick执行时,会从该红黑树中迭代取出定时器对象,检查是否超过定时时间,如果超过,就形成一个事件,它的回调函数将立即执行。
总结
js中的事件循环
机制Node中的
事件循环
javascript基础知识总结