最近在系统的学习Node.js,在学习过程中遇到了Event-loop这个东东,下面把我自己的一些心得进行总结,分享给大家。
Javascript真的是单线程运行吗?
了解Javascript的同学应该知道Javascript是单线程运行的,比如在代码里你打个alert,系统给你弹个框,后面的代码都给阻塞了,事实上也确实如此。因为如果系统给你new Thread的功能,二条线程同时更新一个dom,那不打架了么~~~。
可是,单线程的运行模式是很不友好的,在很多场景下,都需要异步来提高用户体验,于是老美那帮哥们就发明了Promise、Callback,setTimeoutt,setInterval,还有我们最常用的Ajax(XMLHttpRequest类) 等异步编程接口,让我们能开发出,更好用户体验的产品。
那么问题又来了,如果在一个项目里实用了这些异步编程接口,他们是如何工作的,而且如何有效的控制他们的运行先后顺序,这个我们必须要搞清楚,下面就来简单分享一下。
在开始之前先谈谈【进程与线程】
为什么要说进程与线程,我用最简单通俗的话来讲,大家开发用的浏览器Chrome,是一个多进程的浏览器;大家在开发时写的setTimeout,setInterval, XMLHttpRequest类,是在一个进程里开辟的新的线程。
好混乱。别急。画图。
从上图我们可以看到,一个Chrome切页,就是一个网页,也就是一个进程。同时在一个网页里,我们会写很多代码,里面有很多API调用,如Promise、Callback,setTimeoutt,setInterval,Ajax(XMLHttpRequest类),它们会开启自己的一个线程去运行,网页(进程)里包含多个线程。
- 浏览器事件触发线程(用来控制事件循环,存放setTimeout、浏览器事件、ajax的回调函数)
- 定时触发器线程(setTimeout定时器所在线程)
- 异步HTTP请求线程(ajax请求线程)
浏览器的事件循环(Event-loop)
- 所有同步任务都在栈内存图中的(Stack框)的主线程上执行,形成一个执行栈,执行栈也表示作用域栈。
- 主线程之外,还存在一个任务队列(callback quere框)。只要异步任务(一个个线程)有了运行结果,就在任务队列之中放置一个事件(回调函数)。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件(回调函数)放到执行栈中依次执行
- 主线程从任务队列中读取事件,这个过程是循环不断的。
Node.js的事件循环(Event-loop)
- 我们写的node.js代码会交给v8引擎进行处理。
- 代码中可能会调用node.js Api(如:setTimeout, setInterval, setImmediate, I/O,process.nextTick,Promise等),node会交给libuv库处理。
- libuv通过阻塞操作和多线程实现了异步io,也就是说libuv内部是完全异步的,这就是node.js的最大特点事件驱动的核心。
- 通过事件驱动的方式(Callback函数),将结果放到事件队列中,最终交给我们的应用。
宏任务和微任务
任务队列可分为宏任务和微任务
macro-task(宏任务): script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task(微任务): process.nextTick, 原生Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver