最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • node.js中的核心概念

    正文概述 掘金(蒲月阿七)   2021-02-23   516

    阅读之前你需具备的知识

    • 进程和线程是什么,它们区别又是什么?
    • 单线程和多线程的区别?

    node.js中的核心概念

    Node.js特点

    • 单线程

      同步进行,只有前面的代码执行完了才会往下面执行。但是node.js程序在宏观上看是并行的,这是由于它具有非阻塞I/O和事件驱动的特点。

      好处:减少了内存开销,不再有进程创建,销毁的开销;比较简单。

      坏处:一个用户造成了线程的崩溃,整个服务就都崩溃了。

    • 非阻塞I/O

      I/O会阻塞代码的执行,极大地降低了程序的执行效率。

      而非阻塞模式下,一个线程永远在执行计算操作,这个线程的CPU核心利用率永远是100%。

    • 事件驱动

      在node中,在一个时刻只能执行一个事件回调函数,但是在执行一个回调函数的中途,可以转而处理其他事件(I/O事件,网络请求...),这些事件需要消耗比较多的时间,把这些异步操作都先添加到事件队列的后面,然后返回继续执行原事件的回调函数,这称为事件循环机制。

      底层C++代码中,近半数都用于事件队列、回调函数队列的构建。

    node.js中的核心概念

    适用场景

    从node.js的特点中我们可以发现,它最擅长的就是任务调度,如果业务中过度占用了CPU进行计算,实际上也相当于这个计算阻塞了这个单进程,这就不适合用Node.js开发。

    应用程序需要处理大量并发的I/O,而在向客户端发出响应之前,应用程序内部并不需要进行非常复杂的处理的时候,就比较适合用Node.js开发。

    Node.js也适合与Web socket配合,开发长连接的实时应用程序

    事件循环

    执行栈和事件队列

    • 执行栈

      当我们调用一个方法的时候,js会生成一个与这个方法相对应的执行环境。而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为执行栈。

    • 事件队列

      当我们发起异步请求后,主线程并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。等异步任务返回结果后,该异步任务按照执行顺序,加入到与执行栈不同的另一个队列,这个队列被称为事件队列。

    由此我们知道,在Node环境中javascript运行的线程是单线程的,事件循环的线程也是单线程的,但这两个不是一个线程。

    JavaScript 事件循环机制分为浏览器和 Node 事件循环机制,浏览器 Event Loop 是 HTML 中定义的规范,Node Event Loop 是由 libuv 库实现。我们先来了解一下浏览器的事件循环~

    浏览器事件循环

    浏览器中的事件循环,主要就在理解宏任务和微任务这两种异步任务。

    • 宏任务(macrotask)

    setTimeOut 、 setInterval 、 setImmediate 、 I/O 、 各种callback、 UI渲染 、messageChannel等。

    优先级:主代码块 > setImmediate > postMessage > setTimeOut/setInterval

    • 微任务(microtask)

    process.nextTick 、Promise 、MutationObserver 、async(实质上也是promise)

    优先级:process.nextTick > Promise > MutationOberser

    执行分区:

    我们常常吧EventLoop中分为 内存、执行栈、WebApi、异步回调队列(包括微任务队列和宏任务队列)。 node.js中的核心概念 第一次事件循环中,JavaScript 引擎会把整个 script 代码当成一个宏任务执行,执行完成之后,再检测本次循环中是否寻在微任务,存在的话就依次从微任务的任务队列中读取执行完所有的微任务,再读取宏任务的任务队列中的任务执行,再执行所有的微任务,如此循环。JS 的执行顺序就是每次事件循环中的宏任务-微任务。

    Node.js事件循环

    Node.js采用V8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv。libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,事件循环机制也是它里面的实现。

    node.js中的核心概念

    根据上图,我们可以了解到Node.js的运行机制分为一下几个步骤:

    • 我们写的js代码会交给v8引擎进行处理。
    • 解析后的代码会调用NodeApi,再交由libuv库去执行。
    • libuv会将不同的任务分配给不同的线程,形成一个事件循环(Event Loop)。
    • 任务处理完成后会以异步的方式将执行结果返回给V8引擎、再由v8返回给我们。

    Node.js事件循环原理

    node.js中的核心概念

    1.timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调

    2.I/O callbacks 阶段:执行一些系统调用错误,比如网络通信的错误回调

    3.idle, prepare 阶段:仅node内部使用

    4.poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里

    5.check 阶段:执行 setImmediate() 的回调

    6.close callbacks 阶段:执行 socket 的 close 事件回调

    我们重点看timers、poll、check这3个阶段,因为日常开发中的绝大部分异步任务都是在这3个阶段处理的。

    • timers 阶段

    timers 是事件循环的第一个阶段,Node 会去检查有无已过期的timer,如果有则把它的回调压入timer的任务队列中等待执行,事实上,Node 并不能保证timer在预设时间到了就会立即执行,因为Node对timer的过期检查不一定靠谱,它会受机器上其它运行程序影响,或者那个时间点主线程不空闲。比如下面的代码,setTimeout() 和 setImmediate() 的执行顺序是不确定的。

    setTimeout(() => {
      console.log('timeout')
    }, 0)
    
    setImmediate(() => {
      console.log('immediate')
    })
    

    但是把它们放到一个I/O回调里面,就一定是 setImmediate() 先执行,因为poll阶段后面就是check阶段。

    • poll 阶段

      poll 阶段主要有2个功能:

      1.处理 poll 队列的事件。

      2.当有已超时的 timer,执行它的回调函数。

    event loop将同步执行poll队列里的回调,直到队列为空或执行的回调达到系统上限(上限具体多少未详),接下来event loop会去检查有无预设的setImmediate(),分两种情况:

    1.若有预设的setImmediate(), event loop将结束poll阶段进入check阶段,并执行check阶段的任务队列。

    2.若没有预设的setImmediate(),event loop将阻塞在该阶段等待。

    我们发现,没有setImmediate()会导致event loop阻塞在poll阶段,那这样之前设置的timer岂不是执行不了了?

    所以,在poll阶段执行的时候,会传入一个timeout超时时间,该超时时间就是poll阶段的最大阻塞时间。timeout时间未到的时候,如果有事件返回,就执行该事件注册的回调函数。timeout超时时间到了,则退出poll阶段,执行下一个阶段。

    • check 阶段

    setImmediate()的回调会被加入check队列中,check阶段的执行顺序在poll阶段之后。

    总结

    • node 的初始化

      • 初始化 node 环境。
      • 执行输入代码。
      • 执行 process.nextTick 回调。
      • 执行 microtasks。
    • 进入 event-loop

      • 进入 timers 阶段

        • 检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerId 升序执行。
        • 检查是否有 process.nextTick 任务,如果有,全部执行。
        • 检查是否有microtask,如果有,全部执行。
        • 退出该阶段。
      • 进入IO callbacks阶段。

        • 检查是否有 pending 的 I/O 回调。如果有,执行回调。如果没有,退出该阶段。
        • 检查是否有 process.nextTick 任务,如果有,全部执行。
        • 检查是否有microtask,如果有,全部执行。
        • 退出该阶段。
    • 进入 idle,prepare 阶段:

      • 这两个阶段与我们编程关系不大,暂且按下不表。
    • 进入 poll 阶段

      • 首先检查是否存在尚未完成的回调,如果存在,那么分两种情况。

        • 第一种情况:

          • 如果有可用回调(可用回调包含到期的定时器还有一些IO事件等),执行所有可用回调。
          • 检查是否有 process.nextTick 回调,如果有,全部执行。
          • 检查是否有 microtaks,如果有,全部执行。
          • 退出该阶段。
        • 第二种情况:

          • 如果没有可用回调。
          • 检查是否有 immediate 回调,如果有,退出 poll 阶段。如果没有,阻塞在此阶段,等待新的事件通知。
      • 如果不存在尚未完成的回调,退出poll阶段。

    • 进入 check 阶段。

      • 如果有immediate回调,则执行所有immediate回调。
      • 检查是否有 process.nextTick 回调,如果有,全部执行。
      • 检查是否有 microtaks,如果有,全部执行。
      • 退出 check 阶段
    • 进入 closing 阶段。

      • 如果有immediate回调,则执行所有immediate回调。
      • 检查是否有 process.nextTick 回调,如果有,全部执行。
      • 检查是否有 microtaks,如果有,全部执行。
      • 退出 closing 阶段
    • 检查是否有活跃的 handles(定时器、IO等事件句柄)。

      • 如果有,继续下一轮循环。
      • 如果没有,结束事件循环,退出程序。

    在事件循环的每一个子阶段退出之前都会按顺序执行如下过程:

    • 检查是否有 process.nextTick 回调,如果有,全部执行。
    • 检查是否有 microtaks,如果有,全部执行。
    • 退出当前阶段。

    ?

    // 在node环境下,请输出以下代码的执行结果
    console.log('1');
    
    process.nextTick(function() {
        console.log('2');
    });
    
    setTimeout(() => {
      console.log('3');
    }, 0);
    
    async function async1() {
        console.log('4');
        await async2();
        console.log('5');
    }
    
    async function async2() {
    	console.log('6');
    }
    
    async1();
    
    new Promise(function(resolve) {
        console.log('7')
        resolve();
    }).then(function() {
        console.log('8')
    });
    
    console.log('9');
    
    • 按照js由上到下的执行顺序,先遇到同步任务console输出1。

    • 再往下执行遇到process.nextTick,回调加入微任务队列。

    • 接着遇到setTimeout,setTimeout是宏任务,会先放到宏任务队列中。

    • promise中的异步体现在then和catch中,所以写在promise中的代码是被当做同步任务立即执行的。而在async/await中,在出现await出现之前,其中的代码也是立即执行的。async修饰的函数,默认返回 new Promise对象的resolve内容(若被async修饰的函数无返回值,则最终无返回值)。async方法内部,当程序执行到await方法时,会阻塞await方法后面的程序,进入await方法内部并执行到return前,然后跳出该async方法,执行与该async方法并列的同步任务。

      所以,接下来遇到async/await时,执行async1以及await后面的函数,先立刻输出4,6,然后先跳出async1(等到与async1方法并列的同步代码执行完后,跳回async1内部),此时await返回值为非Promise,继续执行async函数后面的代码。

      紧接着遇到promise,先立刻输出7,然后将then回调加到微任务队列。

    • 再往下遇到同步任务输出9。

    • 此时执行栈中的任务已经清空。由于微任务的优先级高于宏任务,所以会先执行微任务队列中的回调。因为微任务队列中nextTick任务在Promise.next前,所以先执行异步任务process.nextTick,接着是await返回结果后跳回async1内部继续执行async函数后面的代码,最后是then中的回调,所以输出结果依次为2,5,8。

    • 微任务队列清空,该轮循环结束,但是发现事件队列中还有任务,开始进入下一轮循环。

    • 新的一轮循环由timers阶段开始,发现有对应的setTimeout回调,输出3。

    最终输出结果即为:1、4、6、7、9、2、5、8、3

    Node.js 与浏览器的 Event Loop 差异

    浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行。 node.js中的核心概念 Node.js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。 node.js中的核心概念

    参考

    剖析nodejs的事件循环: juejin.cn/post/684490…

    深入浅出NodeJS事件循环: juejin.cn/post/691676…


    起源地下载网 » node.js中的核心概念

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元