零零散散之JS事件循环机制详解

JS事件循环机制(Event Loop)

众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但本质上JavaScript
是单线程,,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?

任务队列

所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout

定时函数等都属于异步任务,异步任务会通过任务队列的机制(先进先出的机制)来进行协调。

当主线程的任务执行完毕,就回去任务队列读取相应的任务,并推入主线程执行。上述过程不断重复也即是我们所说的Event
Loop(事件循环机制)(Tick)

并且推入主线程的任务可以分为宏任务、微任务

宏任务与微任务

最先进入任务队列的宏任务,执行其同步任务,然后判断是否存在微任务,有的话则执行微任务至微任务队列为空,开始下一轮任务循环(Tick),执行宏任务中的异步代码(setTimout等异步回调)

在这里插入图片描述

是否可以理解为优先级: 无论宏任务还是微任务同步任务>异步任务,微任务>宏任务
console.log('script start');

setTimeout(function() {
  console.log('timeout1');
}, 10);

new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})

console.log('script end');

// script start
// promise1
// script end
// then1
// timeout1
// timeout2

首先,事件循环从宏任务 (macrotask) 队列开始,最初始,宏任务队列中,只有一个 script(整体代码)任务;当遇到任务源
(task source) 时,则会先分发任务到对应的任务队列中去。所以,就和上面例子类似,首先遇到了console.log,输出
script start; 接着往下走,遇到 setTimeout 任务源,将其分发到任务队列中去,记为 timeout1; 接着遇到
promise,new promise 中的代码立即执行,输出 promise1, 然后执行 resolve ,遇到 setTimeout
,将其分发到任务队列中去,记为 timemout2, 将其 then 分发到微任务队列中去,记为 then1; 接着遇到
console.log 代码,直接输出 script end 接着检查微任务队列,发现有个 then1 微任务,执行,输出then1
再检查微任务队列,发现已经清空,则开始检查宏任务队列,执行 timeout1,输出 timeout1; 接着执行 timeout2,输出
timeout2 至此,所有的都队列都已清空,执行完毕。其输出的顺序依次是:script start, promise1, script
end, then1, timeout1, timeout2

这方面一些拓展相关知识

async与await是怎么处理异步任务的

简单说async是通过Promise包装异步任务

调用async1,遇见await这时候就会暂停async1的执行,让出线程去执行async2的代码,等async2执行完毕再回到async1的线程继续执行;

因此输出async1()得到pedding状态的promise,因为console.log是同步任务,会立即执行,而此时async1()去执行async2()了,返回一个Promise对象,并且状态是pedding

Promise、process.nextTick谁先执行?

process.nextTick为一个特殊的异步api,它不属于任何的Event
Loop阶段,并且为Node环境下的方法,当Node遭遇这个api时,Event
Loop根本不会继续执行,而是暂停,先执行process.nextTick,因此process.nextTick遭遇Promise,肯定是技高一筹,nextTick的队列优先级会更高

Vue中的vm.$nextTick

	先要注意这里的vm.$nextTick与上面的process.nextTick完全不是同一个东西

	vm.$nextTick 接受一个回调函数作为参数,用于将回调延迟到下次DOM更新周期之后执行。

	它是基于事件循环实现的,‘下次DOM更新周期’指的是下次微任务执行更新DOM时候,vm.$nextTick会将回调函数添加的微任务中,并且在微任务之后运行
//改变数据
vm.message = 'changed'
 
//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'
 
//这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
    console.log(vm.$el.textContent) //可以得到'changed'
})

简单说,如果你需要更新DOM后获取DOM,或者在DOM更新后执行某一块代码,你必须将这块代码放到下一次事件循环,比如setTimeout(fn,0),vm.$nextTick(fn),这样DOM在更新之后就会立即执行这块代码。

事件循环:

简单来说,Vue在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

第一个tick(本次更新循环)

	首先修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及DOM.

	Vue开启一个异步队列,并缓冲在此事件循环中发生的所有数据变化。如果同一个watcher被多次触发,只会被推入队列中一次。

第二个tick(‘下次更新循环’)

	同步任务执行完毕,开始执行异步watcher队列的任务,更新DOM。Vue在内部尝试对异步队列使用原生的Promise.then和MessageChannel 方法,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

第三个tick(下次 DOM 更新循环结束之后)

最后再来一点小练习吧

求求你一定要快、准、狠地做出来!!!

console.log('script start')

async function async1() {
  await async2()
  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')
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值