javascript为何是单线程语言,缘由在于若是是多线程,当一个线程对DOM节点作添加内容操做的时候,另外一个线程要删除这个DOM节点,这个时候,浏览器应该怎么选择,这就形成了混乱,为了解决这类问题,在一开始的时候,javascript就采用单线程模式。javascript
在后面H5出的web worker标准的时候,看似是多线程,实际上是在一个主线程来控制其余线程,并且不能操做DOM,因此本质仍是单线程java
任务能够分为两种,一种为同步,另外一种为异步(具备回调函数)。以下图:
全部的同步任务都在主线程上执行,造成一个执行栈 stack。当全部同步任务执行完毕后,它会去执行microtask queue中的异步任务(nextTick,Promise),将他们所有执行。主线程以外还有一个任务队列task queue,当有异步任务(DOM,AJAX,setTimeout,setImmediate)有结果的时候,就在任务队列里放一个事件,一旦执行栈和microtask queue任务执行完毕,系统就会读取任务队列,将取出排在最前面的事件加入执行栈执行,这种机制就是任务队列。node
主线程在任务队列中读取事件,这个过程是循环不断地,因此这种运行机制叫作Event Loop(事件循环)web
nextTick是在执行栈同步代码结束以后,下一次Event Loop(任务队列)执行以前。当全部同步任务执行完,会在queue中执行nextTick,不管nextTick有多少层回调,都会执行完毕后再去任务队列,因此会形成一直停留在当前执行栈,没法执行任务队列,请看下面代码浏览器
process.nextTick(function () { console.log('nextTick1'); process.nextTick(function (){console.log('nextTick2')}); }); setTimeout(function timeout() { console.log('setTimeout'); }, 0)
执行完毕后输出nextTick一、nextTick二、setTimeout,缘由是nextTick是在当前执行栈末尾执行,而setTimeout是在下次任务队列在执行bash
setImmediate方法是在Event Loop(任务队列)末尾,也就是下一次Event Loop时执行。
setTimeout方法是按照执行时间,放入任务队列,有时快与setImmediate有时慢。请看如下代码多线程
setImmediate(function () { console.log('setImmediate1'); setImmediate(function (){console.log('setImmediate2')}); }); setTimeout(function timeout() { console.log('setTimeout'); }, 0);
这段代码执行完多是setImmediate一、setTimeout、setImmediate2,也多是setTimeout、setImmediate一、setImmediate2,缘由是setTimeout和setImmediate1都是在下次Event Loop中触发,因此前后不肯定,可是setImmediate2确定是最后,由于他是在setImmediate1任务队列以后,也就是下下次Event Loop执行异步
Node.js也是单线程的Event Loop可是和浏览器有些区别,如图所示,socket
1.先经过Chrom V8引擎解析Javascript脚本函数
2.解析完毕后调用Node API
3.LIBUV库负责Node API的执行,将不一样任务分配给不一样的线程,造成一个Event Loop(任务队列)
4.最后Chrom V8引擎将结果返回给用户
node.js的特色是事件驱动,非阻塞单线程。当应用程序须要I/O操做的时候,线程并不会阻塞,而是把I/O操做交给底层库(LIBUV)。此时node线程会去处理其余任务,当底层库处理完I/O操做后,会将主动权交还给Node线程,因此Event Loop的用处是调度线程,例如:当底层库处理I/O操做后调度Node线程处理后续工做,因此虽然node是单线程,可是底层库处理操做依然是多线程
┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
上面处理阶段都是按照先进先出的规则执行回调函数,按顺序执行,直到队列为空或是该阶段执行的回调函数达到该阶段所容许一次执行回调函数的最大限制后,才会将操做权移交给下一阶段。
1.当setTimeout时间最小,读取文件不存在的时候
如图所示,分别是nextTick、readFile、setTimeout、setImmediate,然而如今并无1.txt和2.txt文件,输出结果是next Tick、setTimeout、readFile、setImmediate,在event loop中先判断的是timeers,最早出书next Tick由于process.nextTick的实现是基于v8 MicroTask(是在当前js call stack 中没有可执行代码才会执行的队列,低于js call stack 代码,但高于事件循环,不属于Event Loop,上面javascript的Event Loop介绍过了,因此最早输出。而后开始走Event Loop,第一阶段是timers,判断setTimeout到期,因此输出setTimeout,进入下一阶段,poll将I/O操做权交出,新线程操做,可是并无相关读取文件,因此直接返回回调函数,因此到处readFile,最后到check阶段,输出setImmediate
2.当setTimeout时间最小,读取文件存在的时候
如图所示,分别是nextTick、setTimeout、setImmediate、readFile,此次readFile在最后面,是由于文件存在,执行到poll阶段的时候,执行I/O操做,node线程开始执行check阶段,当交出的I/O操做结束后,返回给Event Loop因此再执行readFile的回调函数,因此他在最后面
3.当setTimeout时间为100毫秒,读取文件不存在的时候 如图所示,分别是nextTick、readFile、setImmediate、setTimeout,它和1不一样的地方是setTimeout排在最后了,这是由于在执行timers的时候,setTimeout没有到期,因此直接执行下一阶段,当执行完poll的时候,会去执行查看定时器有没有到期,若是没有下一次Event Loop再次查看,知道定时器到期,因此他在最后面
4.当setTimeout时间为100毫秒,读取文件存在的时候
如图所示,分别是nextTick、setImmediate、readFile、setTimeout,它和2的区别是setTimeout在最后,缘由和3同样。
1.javascript和node.js都是单线程,可是node底层是多线程操做
2.Event Loop —— 任务队列
3.当同时设置nextTick, setImmediate, setTimeout时必定是nextTick先执行,nextTick不属于Event LOop,它属于v8的micro tasks,而且会阻塞Event Loop
4.setImmediate,setTimeout属于Event Loop可是,直接阶段不一样