for循环里面有异步操作_[JS基础] 6 - 执行机制, 同步异步, Event Loop, 宏任务, 微任务...

e4ec666017373e042ccbfda0a8159b09.png
# 事件循环 Event Loop
本质是: 
1. 作为单线程js对于异步事件的处理机制 

2. 或者可以说是 只有一个主线程js的处理逻辑

3. 如何保证主线程, 有序并高效 或非阻塞 的处理呢? => 事件循环机制 Event Loop

4. 异步任务也是有优先级的,分为 宏任务 MacroTask, 微任务 MicroTask

你也可能会碰到以下问题:

- js如何处理 同步 和 异步? 
- 什么是事件循环机制?  
- 异步的宏任务和微任务区别? 
- 执行流程优先级又是什么?

1- 单线程 js 按照语句出现顺序执行

一道题看懵你, 这个输出结果并不是按照语句顺序阿~

setTimeout(function(){
    console.log('定时器开始啦')
});

new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log('执行then函数啦')
});

console.log('代码执行结束');

结果:

  • 马上执行for循环啦
  • 代码执行结束
  • 执行then函数啦
  • 定时器开始啦

2- 事件循环 Event Loop

  • js是单线程, 那么相当于只有一个柜台窗口的银行, 要一个个顺序的处理业务。前面一个任务是要存款1000w 办理时间长, 后面存款10w的就要等着。
  • 怎么能更高效的处理同步和异步的任务呢?
  • 那么问题是: 如果浏览器加载一张图片非常的慢需要10s, 难道要等着其下载完,才能执行点击 或 其他操作吗?
  • 事件循环机制: 本质上是js对异步处理机制。

任务:

  1. 同步任务
  2. 异步任务

e89685fba7e2bbfdee524361c661d540.png
  • 同步和异步的任务分别进入不同的执行场所。
  • 同步直接在主线程执行,异步的回调 则去小黑屋,等待着,时机到了才会被运行。
  • 当同步任务在主线程中全部执行完毕后,再去事件队列中执行 异步回调的函数,并进入主线程执行。
  • 上述过程不断重复。叫作 事件循环机制

2.1- 例子1

axios.get().then(() => console.log(1))
console.log(2)
// 执行顺序: axios => 输出2 => 输出1
  • axios 请求
  • 异步回调函数 放入异步队列中
  • 执行console.log(2) 主线程空 则执行异步队列中的函数
  • console.log(1) 被执行

2.2- 例子2 setTimeout

你会发现console.log('延时3秒'); 输出的不是3s, 是5s, 超过3s.

<script>
const sleep = time => {
    let startTime = new Date().getTime() + parseInt(time, 10)
    while (startTime > new Date().getTime()) {}
}

setTimeout(() => {
    console.log('延时3秒');
}, 3000)

// 休眠5s
sleep(5000)
</script>
  • setTimeout执行, 3s后, 将 回调函数 放到 异步队列中 [3s => '延时3秒']
  • 执行sleep函数, 停止5s
    当sleep函数执行到3s的时候, 异步函数返回值可执行, 但是因为sleep占用主线程, 只能等待
  • 5s后, 主线程空了, 执行输出 '延时3秒'
  • 所以,此时延迟是大于5s

3- 宏任务 和 微任务

  • 宏任务 macro task: script代码, setTimeout, setInterval
  • 微任务 micro task: Promise.then, process.nextTick

异步的事件队列 还分是去 宏任务 还是去 微任务 !

  • 同步
  • 微任务异步队列(Micro Event Queue)
  • 宏任务异步队列(Macro Event Queue)
<script>
setTimeout(() => {
    console.log('setTimeout 宏任务: 异步队列')
})

console.log('宏任务: 同步队列 = 1')

new Promise(resolve => {
    resolve('666')
    console.log('promise 任务先执行, then才是微任务处理')
}).then(data => {
    console.log('promise.then 微任务执行', data)
})

console.log('宏任务: 同步队列 = 2')
</script>

