Node 事件循环和浏览器端的差异
在浏览器环境下,microtask(微任务)的任务队列,在每个 macrotask(宏任务)执行完之后执行。
在 node 中,microtask(微任务)会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行清空 microtask 队列的任务。
在 node 中,setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作,属于宏任务。new Promise().then(回调) 属于微任务。process.nextTick 优先级大于微任务,当每个阶段完成后,就会清空 process.nextTick。
js
// 在浏览器环境之中
// 1 2 3 4
setTimeout(()=>{
console.log('1')
Promise.resolve().then(function() {
console.log('2')
})
}, 0)
setTimeout(()=>{
console.log('3')
Promise.resolve().then(function() {
console.log('4')
})
}, 0)
js
// 在 node 环境之中,两个 timerout,添加到 timer 队列中,清空 timer 队列,然后清空微任务队列
// 1 3 2 4
setTimeout(()=>{
console.log('1')
Promise.resolve().then(function() {
console.log('2')
})
}, 0)
setTimeout(()=>{
console.log('3')
Promise.resolve().then(function() {
console.log('4')
})
}, 0)
进程和线程的区别
进程
- 进程是程序的实体
- 每一个进程相互独立
- 线程是进程的基本执行单位
- 操作系统可以执行多个进程。但是CPU一次只能执行一个进程,CPU通过快速切换执行进程实现多个进程的执行。(多核CPU可以真正的实现多个进程的执行)
- 一个进程可以创建其他进程,这些进程被称为子进程 6, 进程不与其他进程之间共享内存
- 进程的切换销毁需要的资源更多
线程
- 同一个进程下的线程会共享内存,不同的进程不会共享内存
- 同一个进程中,可能会有多个线程
- 线程切换销毁的资源更少
Node 事件循环
Node10 及以前,事件循环和浏览器有所差异。Node11 之后 Node 的事件循环和浏览器中一致
shell
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
- timers: 执行 setTimeout 和 setInterval 的回调
- pending callbacks: 执行推迟到下一次循环迭代的 I/O 回调。
- idle, prepare: node内部使用
- poll: 执行 IO 相关的回调
- check: 执行 setImmediate 的回调
- close callbacks: 执行关闭事件的回调,比如:socket.on('close', ...)
timers
定时器指定了一个阀值,阀值到了之后,会执行定时器的回调。但是 node 可能不是特别准确,只能说对于定时器回调,将在指定的时间过后尽可能早地运行。
在 timers 阶段,timers 队列中无论有多少个回调都会依次执行。并不像浏览器端,每执行一个宏任务后就去清空微任务。node 会先清空 timer 队列,然后清空微任务队列。
js
// 由于定时器可能不是特别准确,所以打印的顺序是不一定的
setTimeout(() => {
console.log('timeout')
}, 0)
setImmediate(() => {
console.log('immediate')
})
poll
处理 IO 相关的回调队列。even loop 将同步执行 poll 队列里的回调,直到队列为空。接下来 even loop 会去检查有没有 setImmediate,如果有 setImmediate, event loop 将结束 poll 阶段进入 check 阶段,并执行 check 阶段的任务队列。
如果没有 setImmediate,event loop 将阻塞在该阶段等待。同时 event loop 会有一个检查机制,检查 timer 队列是否为空,如果 timer 队列非空,会再次进入timer 阶段。
所以在处理完 poll 队列之后,setImmediate 先执行,之后是 setTimeout。
js
const fs = require('fs')
// setImmediate先执行
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
check
setImmediate 的回调会被加入 check 队列中, event loop 处理 check 队列。
setTimeout 和 setImmediate
- setImmediate 设计在 poll 阶段完成时执行,即 check 阶段
- setTimeout 设计在 poll 阶段为空闲时,且设定时间到达后执行,它在 timer 阶段执行
process.nextTick
process.nextTick 其实是独立于 event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。
js
// 1. poll 阶段
// 2. poll 阶段之后清空 nextTick 队列,打印:nextTick1,nextTick2,nextTick3,nextTick4
// 3. timers 阶段 打印:timer1
// 4. timers 阶段之后清空微任务,打印:promise1
// 打印的顺序:
// nextTick1
// nextTick2
// nextTick3
// nextTick4
// timer1
// promise1
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
process.nextTick(() => {
console.log('nextTick1')
process.nextTick(() => {
console.log('nextTick2')
process.nextTick(() => {
console.log('nextTick3')
process.nextTick(() => {
console.log('nextTick4')
})
})
})
})
什么是 NPX ?
-
NPM, Node 包管理器, NPM 内置在 Node.js 中,通过命令行工具 CLI 来和线上 NPM 数据库进行交互,这个数据库被称为 NPM Register
-
NPX, Node 包执行器, 该 Node 包可以是本地也可以是远程的。允许开发者在无需安装的情况下执行任意 Node 包
- 执行本地 Node 包时,NPX 会到node_modules/.bin路径和环境变量 $PATH 里面,检查命令是否存在
- 执行远程 Node 包时,NPX 会将 Node 包下载到一个临时目录中,使用以后再删除
-
使用 NPM 执行一个包
-
- 按照到本地,npm install package_name
-
- 执行包 ./node_modules/.bin/package_name(或者使用 npm run package-name 执行)
-
-
使用 NPX 执行一个包
-
- 直接执行 npx your-package-name
-
总结: NPM 是一个 Node 包管理器,NPX 是一个 Node 包执行器。包的执行也可以 NPM 来完成,但是必须进行本地安装,通过定位本地路径或者配置 scripts 来能执行。NPX 则通过一个简单命令大大简化了包运行的成本,既可以运行本地包,也可以远程包,无需安装包也可以执行该包,这就有效避免了本地磁盘污染的问题,节省了本地磁盘空间。
什么是 PNPM ?
- pnpm 本质上就是一个包管理器, 同 npm/yarn 一样。但是速度会比 npm/yarn 快 2, 3 倍。
- 同时占用磁盘空间小。用 npm/yarn 的时候,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次。但在使用 pnpm 只会安装一次,磁盘中只有一个地方写入,后面再次使用都会直接使用 hardlink。即使一个包的不同版本,pnpm 也会极大程度地复用之前版本的代码。举个例子,比如 lodash 有 100 个文件,更新版本之后多了一个文件,那么磁盘当中并不会重新写入 101 个文件,而是保留原来的 100 个文件的 hardlink,仅仅写入那一个新增的文件。
package-lock.json 和 york.lock
package-lock.json 和 york.lock 是为了解决扁平化的 node_modules 不确定性的问题的。保证 install 之后都产生确定的 node_modules 结构。