node基础与event loop

741 阅读4分钟

1.node是什么?

node是一个基于Chrome V8引擎的javascript运行环境,node不是一门语言,而是让javascript运行在后端的运行时环境,所以node中没有DOM和BOM,node也提供了一些内置模块,例如:http,fs等。nodejs使用了事件驱动、非阻塞式I/O的模型,使其轻量又高效。

2.进程和线程

一个进程中可以包含多个线程。例如浏览器的渲染引擎,其实就是浏览器内核,它内部就是多线程的,内部包含两个非常重要的线程UI线程和JS线程,特别要注意的是UI线程和JS线程是互斥的,因为JS运行结果会影响到UI线程的结果,那么UI更新会被保存在队列中,等到JS线程空闲时,立即被执行。

3.浏览器中的Event Loop(事件循环)

我们从一道面试题,说明一下浏览器的event loop:
console.log(1);
setTimeout(function(){
    console.log(2);
});
console.log(3);
我们大家都知道,运行结果是1,3,2。但是,浏览器到底是怎么执行的呐?
因为console.log(1)和console.log(3)是同步任务,所以将它们放到运行栈中去执行,js引擎在遇到setTimeout时,会把它当作异步任务,不会把它放到运行栈中去执行,浏览器的timer模块会把setTimeout先拿走,时间到了,timer模块会把它放到异步队列中去,js引擎发现运行栈中没有要执行的东西了,就会读取异步队列中的内容,放到运行栈中去执行,这时setTimeout中的函数体就变成运行栈中的同步任务,执行完后,再去监听异步队列中有没有,如果有继续执行,如此循环,这个循环的过程就是event loop。

4.node系统

我们写的js代码会交给V8引擎进行处理;代码中可能调用node API,node会交给libuv库处理;libuv通过阻塞I/O和多线程实现了异步I/O;通过事件驱动的方式将结果放到事件队列中,最终交给我们的应用。

5.宏任务和微任务

任务分为宏任务和微任务。
macro-task(宏任务):setTimeout,setInterval,setImmediate,I/O
micro-task(微任务):原生Promise,process.nextTick,MessageChannel(vue中nextTick实现原理)
在浏览器中,先执行当前栈,执行完后,再走微任务,微任务执行完后,再去取事件队列中的内容。
console.log(1);
console.log(2);
setTimeout(function(){
    console.log('setTimeout1');
    Promise.resolve().then(function(){
        console.log('promise');
    });
});
setTimeout(function(){
    console.log('setTimeout2');
});
浏览器中的运行结果是:
1
2
setTimeout1
promise
setTimeout2
node中的运行结果:
1
2
setTimeout1
setTimeout2
promise
我们发现,同样的代码,在浏览器中运行和在node中运行的结果不同,这是为什么呐?接下来,我就要说一下node中的event loop。
在libuv内部,有这样一个事件环机制,在node启动时,会初始化事件环。

这里每一个阶段都对应一个事件队列,当event loop执行到某个阶段时,会将当前阶段对应的队列依次执行完。当队列执行完毕或执行的数量超过上限时,会转入下一个阶段。
那就说一下上题,在node中的执行的详细过程吧。
首先,将console.log(1)和console.log(2)放到运行栈中去执行,执行完后,会看一下是否有微任务,如果有,会执行微任务(微任务执行,都会在阶段转换时被执行),那么现在没有,接着往下执行,遇到两个setTimeout,会将它们放到第一阶段timers(计数器)中,接着往下一个阶段执行,当setTimeout到时间了,会将到时间的setTimeout都执行完毕,再去执行微任务,当执行第一个setTimeout时,遇到了一个Promise微任务,会将它放到微任务中,然后,执行第二个setTimeout,都执行完后,再去执行微任务。
我们再看几个题,能够更好的理解node中的event loop。
process.nextTick(function(){
    console.log("nextTick");
});
setImmediate(function(){
    console.log("immediate");
});
node中的运行结果,毫无疑问是
nextTick
immediate
let fs = require('fs');
fs.readFile('./1.log' , function(){
    console.log('fs');
    setTimeout(function(){
        console.log('timeout');
    });
    setImmediate(function(){
        console.log('immediate');
    });
});
node中的运行结果是:
fs
immediate
timeout
因为I/O操作完了,会走check阶段,所以setImmediate会早于setTimeout。
再看一下,最后一道题,这道题你答对了,说明你真的明白了node中的event loop了。
setImmediate(function(){
    console.log(1);
    process.nextTick(function(){
        console.log(4);
    });
});
process.nextTick(function(){
    console.log(2);
    setImmediate(function(){
        console.log(3);
    });
});
node中的运行结果:
2
1
3
4
你答对了吗?