目标
- 理解 Vue 异步更新机制
- 理解 nextTick 原理
先来看看官网关于 异步更新队列 怎么说的。
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
官网提到了一个关键词异步,也就是说 Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
简单来说,Vue在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
先来验证一下刚才的说法:
验证DOM 异步更新
验证1:在更新了data中的message值后,马上去获取使用到message 的dom元素的值,发现此时dom并没有更新。
image.png
验证2:在改变message的值后,调用Vue.nextTick()方法,并在传入回调函数中获取使用message的dom元素的值就是message修改后的值。
image.png
于是,打开debugger,查看代码具体的执行步骤,调试结果如下:
打开debugger调试 image.png总结:DOM 更新是异步执行的,而Vue.nextTick 会在DOM完成更新后执行。
Vue.nextTick应用场景
需要在视图更新之后,基于新的视图进行操作。
扩展-JS的运行机制(事件循环)
那么为什么DOM是异步执行的呢?要知道其它语言如python、C语言都是多线程,而JS的一大特点就是单线程,也就是说,同一个时间只能做一件事,这似乎听起来是JS设计的一大弊端,没有充分利用到CPU多核的能力,其实并不是。因为JS主要是用来做一些用户交互、操作DOM等,如果JS是多线程,会带来很复杂的同步问题,如线程1在添加DOM,线程2在删除DOM,这就导致问题很复杂了。因此如果DOM更新为同步执行,就会大大消耗性能。
JS的运行机制(事件循环)如下:
1)所有同步任务都在主线程上执行,形成一个[执行栈] (execution context stack)。
2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
4)主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这个过程是源源不断的,每完成第一、二、三条,就相当于完成一个事件循环(Event Loop),这就是是JavaScript的运行机制。
扩展-任务队列是什么
"任务队列"是一个事件的队列,只要异步任务有了运行结果,就在"任务队列"之中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。异步任务除了接口调用,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。
所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,其实就是执行对应的回调函数。
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。
网友评论