eventLoop浅论

浏览器事件环

代码执行顺序:默认先执行主栈中的代码,主栈执行完成后清空微任务对列,微任务对列清空后,取出第一个宏任务到主栈中执行,执行完成后再去检查微任务对列是否有未执行任务,若是有清空,若没有取下一个宏任务到主栈中执行,执行完成后再去检查微任务对列是否有未执行任务,若是有清空...造成一个事件环javascript

栈(Stack)

是一种遵循后进先出(last in first out LIFO)原则的有序集合;在栈里,新元素都靠近栈顶,旧元素都靠近栈底(现实中栈的例子:一摞书或者堆放在一块儿的盘子)html

  • 栈顶:新添加的或待删除的元素保存的一端

队列(Queue)

是一种遵循先进先出(first in first out FIFO)原则的一组有序的项;在队列中,最新添加的元素必须排在队列的末尾(现实中队列的例子:排队过安检) java

宏任务

  • setTimeout
  • setImmediate(只有IE支持)
    • setTimeout
  • messageChannel
    • web worker中可用
    • 容许建立一个新的消息通道,并经过它的两个MessagePort属性发送数据

白话说明:咱们小的时候都玩过土电话,咱们能够新的消息通道想象成咱们的土电话,把两个MessagePort属性想像成图中的小熊(port2)和小兔(port1),当小兔经过土电话向小熊喊话(port1向另个port2发送数据),声音的传递过程(数据传递的过程就是)就是异步的。node

<html>
<body>
  <div id="app"></div>
  <script> console.log('start') // 这里咱们为了测试他是异步的,咱们采用port1先发送数据,port2后监听 let channel = new MessageChannel(); let port1 = channel.port1; let port2 = channel.port2; port2.postMessage('你好') setTimeout(()=>{ console.log('setTimeout'); },0) port1.onmessage = function (e) { console.log(e); } console.log('end') </script>
</body>
</html>
复制代码

微任务

  • Promise.resolve.then
  • MutationObserver
    • DOM3 Events规范的一部分
    • 提供了监视对DOM树所作更改的能力
<html>
<body>
  <div id = "app"></div>
  <script> /* 实现 等待dom更新后 再取dom中的数据 */ setTimeout(()=>{ console.log('timeout'); },0) // 1.建立一个MutationObserver的实例并传入一个异步的callback let mutationObserver = new MutationObserver(()=>{ console.log(app.children.length); // 异步执行 }) // 2.观察app的childList的变化 mutationObserver.observe(app,{childList:true}) // 3.经过脚本动态向app中插入30个p for (let i = 0; i < 10; i++) { app.appendChild(document.createElement('p')); } for (let i = 0; i < 10; i++) { app.appendChild(document.createElement('p')); } for (let i = 0; i < 10; i++) { app.appendChild(document.createElement('p')); } </script>
</body>
</html>
复制代码

小题开胃

console.log('start')
setTimeout(()=>{
    console.log(1);
    Promise.resolve().then(()=>{
        console.log(2)
    })
},0);
Promise.resolve().then(data=>{
    console.log(3);
    setTimeout(()=>{
        console.log(4);
    })
})
console.log('end');
// start end 3 1 2 4
复制代码
  • 图文解析

难题巩固

async function async1() {
  console.log('async1Start');
  await async2(); 
  console.log('async1End');
}
async function async2() {
  console.log('async2');
}
console.log('scriptStart');
setTimeout(function () {
  console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
  console.log('promise1');
  resolve();
}).then(function () {
  console.log('promise2');
});
console.log('scriptEnd');
//scriptStart async1Start async2 promise1 scriptEnd async1End promise2 setTimeout
复制代码
  • 图文解析

思考题

  • 分析如下代码如何输出?为何?
let guYan = async () => {
  console.log(await new Promise((resolve, reject) => {
    console.log('guYan');
  }))
}
guYan()
复制代码

注:前面的两道题的分析中没加入宏、微任务事件池的概念(就是在进入队列以前须要先通过事件池的排列)web

