浏览器和nodejs事件循环(Event Loop)有什么区别?

单线程和异步

  • JS是单线程的,无论在浏览器还是在nodejs
  • 浏览器中JS执行和DOM渲染共用一个线程,是互斥的
  • 异步是单线程的解决方案

1. 浏览器中的事件循环

异步里面分宏任务和微任务

  • 宏任务:setTimeoutsetIntervalsetImmediateI/OUI渲染,网络请求
  • 微任务:Promiseprocess.nextTickMutationObserverasync/await
  • 宏任务和微任务的区别:微任务的优先级高于宏任务,微任务在当前宏任务结束后、下一个渲染周期前执行。宏任务在下一个事件循环中执行,通常在微任务之后。
    • 宏任务在页面渲染之后执行
    • 微任务在页面渲染之前执行
    • 也就是微任务在下一轮DOM渲染之前执行,宏任务在DOM渲染之后执行
console.log('start')
setTimeout(() => { 
  console.log('timeout')
})
Promise.resolve().then(() => {
  console.log('promise then')
})
console.log('end')

// 输出
// start 
// end 
// promise then
// timeout

// 分析

// 等同步代码执行完后,先从微任务队列中获取(微任务队列优先级高),队列先进先出

// 宏任务 MarcoTask 队列
// 如setTimeout 1000ms到1000ms后才会放到队列中
const MarcoTaskQueue = [
  () => {
    console.log('timeout')
  },
  fn // ajax回调放到宏任务队列中等待
]  

ajax(url, fn) // ajax 宏任务 如执行需要300ms


// ********** 宏任务和微任务中间隔着 【DOM 渲染】 ****************

// 微任务 MicroTask 队列
const MicroTaskQueue = [
  () => {
    console.log('promise then')
  }
]

// 等宏任务和微任务执行完后 Event Loop 继续监听(一旦有任务到了宏任务微任务队列就会立马拿过来执行)...

<p>Event Loop</p>

<script>
  const p = document.createElement('p')
  p.innerHTML = 'new paragraph'
  document.body.appendChild(p)
  const list = document.getElementsByTagName('p')
  console.log('length----', list.length) // 2

  console.log('start')
  // 宏任务在页面渲染之后执行
  setTimeout(() => {
    const list = document.getElementsByTagName('p')
    console.log('length on timeout----', list.length) // 2
    alert('阻塞 timeout') // 阻塞JS执行和渲染
  })
  // 微任务在页面渲染之前执行
  Promise.resolve().then(() => {
    const list = document.getElementsByTagName('p')
    console.log('length on promise.then----', list.length) // 2
    alert('阻塞 promise') // 阻塞JS执行和渲染
  })
  console.log('end')
</script>

2. nodejs中的事件循环

  • nodejs也是单线程,也需要异步
  • 异步任务也分为:宏任务 + 微任务
  • 但是,它的宏任务和微任务分为不同的类型,有不同的优先级
  • 和浏览器的主要区别就是类型优先级,理解了这里就理解了nodejs的事件循环

宏任务类型和优先级

类型分为6个,优先级从高到底执行

  • TimersetTimeoutsetInterval
  • I/O callbacks:处理网络、流、TCP的错误回调
  • Idle,prepare:闲置状态(nodejs内部使用)
  • Poll轮询 :执行poll中的I/O队列
  • Check检查 :存储setImmediate回调
  • Close callbacks :关闭回调,如socket.on('close')

注意process.nextTick优先级最高,setTimeoutsetImmediate优先级高

执行过程

  • 执行同步代码
  • 执行微任务(process.nextTick优先级最高)
  • 按顺序执行6个类型的宏任务(每个开始之前都执行当前的微任务)