- 宏任务: 同步队列 = 1
- promise 任务先执行, then才是微任务处理
- 宏任务: 同步队列 = 2
- promise.then 微任务执行 666
- setTimeout 宏任务: 异步队列
  1. setTimeout 先执行, 将回调函数放到 异步队列中, 等待执行 MacroQueue = [0s=> 'setTimeout 宏任务: 异步队列']
  2. 输出 => '宏任务: 同步队列 = 1'
  3. new Promise立即被执行
  4. 输出 => 'promise 任务先执行, then才是微任务处理'
  5. then 放入 MicroQueue = [then=> 'promise.then 微任务执行' + data ]
  6. 输出 => '宏任务: 同步队列 = 2'
  7. 主线程空了,可以执行异步的事情。
  8. 先去执行 MicroQueue, 再去执行MacroQueue
  9. 输出 => promise.then 微任务执行 666
  10. 输出 => setTimeout 宏任务: 异步队列

4- 宏任务 和 微任务 执行顺序?

宏任务 macro task: script代码, setTimeout, setInterval
微任务 micro task: Promise.then, process.nextTick
  • 同步任务线进入主线程执行
  • 异步处理还分为: macro宏任务, micro 微任务
  • micro 微任务优先级优于macro宏任务
  • 优先级: 同步任务 > micro 微任务 > macro宏任务

014beae7ad5ff4a5aa2ebf2342fad352.png
// 同步, 异步宏任务macro, 异步微任务micro
// 同步 > 异步微任务micro > 异步宏任务macro
console.log('1');

// as setTimeout1
setTimeout(function() {
    console.log('2');
    // as process2
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        // promise.then__setTimeout1
        console.log('5')
    })
})

// as process1
process.nextTick(function() {
    console.log('6');
})

new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

// as setTimeout2
setTimeout(function() {
    console.log('9');
    // as process3
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        // promise.then__setTimeout2
        console.log('12')
    })
})

/** 
结果
1
7
6
8
2
4
3
5
9
11
10
12
*/

步骤分析

1. 输出 => 1
2. 放入宏任务 异步队列中 MacroEventQueue = [setTimeout1]
3. 放入微任务 异步队列中 MicroEventQueue = [process1]
4. promise先执行
   输出 => 7
   将promise.then 放入微任务 异步队列中 MicroEventQueue = [process1, promise.then]
5. 放入宏任务 异步队列中 MacroEventQueue = [setTimeout1, setTimeout2]
6. 此时同步已经执行完了, 主线程空闲, 先执行MicroEventQueue, 再执行MacroEventQueue
7. 先执行MicroEventQueue = [process1, promise.then]
8. 输出 => 6
9. 输出 => 8
10. MicroEventQueue已空, 执行MacroEventQueue
11. MacroEventQueue = [setTimeout1, setTimeout2]
12. 输出 => 2
    MicroEventQueue = [process2]
    Promise立刻执行
    输出 => 4
    Promise.then放到 MicroEventQueue = [process2, promise.then__setTimeout1]
13. 此时剩下 MacroEventQueue = [setTimeout2] MicroEventQueue = [process2, promise.then__setTimeout1]
    MicroEventQueue不为空, 先执行
    输出 => 3
    输出 => 5
    此时剩下 MacroEventQueue = [setTimeout2] MicroEventQueue 空

14. MacroEventQueue = [setTimeout2]
    输出 => 9
    MicroEventQueue = [process3]
    Promise立刻执行
    输出 => 11
    MicroEventQueue = [process3, promise.then__setTimeout2]

15. 此时 MacroEventQueue 空, MicroEventQueue = [process3, promise.then__setTimeout2]
    输出 => 10
    输出 => 12

5- Recap

  • js单线程,同步阻塞
  • 同步和异步。同步优于异步在主线程中执行, 只有主线程空闲, 异步才能被执行
  • 事件循环机制,异步的解决方案 / 机制
  • 事件循环机制 => macro 宏任务 和 micro任务
  • 最后: 优先级 同步 > micro任务 > macro 宏任务

6- 整体总结

js是单线程, 也就是说只有一个 主线程执行