node事件环

  • 每一个阶段都有一个本身的队列
  • 在低版本node中在主执行栈执行完毕和切换队列的时候清空微任务
  • 在高版本(11+)node中和浏览器同样在主执行栈执行完毕和一个宏任务执行完毕后清空微任务
// 咱们排除掉node内部本身调用的循环部分,其实须要咱们掌握的部分并很少,我总结为三部分
// 1.timer阶段主要表明setTimeout
// 2.poll 轮循阶段 主要是i/o的读写(fs模块的一些操做等)
// 3.check阶段主要表明setImmediate
   ┌───────────────────────────┐
┌─>│         1.timers          │──┐                         
│  └─────────────┬─────────────┘  │   ┌───────────────┐     | 
│  ┌─────────────┴─────────────┐  │   │   incoming:   │     | 执
│  │         2.poll            │<─────┤  connections, │     | 行
│  └─────────────┬─────────────┘  │   │   data, etc.  │     | 顺
│  ┌─────────────┴─────────────┐  │   └───────────────┘     | 序
└──│         3.check           │<─┘                         ↓
   └───────────────────────────┘
/* *执行顺序分析: * 1.从timer阶段开始检查是否有可执行的若没有向下执行 * 2.在poll阶段执行完毕后检查timer阶段是否待执行的,如有准备跳回timer阶段执行; * 可是在这以前须要检查check阶段是否有待执行的,如有先跳到check阶段执行,再跳到timer阶段执行,若没有可直接跳回timer阶段执行; * 3.无条件按照顺序执行,没有可跳过 */
复制代码

宏任务

  • setTimeout
  • setImmediate
  • setInterval
  • readFile文件读写系列

微任务

  • promise.then
  • process.nextTick

案例解析

案例一

setTimeout(()=>{
  console.log('setTimeout');
},0)
setImmediate(()=>{
  console.log('setImmediate');
})
复制代码
  • 上面的代码你频繁的在node环境中执行你会发现两种结果
    • 1.先输出setTimeout再输出setImmediate,这种状况我不作说明哈就是正常状况
    • 2.先输出setImmediate再输出setTimeout,这种状况的缘由是:当咱们程序运行的时候咱们知道setTimeout的第二个参数表明时间虽然咱们写成0可是最小时间并非0,大约是4。因此当执行到timer阶段的时候setTimeout的执行时间还没到,直接进行到poll阶段等待,等待一段时候后发现timer阶段的setTimeout能够执行,准备回到timer阶段执行,可是回去以前须要检查check阶段,发现有setImmediate须要执行

案例二

let fs = require('fs');
fs.readFile(__filename,'utf-8',(err,data)=>{
    setTimeout(()=>{
       console.log('setTimeout') 
    },0)
    setImmediate(()=>{
        console.log('setImmediate')
    })
)
复制代码
  • 程序运行发现timer阶段没有可执行的,直接进入poll阶段,在poll阶段分别给timer阶段和check阶段增长一个待执行函数后开始等待setTimeout的执行,当setTimeout可执行后,准备从poll阶段跳到timer以前检查check阶段有可执行的setImmediate因此永远是先输出setImmediate后输出setTimeout

小概念总结

进程和线程的关系

  • 进程:计算机分配任务和调度任务的最小单位(进程是线程的集合)
  • js中一个进程只有一个主线程
    • js线程和UI线程是互斥的

如何提升js代码的加载速度

  • defer
    • 表示脚本会被延迟到文档彻底被解析和显示以后再执行,加载后续文档元素的过程将和js脚本的加载并行进行(即实现了异步加载js脚本),这样页面加载会变快
    <script src="index.js" defer></script>
    复制代码
  • async(h5属性)
    • 脚本一旦可用,执行就会异步
    • 不保证脚本的加载顺序按照引入的顺序,所以一旦使用就要保证所需加载的js脚本无依赖关系
    <script src="index.js" async></script>
    复制代码
  • preload(会先加载资源)
  • prefetch(默认不会立刻加载,等待浏览器空闲时偷偷加载)
相关文章
相关标签/搜索