总结

  • 浏览器和nodejs的事件循环流程基本相同

  • nodejs宏任务和微任务分类型,有优先级。浏览器里面的宏任务和微任务是没有类型和优先级的

  • node17之后推荐使用setImmediate代替process.nextTick(如果使用process.nextTick执行复杂任务导致后面的卡顿就得不偿失了,尽量使用低优先级的api去执行异步)

    console.info('start')
    setImmediate(() => {
    console.info('setImmediate')
    })
    setTimeout(() => {
    console.info('timeout')
    })
    Promise.resolve().then(() => {
    console.info('promise then')
    })
    process.nextTick(() => {
    console.info('nextTick')
    })
    console.info('end')

    // 输出
    // start
    // end
    // nextTick
    // promise then
    // timeout
    // setImmediate

3. nodejs如何开启多进程,进程如何通讯

进程process和线程thread的区别

  • 进程,OS进行资源分配和调度的最小单位,有独立的内存空间
  • 线程,OS进程运算调度的最小单位,共享进程内存空间
  • JS是单线程的,但可以开启多进程执行,如WebWorker

为何需要多进程

  • 多核CPU,更适合处理多进程
  • 内存较大,多个进程才能更好利用(单进程有内存上限)
  • 总之,压榨机器资源,更快、更节省

如何开启多进程

  • 开启子进程 child_process.forkcluster.fork
    • child_process.fork用于单个计算量较大的计算
    • cluster用于开启多个进程,多个服务
  • 使用sendon传递消息

使用child_process.fork方式

const http = require('http')
const fork = require('child_process').fork

const server = http.createServer((req, res) => {
  if (req.url === '/get-sum') {
    console.info('主进程 id', process.pid)

    // 开启子进程 计算结果返回
    const computeProcess = fork('./compute.js')
    computeProcess.send('开始计算') // 发送消息给子进程开始计算,在子进程中接收消息调用计算逻辑,计算完成后发送消息给主进程

    computeProcess.on('message', data => {
      console.info('主进程接收到的信息:', data)
      res.end('sum is ' + data)
    })

    computeProcess.on('close', () => {
      console.info('子进程因报错而退出')
      computeProcess.kill() // 关闭子进程
      res.end('error')
    })
  }
})
server.listen(3000, () => {
  console.info('localhost: 3000')
})

// compute.js

/**
 * @description 子进程,计算
 */

function getSum() {
  let sum = 0
  for (let i = 0; i < 10000; i++) {
    sum += i
  }
  return sum
}

process.on('message', data => {
  console.log('子进程 id', process.pid)
  console.log('子进程接收到的信息: ', data)

  const sum = getSum()

  // 发送消息给主进程
  process.send(sum)
})

使用cluster方式

const http = require('http')
const cpuCoreLength = require('os').cpus().length
const cluster = require('cluster')

// 主进程
if (cluster.isMaster) {
    for (let i = 0; i < cpuCoreLength; i++) {
      cluster.fork() // 根据核数 开启子进程
    }

    cluster.on('exit', worker => {
      console.log('子进程退出')
      cluster.fork() // 进程守护
    })
} else {
  // 多个子进程会共享一个 TCP 连接,提供一份网络服务
  const server = http.createServer((req, res) => {
    res.writeHead(200)
    res.end('done')
  })
  server.listen(3000)
}


// 工作中 使用PM2开启进程守护更方便
相关推荐
荔枝味-1 小时前
html详细知识
前端·javascript·html
Jason-河山3 小时前
「铭记历史 珍爱和平」勿忘9.18!
前端
国王不在家3 小时前
keyof和infer
前端
猿大撒3 小时前
Spring-data-redis
前端·bootstrap·html
帅过二硕ฅ4 小时前
uniapp点击跳转到对应位置
前端·javascript·uni-app
正小安7 小时前
URL.createObjectURL 与 FileReader:Web 文件处理两大法宝的对比
前端·javascript
赵广陆7 小时前
SprinBoot+Vue宠物寄养系统的设计与实现
前端·vue.js·宠物
A黄俊辉A7 小时前
vue3中把封装svg图标为全局组件
前端·javascript·vue.js
老贾爱编程8 小时前
VUE实现刻度尺进度条
前端·javascript·vue.js
F2E_Zhangmo8 小时前
vue如何做到计算属性传参?
前端·javascript·vue.js