不恰当例子有助于理解:
银行就一个柜台, 好多人去排队。
每个人的业务不同, 有人就是交水电费, 办理存款业务的人花费时间多些
如何提高银行的办事儿效率?

规定, 我们暂定按照处理时间来算优先级
1. 交水电费优先级更高些 (花费10ms)
2. 办理存款业务的人低优先级 (花费1s)
   - 存款10w (花费10s) 
   - 存款1个亿 (花费100s)

按照花费的时间算优先级
- 交水电费 (同步) 直接在主线程运行
- 办理存款业务 另外开个小窗口, 毕竟是VIP大客户, 处理存款
- 存款10w 放到 Micro Event Queue 微任务队列中
- 存款1个亿 放到 Macro Event Queue 宏任务队列中

优先级: 同步任务 > Micro Event Queue 异步微任务队列 > Macro Event Queue 异步宏任务队列

事件循环: 一旦发现优先级比自己高的, 要去执行高的, 让出主线程, 等前一个优先级任务空了, 再执行后面的。

如何管理 同步和异步 在主线程的处理逻辑?

- 同步: 优先级更高在主线程处理掉, 异步只能等同步处理完, 再处理.

Event Loop 事件循环
- 异步: 也分369等, 宏任务和微任务之分.
  微任务: promise.then, process.nextTick
  宏任务: setTimeout, setInterval, axios回调, 正常js逻辑等

- 优先级: 同步 > 异步(微任务) > 异步(宏任务)

7 - 题巩固一下

7.1 - 题1

console.log('script start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
console.log('script end');
  • 输出 => script start
  • setTimeout放到macro queue 等到主线程空运行
  • Promise.resolve()里的立刻执行,then后的都放到micro queue 等待运行
  • 输出 => script end
  • micro queue 优先 macro queue
  • 输出 => promise1
  • 输出 => promise2
  • 输出 => setTimeout

7.1 - 题2

注意async, await 的用法 和 promise的关系

console.log('script start')
async function async1() {
  await async2() // await = 立即执行,后面的code, 相当于放到promise.then(...)
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}
async1()
setTimeout(function() {
  console.log('setTimeout')
}, 0)
new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })
console.log('script end')
  • 输出 => script start
  • async1() 立即执行
  • await async2() 立即执行, 后面的code, 相当于放到promise.then(...)
// 重点
async function f() {
  await p() // p立即执行, 后面的代码,相当于放到then里
  console.log(1)
}

// 相当于
function f () {
  return new Promise(() => {
     resolve(p())
  }).then(() => console.log(1))
}
  • 输出 => script start
  • 输出 => async2 end await async2() = new Promise(xxx) 一样会立刻执行
  • console.log('async1 end') 被放到micro queue中 = await下一行 相当于 promise.then后内容
  • setTimeout 放到 macro queque中
  • 输出 => Promise, 两个then的内容放到micro queue中
  • 输出 => script end
  • 执行 micro queue
  • 输出 => async1 end
  • 输出 => promise1
  • 输出 => promise2
  • 执行 macro queue
  • 输出 => setTimeout

7.3 - 题3

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)

setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')
  • 输出 => start
  • setTimeout 进入 marco queue
  • setTimeout 进入 marco queue
  • Promise.resolve()立即执行,then放到 micro queue
  • 输出 => end
  • 先执行 micro queue
  • 输出 => promise3
  • marco queue执行
  • 输出 => timer1
  • Promise.resolve()立即执行,then放到 micro queue
  • 因为micro queue 优先级 > marco queue
  • 输出 => promise1 此时micro queue已清空
  • marco queue执行
  • 输出 => timer2
  • 输出 => promise2
  • 如果你用浏览器去执行,下面即使答案。同步 > micro任务 > macro 宏任务
  • 如果你用Node11+版本,和浏览器行为一致。同步 > micro任务 > macro 宏任务
  • 如果你是Node10以下版本,输出为: 当macro为空才去执行micro。
start
end
promise3
timer1
timer2
promise1
promise2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值