培育能力的事必须继续不断地去作,又必须随时改善学习方法,提升学习效率,才会成功。 —— 叶圣陶javascript
Node的首要目标是提供一种简单的,用于建立高性能服务器的开发工具。还要解决web服务器高并发的用户请求。java
咱们这里来举个例子,咱们node和java相比,在一样的请求下谁更占优一点。看图node
并发
性能,它能够快速经过主线程绑定事件。java每次都要建立一个线程,虽然java如今有个线程池
的概念,能够控制线程的复用和数量。(单线程)
,致使其下面的时间没法快速绑定,因此node不适用于大型密集型CPU运算案例
,而java却很适合。web端场景主要是用户的请求
或者读取静态资源
什么的,很适合node开发。应用场景主要有聊天服务器
,电子商务网站
等等这些高并发的应用。web
Node.js是一个基于 Chrome V8 引擎的JavaScript运行环境(runtime)
,Node不是一门语言,是让js运行在后端的运行时
,而且不包括javascript全集,由于在服务端中不包含DOM
和BOM
,Node也提供了一些新的模块例如http,fs
模块等。Node.js 使用了事件驱动、非阻塞式 I/O
的模型,使其轻量又高效而且Node.js 的包管理器 npm
,是全球最大的开源库生态系统。面试
总而言之,言而总之,它只是一个运行时,一个运行环境。数据库
(回调函数)
。非阻塞式i/o
,便可以异步读写。事件驱动
(发布订阅)。
进程
是操做系统分配资源和调度任务的基本单位,线程
是创建在进程上的一次程序运行单位,一个进程上能够有多个线程。npm
在此以前咱们先来看看浏览器的进程机制后端
自上而下,分别是:promise
从咱们的角度来看,咱们更关心的是浏览器的
渲染引擎
,让咱们往下看。浏览器
多线程
的,包含ui线程和js线程。ui线程和js线程会互斥,由于js线程的运行结果会影响ui线程,ui更新会被保存在队列,直到js线程空闲,则被取出来更新。这里我先要说一下浏览器的事件环,可能有人会说,你这篇文章明明是讲node的怎么会扯到浏览器。首先他们都是以js为底层语言的不一样运行时,有其类似之处,再者多学一点也不怕面试官多问。好了我废话很少说,开始。
队列是先进先出的
,好比下面的图,最早进队列的会先被打出去引用变量
是指向堆里的引用对象的地址,只是一串地址。这里栈表明的是执行栈,咱们js的主线程。栈是先进后出的
,先进后出就是至关于喝水的水杯,咱们倒水进去,理论上喝到的水是最后进水杯的。咱们能够看代码,follow me。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,如今你们已经知道堆,栈和队列的关系,如今咱们来看一张图。
我分析一下这张图
setTimeout、onClick
等等的一些操做,咱们会将他的执行结果放入队列,此期间主线程不阻塞event loop
在队列里面从头开始取,在执行栈中执行event loop
永远不会断Event Loop
(事件循环机制)macro-task(宏任务): setTimeout,setImmediate,MessageChannel micro-task(微任务): 原生Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver
微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。那么他们之间到底有什么区别呢
每次执行栈的同步任务执行完毕,就会去任务队列中取出完成的异步任务,队列中又分为microtasks queues和宏任务队列
等到把microtasks queues全部的microtasks
都执行完毕,注意是全部的
,他才会从宏任务队列
中取事件。等到把队列中的事件取出一个
,放入执行栈执行完成,就算一次循环结束,以后event loop
还会继续循环,他会再去microtasks queues
执行全部的任务,而后再从宏任务队列
里面取一个
,如此反复循环。
microtasks
,把全部microtasks queues
清空macrotasks queues
的完成事件,在执行栈执行microtasks
我这么说可能你们会有点懵,不慌,咱们来看一道题
setTimeout(()=>{
console.log('setTimeout1')
},0)
let p = new Promise((resolve,reject)=>{
console.log('Promise1')
resolve()
})
p.then(()=>{
console.log('Promise2')
})
复制代码
最后输出结果是Promise1,Promise2,setTimeout1
microtasks
,会在同步任务执行完后会去清空microtasks queues
,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
microtasks queues
找microtasks queues
,输出Promise1,同时会生成一个异步任务setTimeout1宏任务队列
查看此时队列是setTimeout1在setTimeout2以前,由于setTimeout1执行栈一开始的时候就开始异步执行,因此输出setTimeout1,在执行setTimeout1时会生成Promise2的一个microtasks,放入microtasks queues
中microtasks queues
,输出Promise2microtasks queues
,就又会去宏任务队列取一个,这回取的是setTimeout2node的事件环相比浏览器就不同了,咱们先来看一张图,他的工做流程
(APPLICATION)
会先进入v8引擎,v8引擎中主要是一些setTimeout
之类的方法。require('fs').read()
,node就会交给libuv
库处理,这个libuv
库是别人写的,他就是node的事件环。libuv
库是经过单线程异步的方式来处理事件,咱们能够看到work threads
是个多线程的队列,经过外面event loop
阻塞的方式来进行异步调用。work threads
队列中有执行完成的事件,就会经过EXECUTE CALLBACK
回调给EVENT QUEUE
队列,把它放入队列中。EVENT QUEUE
队列的事件,交给咱们的应用node中的event loop是在libuv里面的,libuv里面有个事件环机制,他会在启动node时,初始化事件环
event loop
执行到某个阶段时,都会执行对应的事件队列中的事件,依次执行event loop
就会执行下一个阶段event loop
切换一个执行队列时,就会去清空microtasks queues
,而后再切换到下个队列去执行,如此反复这里咱们要注意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)
复制代码
setImmediate1
,此时event loop
在check队列setImmediate1
从队列取出以后,输出setImmediate1
,而后会将setTimeout1
执行event loop
执行完check队列以后,开始往下移动,接下来执行的是timers队列setTimeout1
设置延迟为0的话,其实仍是有4ms的延迟,那么这里就会有两种状况。先说第一种,此时setTimeout1
已经执行完毕
setTimeout2,setTimeout1
setTimeout2,setTimeout1
,在取出setTimeout2
时,会将一个process.nextTick
执行(执行完了就会被放入微任务队列),再将一个setImmediate
执行(执行完了就会被放入check队列)event loop
会再去寻找下个事件队列,此时event loop
会发现微任务队列有事件process.nextTick
,就会去清空它,输出nextTick1
event loop
找到下个有事件的队列check队列,执行setImmediate
,输出setImmediate2
setTimeout1
还未执行完毕(4ms耽误了它的终身大事?)
event loop
找到timers队列,取出*timers队列**中的setTimeout2
,输出setTimeout2
,把process.nextTick
执行,再把setImmediate
执行event loop
须要去找下一个事件队列,这里你们要注意一下,这里会发生2步操做,一、setTimeout1
执行完了,放入timers队列。二、找到微任务队列清空。,因此此时会先输出nextTick1
event loop
会找到check队列,取出里面已经执行完的setImmediate2
event loop
找到timers队列,取出执行完的setTimeout1
。这种状况下event loop
比上面要多切换一次因此有两种答案
setImmediate1,setTimeout2,setTimeout1,nextTick1,setImmediate2
setImmediate1,setTimeout2,nextTick1,setImmediate2,setTimeout1
这里的图只参考了第一种状况,另外一种状况也相似
有些人可能会搞乱他们之间的关系,同步、异步
是被调用者的状态,阻塞、非阻塞
是调用者的状态、消息
接下来咱们来看看他们的组合会是怎么样的
组合 | 意义 |
---|---|
同步阻塞 | 这就至关于我去饭店吃饭,我须要在厨房等待菜烧好了,才能吃。我是调用者我须要等待上菜因而被阻塞,菜是被调用者作好直接给我是同步 |
异步阻塞 | 我去饭店吃饭,我须要等待菜烧好了才能吃,可是厨师有事,但愿以后处理完事能作好以后通知我去拿,我做为调用者等待就是阻塞的,而菜做为被调用者是作完以后通知个人,因此是异步的,这种方式通常没用。 |
同步非阻塞 | 我去饭店吃饭,先叫了碗热菜,在厨房等厨师作菜,但我很饿,就开始吃厨房冷菜,我是调用者我没等热菜好就开始吃冷菜,是非阻塞的,菜做为被调用者作好直接给我是同步的,这种方式通常也没人用 |
异步非阻塞 | 我去饭店吃饭。叫了碗热菜,厨师在作菜,但我很饿,先吃冷菜,厨师作好了通知我去拿,我是调用者我不会等热菜烧好了再吃冷菜,是非阻塞的,菜做为被调用者通知我拿是异步的 |
但愿你们看了本篇文章都有收获,这样出去面试的时候就不会这样