this.$nextTick()
原理:Vue 实现响应式并不是数据发⽣变化之后 DOM ⽴即变化,⽽是按⼀定的策略进⾏ DOM 的更新。$nextTick 是在下次 DOM更新循环竣事之后执⾏耽误回调,
在修改数据之后使⽤ $nextTick,则可以在回调中获取更新后的 DOM,
在下次 DOM更新循环竣事之后执行耽误回调。
简单的明白是:当数据更新了,在dom中渲染后,⾃动执⾏该函数
Vue在更新data之后并不会立即更新DOM上的数据,就是说如果我们修改了data中的数据,再马上获取DOM上的值,我们取得的是旧值,我们把获取DOM上值的操作放进$nextTick里,就可以得到更新后得数据。
正确的⽤法是:vue改变data中的数据后,使⽤vue.$nextTick()⽅法包裹js对象执行后续代码
什么时候使用$nextTick
1、Vue⽣命周期的created()钩⼦函数进⾏的DOM操作⼀定要放在Vue.nextTick()的回调函数中,原因是在created()钩⼦函数执⾏的时候,DOM
其实并未进⾏任何渲染,⽽此时进⾏DOM操作⽆异于徒劳,所以此处⼀定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。
2、当项⽬中改变data函数的数据,想基于新的dom做点什么,对新DOM⼀系列的js操作都须要放进Vue.nextTick()的回调函数中
$nextTick()执行原理
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue
将开启一个任务队列,并缓冲在同一时间循环中发生的所有数据变更。如果同一个 watcher
被多次触发,只会被推入到队列中一次。(这种在缓冲时去除重复数据对于避免不须要的计算和 DOM 操作是非常重要的)
然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行任务队列 (已去重的) 工作。
Vue 在内部对异队伍列尝试使用原生的 Promise.then(微任务)、MutationObserver 和
setImmediate,如果执行环境不支持,则会接纳 setTimeout(fn, 0)(宏任务)取代。
二、JS 运行机制 (JS 执行是单线程的,它是基于事件循环的。)
JS 执行是单线程的,它是基于事件循环的。 事件循环大致分为以下几个步调:
所有 同步任务都在主线程上执行 ,形成一个执行栈
主线程之外,还存在一个"任务队列"(task queue)。
只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是竣事等待状态,进入执行栈,开始执行。主线程不绝重复上面的第三步。
https://i-blog.csdnimg.cn/blog_migrate/e42b367d87e4a2578e044dd54bbfa835.png
主线程的执行过程就是一个 tick,而所有的异步结果都是通过 “任务队列”
来调理。"任务队列"中存放的是一个个的任务(task)。规范中规定 task 分为两大类,宏任务和微任务
https://i-blog.csdnimg.cn/blog_migrate/c8ff7400e75165bb8af21e47da8f9bf1.webp?x-image-process=image/format,png
微任务,也称job
通常用于在当前正在执行的脚本之后直接发生的事变,好比对一系列的行为做出反应,大概做出一些异步的任务,而不须要新建一个全新的task。只要执行栈没有其他javascript在执行,在每个task竣事时,微任务队列就会在回调后处理。在微任务期间排队的其他微任务将被添加到这个队列的末尾
常见的 微任务 有 MutationObsever 、 Promise.then 、$nextTick
宏任务
作用是为了让浏览器能够从内部获取javascript /
dom的内容并确保执行栈能够顺序进行。调理是随处可见的,例如解析HTML,获得鼠标点击的事件回调等等。
常见的 宏任务 有 setTimeout、MessageChannel、postMessage、setImmediate;
vue进行DOM更新内部也是调用nextTick来做异队伍列控制。而当我们本身调用nextTick的时候,它就在更新DOM的那个micro
task后追加了我们本身的回调函数,从而确保我们的代码在DOM更新后执行。
setTimeout是宏任务:只是耽误执行,在耽误执行的方法里,DOM有大概会更新也有大概没有更新。
异步更新
也就是说我们在设置this.msg = 'some thing'的时候,Vue并没有马上去更新DOM数据,而是将这个操作放进一个队列中;如果我们重复执行的话,队列还会进行去重操作;等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿出来处理。
这样做重要是为了提拔性能,因为如果在主线程中更新DOM,循环100次就要更新100次DOM;但是如果等事件循环完成之后更新DOM,只须要更新1次。
为了在数据更新操作之后操作DOM,我们可以在数据变化之后立即使用Vue.nextTick(callback);这样回调函数会在DOM更新完成后被调用,就可以拿到最新的DOM元素了。
$nextTick源码分析
https://i-blog.csdnimg.cn/blog_migrate/de818d12f9738feb10c1377908de1ebd.webp?x-image-process=image/format,png
我们首先找到 $ nextTick这个函数定义的地方,看看它具体做了什么操作;看到它在外层定义了三个变量,有一个变量看名字就很熟悉:callbacks,就是我们上面说的队列;
在$ nextTick的外层定义变量就形成了一个闭包,所以我们每次调用$nextTick的过程其实就是在向callbacks新增回调函数的过程。
callbacks新增回调函数后又执行了timerFunc函数,pending用来标识同一个时间只能执行一次。那么这个timerFunc函数是做什么用的呢,我们继续来看代码:
https://i-blog.csdnimg.cn/blog_migrate/3a1bfe047683333719a73ab86805f9e6.webp?x-image-process=image/format,png
这里出现了好几个isNative函数,这是用来判断所传参数是否在当前环境原生就支持;例如某些浏览器不支持Promise,虽然我们使用了垫片(polify),但是isNative(Promise)照旧会返回false。
可以看出这边代码其实是做了四个判断,对当前环境进行不绝的降级处理,尝试使用原生的Promise.then、MutationObserver和setImmediate,上述三个都不支持最后使用setTimeout;降级处理的目的都是将flushCallbacks函数放入微任务(判断1和判断2)大概宏任务(判断3和判断4),等待下一次事件循环时来执行。MutationObserver是Html5的一个新特性,用来监听目标DOM结构是否改变,也就是代码中新建的textNode;如果改变了就执行MutationObserver构造函数中的回调函数,不过是它是在微任务中执行的。
那么最终我们顺藤摸瓜找到了最终的大boss:flushCallbacks;$nextTick不顾一切的要把它放入微任务大概宏任务中去执行,它毕竟是何方神圣呢?让我们来一睹它的真容:
https://i-blog.csdnimg.cn/blog_migrate/ce294a65982feea29fa6b0e1ad4e8f6e.jpeg
原来以为有多复杂的flushCallbacks,居然不过短短的8行。它所做的事变也非常的简单,把callbacks数组复制一份,然后把callbacks置为空,最后把复制出来的数组中的每个函数依次执行一遍;所以它的作用仅仅是用来执行callbacks中的回调函数。
总结
到这里,团体$nextTick的代码都分析完毕了,总结一下它的流程
[*]把回调函数放入callbacks等待执行
[*]将执行函数放到微任务大概宏任务中
[*]事件循环到了微任务大概宏任务,执行函数依次执行callbacks中的回调
再回到我们开头说的setTimeout,可以看出来
$ nextTick是对setTimeout进行了多种兼容性的处理,宽泛的也可以明白为将回调函数放入setTimeout中执行;不过$ nextTick优先放入微任务执行,而setTimeout是宏任务,
因此$nextTick一样平常环境下总是先于setTimeout执行,我们可以在浏览器中尝试一下:
https://i-blog.csdnimg.cn/blog_migrate/7645e8f9a380a48cc2622c4863eb7436.jpeg
**
一个例子
:**
首次事件循环:
点击用户界面:
https://i-blog.csdnimg.cn/blog_migrate/8ddcdad437c4d24ed391c8beb945c93d.png
使用原生的 Promise.then(微任务):
https://i-blog.csdnimg.cn/blog_migrate/080dd6a3b6fbab2dd45f9988f30a8b98.png
进入Vue内部方法:
https://i-blog.csdnimg.cn/blog_migrate/fd0502baa4f6110aeb46a17e10e58516.png
按f11步进,把回调函数放入callbacks等待执行:
https://i-blog.csdnimg.cn/blog_migrate/bf22553645ff5eef8f81445043fefb85.png
pending用来标识同一个时间只能执行一次:
https://i-blog.csdnimg.cn/blog_migrate/951a1a045e09cb6ab94ccb41b881154c.png
执行timerFunc函数:
https://i-blog.csdnimg.cn/blog_migrate/41b9a4aac9f103e6f6242cf479f75115.png
在下一个的事件循环“tick”中(主线程的执行过程就是一个 tick), Vue 刷新队列并执行任务队列
(已去重的) 工作。
进入下个一循环:
https://i-blog.csdnimg.cn/blog_migrate/b60c4f48bda0f8eee6669dc8b31cc7c2.png
此时按f11步进并不会再次进入function nextTick (cb, ctx) { },而是如下图所示:
https://i-blog.csdnimg.cn/blog_migrate/a561251d278e2d265824487f4a30248c.png
再按f11步进
此时任务队列只有一个任务:
https://i-blog.csdnimg.cn/blog_migrate/e8399334f52b56570da80fe99a588d35.png
执行队列任务:
https://i-blog.csdnimg.cn/blog_migrate/8233d116f40360108c6a4614426d2e7e.png
执行业务逻辑:
https://i-blog.csdnimg.cn/blog_migrate/4566e6af4f5fd316ce66cf8ce98ed0fa.png
参考于:
https://zhuanlan.zhihu.com/p/410608584
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]