让前端飞程序员

node基础面试事件环?微任务、宏任务?一篇带你飞

2018-06-29  本文已影响13人  Shinemax
image

培育能力的事必须继续不断地去做,又必须随时改善学习方法,提高学习效率,才会成功。 —— 叶圣陶

一、我们为什么要使用node,它的好处是什么?

Node的首要目标是提供一种简单的,用于创建高性能服务器的开发工具。还要解决web服务器高并发的用户请求。

解决高并发?

我们这里来举个例子,我们node和java相比,在同样的请求下谁更占优一点。看图

image

node在web端场景?

web端场景主要是用户的请求或者读取静态资源什么的,很适合node开发。应用场景主要有聊天服务器电子商务网站等等这些高并发的应用。

二、node是什么?

Node.js是一个基于 Chrome V8 引擎的JavaScript运行环境(runtime),Node不是一门语言,是让js运行在后端的运行时,并且不包括javascript全集,因为在服务端中不包含DOMBOM,Node也提供了一些新的模块例如http,fs模块等。Node.js 使用了事件驱动、非阻塞式 I/O的模型,使其轻量又高效并且Node.js 的包管理器 npm,是全球最大的开源库生态系统。

总而言之,言而总之,它只是一个运行时,一个运行环境。

node特性

node的进程与线程

进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。

在此之前我们先来看看浏览器的进程机制

image

自上而下,分别是:

从我们的角度来看,我们更关心的是浏览器的渲染引擎,让我们往下看。

渲染引擎

三、浏览器中的Event Loop

这里我先要说一下浏览器的事件环,可能有人会说,你这篇文章明明是讲node的怎么会扯到浏览器。首先他们都是以js为底层语言的不同运行时,有其相似之处,再者多学一点也不怕面试官多问。好了我废话不多说,开始。

首先我们需要知道堆,栈和队列的关系和意义。

隔山打牛!
function a(){
  console.log('a')
  function b(){
    console.log('b')    
    function c(){
      console.log('c')
    }
    c()
  }
  b()
}
a()

//这段代码是输出a,b,c,执行栈中的顺序的c,b,a,如果是遵循先进先出,就是输出c,b,a。所以栈先进后出这个特性大家要牢记。

OK,现在大家已经知道堆,栈和队列的关系,现在我们来看一张图。

image

我分析一下这张图

微任务、宏任务?

macro-task(宏任务): setTimeout,setImmediate,MessageChannel
micro-task(微任务): 原生Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver

微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。那么他们之间到底有什么区别呢

image

每次执行栈的同步任务执行完毕,就会去任务队列中取出完成的异步任务,队列中又分为microtasks queues和宏任务队列等到把microtasks queues所有的microtasks都执行完毕,注意是所有的,他才会从宏任务队列中取事件。等到把队列中的事件取出一个,放入执行栈执行完成,就算一次循环结束,之后event loop还会继续循环,他会再去microtasks queues执行所有的任务,然后再从宏任务队列里面取一个,如此反复循环。

我这么说可能大家会有点懵,不慌,我们来看一道题

setTimeout(()=>{
  console.log('setTimeout1')
},0)
let p = new Promise((resolve,reject)=>{
  console.log('Promise1')
  resolve()
})
p.then(()=>{
  console.log('Promise2')    
})

最后输出结果是Promise1,Promise2,setTimeout1

Promise.resolve().then(()=>{
  console.log('Promise1')  
  setTimeout(()=>{
    console.log('setTimeout2')
  },0)
})

setTimeout(()=>{
  console.log('setTimeout1')
  Promise.resolve().then(()=>{
    console.log('Promise2')    
  })
},0)

这回是嵌套,大家可以看看,最后输出结果是Promise1,setTimeout1,Promise2,setTimeout2

image

四、node中的事件环

node的事件环相比浏览器就不一样了,我们先来看一张图,他的工作流程

[图片上传失败...(image-d0ae0-1530284206162)]

node中的event loop

node中的event loop是在libuv里面的,libuv里面有个事件环机制,他会在启动node时,初始化事件环

image

这里我们要注意setImmediate是属于check队列的,还有poll队列主要是异步的I/O操作,比如node中的fs.readFile()

我们来具体看一下他的用法吧

setImmediate(()=>{
  console.log('setImmediate1')
  setTimeout(()=>{
    console.log('setTimeout1')    
  },0)
})
setTimeout(()=>{
  console.log('setTimeout2') 
  process.nextTick(()=>{console.log('nextTick1')})
  setImmediate(()=>{
    console.log('setImmediate2')
  })   
},0)

所以有两种答案

  1. setImmediate1,setTimeout2,setTimeout1,nextTick1,setImmediate2
  2. setImmediate1,setTimeout2,nextTick1,setImmediate2,setTimeout1
image

这里的图只参考了第一种情况,另一种情况也类似

五、node的同步、异步,阻塞、非阻塞

有些人可能会搞乱他们之间的关系,同步、异步是被调用者的状态,阻塞、非阻塞是调用者的状态、消息

接下来我们来看看他们的组合会是怎么样的

组合 意义
同步阻塞 这就相当于我去饭店吃饭,我需要在厨房等待菜烧好了,才能吃。我是调用者我需要等待上菜于是被阻塞,菜是被调用者做好直接给我是同步
异步阻塞 我去饭店吃饭,我需要等待菜烧好了才能吃,但是厨师有事,希望之后处理完事能做好之后通知我去拿,我作为调用者等待就是阻塞的,而菜作为被调用者是做完之后通知我的,所以是异步的,这种方式一般没用。
同步非阻塞 我去饭店吃饭,先叫了碗热菜,在厨房等厨师做菜,但我很饿,就开始吃厨房冷菜,我是调用者我没等热菜好就开始吃冷菜,是非阻塞的,菜作为被调用者做好直接给我是同步的,这种方式一般也没人用
异步非阻塞 我去饭店吃饭。叫了碗热菜,厨师在做菜,但我很饿,先吃冷菜,厨师做好了通知我去拿,我是调用者我不会等热菜烧好了再吃冷菜,是非阻塞的,菜作为被调用者通知我拿是异步的

结尾

希望大家看了本篇文章都有收获,这样出去面试的时候就不会这样

image
而是这样。好了,最后希望大家世界杯都能够逢赌必赢,自己喜欢的球队也能够杀进决赛
image
上一篇 下一篇

猜你喜欢

热点阅读