事件循环、异步更新与Vue.nextTick()

JS的事件循环

JS是单线程的,Node在处理I/O的时候也是单线程的,好处就是没有线程的切换和数据共享的问题。但实际上在系统底层非阻塞的I/O解决方案,只有Network I/O部分:linux底层使用epoll,windows下使用IOCP。文件等操作则没有很好的系统解决方案,为了模拟实现非阻塞的I/O方案,Node在libuv层启用了一个线程池,用于调用系统的阻塞式I/O。

实际上,node只是在应用层属于单线程,底层其实通过libuv维护了一个阻塞I/O调用的线程池。

JS之所以可以在执行异步操作时可以设置好callback函数之后,就继续执行后续代码,当异步操作执行完成时,又能够及时触发回调函数,获取异步结果。最关键的核心就在于事件循环。
Tick流程

在每个tick,判断有事件需要处理,需要观察者,每一个事件循环都有一个或者多个观察者,判断是否有事件要处理的过程就是向这些观察者询问是否有需要处理的事件。
Node的观察者有以下几种:

  • idle观察者:顾名思义,就是早已等在那里的观察者,以后会说到的process.nextTick就属于这类

  • I/O观察者:顾名思义,就是I/O相关观察者,也就是I/O的回调事件,如网络,文件,数据库I/O等

  • check观察者:顾名思义,就是需要检查的观察者,后面会说到的setTimeout/setInterval就属于这类
    事件循环是一个典型的生产者/消费者模型。异步I/O,网络请求,setTimeout等都是典型的事件生产者,源源不断的为Node提供不同类型的事件,这些事件被传到对应的观察者那里,事件循环在每次tick时则从观察者那里取出事件并处理。

宏任务和微任务

当拿到一段Javascript代码,浏览器货Node环境首先要做的就是:传递给Javascript引擎,并要求它去执行。但是,当宿主环境遇到一些事件时,会继续把一段代码传递给Javascript引擎去执行,此外,如setTimeout这样的API,他会允许Javascript在特定的时机执行。
因此可以理解为:一个Javascript引擎会常驻于内存中,它等待着宿主把Javascript代码或者函数传递给它执行。

在ES3和更早的版本中,Javascript本身还没有异步执行代码的能力,即宿主环境传递给Javascript引擎一段代码,引擎就把代码直接顺次执行了,这个任务也就是宿主发起的任务。

在ES5之后,Javascript引入了Promise,这样,不需要浏览器的安排,Javascript引擎本身也可以发起任务了。

把宿主发起的任务称为宏任务,Javascript引擎发起的任务称为微任务。

Javascript引擎等待宿主环境分配宏任务,在Node术语中,这个部分称为事件循环。

整体来说:整个循环做的事情基本上就是反复“等待 - 执行”,每次的执行过程,其实都是一个宏任务,宏任务的队列就相当于事件循环

在宏观任务中,JavaScript 的 Promise 还会产生异步代码,JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列:
在这里插入图片描述
Promise 永远在队列尾部添加微任务。setTimeout等宿主API,则会添加宏任务。

优先级顺序:process.nextTick > setTimeout/setInterval > setImmediate

Vue的异步更新队列

vue的异步更新是基于JS事件循环原理的。

  • Vue在更新DOM时是异步执行的,只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
  • 如果同一个watcher被多次触发,只会被推入到队列中一次,在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。
  • 然后再下一个事件循环“tick”中,Vue刷新队列并执行实际(已去重)的工作。
    Vue在内部对异步队列尝试使用原生的Promise.then, MutationObserver, setImmediate,如果执行环境不支持,则采用setTimeout(fn,0)代替。

process.nextTick

每次调用Process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick时取出执行。定时器采用红黑树的操作时间复杂度为o(lg(n)),而nextTick()的时间复杂度为o(1)。相较之下,process.nextTick()更高效。

Vue.nextTick()用法

官方文档:
Vue.nextTick([callback, context])

  • 参数:
    {function} [callback]
    {Object} [context]
  • 用法:
  1. 在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
  2. (2.1.0起新增)如果没有提供回调且在支持Promise的环境中,则返回一个Promise。
    注:Vue不自带Promise的polyfill,如果目标浏览器不原生支持Promise,得自己提供polyfill。
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
  // DOM 更新了
})

// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
  .then(function () {
    // DOM 更新了
  })
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值