寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟
專(zhuān)注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧
研究基于 Vue版本 【2.5.17】
如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧
【Vue原理】NextTick - 源碼版 之 服務(wù)Vue
初次看的兄弟可以先看 【Vue原理】NextTick - 白話版 簡(jiǎn)單了解下NextTick
好的,今天,就來(lái)詳細(xì)記錄 Vue 和 nextTick 的那些事
nextTick 在 Vue 中,最重要的就是~~~
協(xié)助 Vue 進(jìn)行更新操作!
上篇文章
NextTick-源碼版之獨(dú)立自身
提到過(guò),nextTick 幫助 Vue 避免頻繁的更新,這里簡(jiǎn)單提一下,
每次修改數(shù)據(jù),都會(huì)觸發(fā)數(shù)據(jù)的依賴(lài)更新
也就是說(shuō)數(shù)據(jù)被修改的時(shí)候,會(huì)調(diào)用一遍【引用這個(gè)數(shù)據(jù)的實(shí)例】的更新函數(shù)
那么,按道理來(lái)說(shuō),修改3次,就應(yīng)該調(diào)用3遍更新函數(shù),但是實(shí)際上只會(huì)調(diào)用一遍
比如我們使用 watch 監(jiān)聽(tīng) data(data 便收集了 watch 的 watcher,監(jiān)聽(tīng)回調(diào)就是更新函數(shù))
結(jié)果就是只打印一次
至于依賴(lài)更新,可以看下面的文章
依賴(lài)更新 - 源碼版
其實(shí),修改數(shù)據(jù)能夠只更新一次,不止是 nextTick 起了作用,Vue 也做了其他處理,比如過(guò)濾實(shí)例,清空隊(duì)列等等,下面就來(lái)說(shuō)一下
一切先從【實(shí)例更新函數(shù)】開(kāi)始
第一個(gè)要說(shuō)的就是 watcher!每個(gè)實(shí)例都有一個(gè) watcher,然后 watcher 保存著實(shí)例的更新函數(shù)
每個(gè)實(shí)例都會(huì)通過(guò) new Vue 生成的,所以會(huì)有一個(gè)專(zhuān)屬的 watcher
更新函數(shù)被保存在 watcher.getter 上
function Vue(){ .... new Watcher(vm, 實(shí)例更新函數(shù)) } function Watcher(vm, expOrFn) { this.getter = expOrFn; }; Watcher.prototype.get = function() { this.getter.call(vm, vm); }; Watcher.prototype.update = function() { queueWatcher(this); }; Watcher.prototype.run = function() { this.get(); };
我們知道, Vue 的 data 是響應(yīng)式的,就是通過(guò) Object.defineProperty 設(shè)置 get 和 set
當(dāng)數(shù)據(jù)被修改的時(shí)候, set 函數(shù)被觸發(fā),函數(shù)內(nèi)部會(huì)通知所有的實(shí)例進(jìn)行更新(就是調(diào)用每個(gè)實(shí)例的 watcher.update 方法)
具體可以看這個(gè)
響應(yīng)式原理 - 白話版
依賴(lài)更新 - 源碼版
那么我們現(xiàn)在的重點(diǎn)就在 watcher.update 上了,看看上面的 Watcher 代碼
出現(xiàn)了一個(gè) queueWatcher 的東西
更新隊(duì)列速度看源碼!
var queue = []; var has = {}; var index = 0; var flushing = false; var waiting= false; function queueWatcher(watcher) { var id = watcher.id; // 如果是同一個(gè) Vue 實(shí)例,就不要重復(fù)添加了 if (has[id] == null) { // 這個(gè)實(shí)例已經(jīng)被標(biāo)記了 has[id] = true; // 如果沒(méi)有在運(yùn)行,那么直接放入隊(duì)列 if (!flushing) { queue.push(watcher); } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. var i = queue.length - 1; // 跳過(guò)所有比我大的 while (i > index && queue[i].id > watcher.id) { i--; } // 最后放在隊(duì)列中,一個(gè)比我老的 watcher 后面 queue.splice(i + 1, 0, watcher); } // 在 flushSchedulerQueue 執(zhí)行之后設(shè)置為 false if (!waiting) { waiting = true; nextTick(flushSchedulerQueue); } } }
先說(shuō)說(shuō)其中涉及的幾個(gè)變量
has是一個(gè)對(duì)象,用來(lái)過(guò)濾watcher。
當(dāng)這個(gè)watcher 已經(jīng)調(diào)用過(guò)更新函數(shù),那么就在 has 中標(biāo)記這個(gè) id
也就是,你同時(shí)間調(diào)用多次 watcher.update ,其實(shí)只有第一次調(diào)用有用,后面的都會(huì)被過(guò)濾掉
queue一個(gè)數(shù)組,watcher 更新隊(duì)列,存放需要更新的 watcher
flushSchedulerQueue
watcher 更新隊(duì)列執(zhí)行函數(shù),下面有講到
waiting為 true 表示已經(jīng)把 【watcher 更新隊(duì)列執(zhí)行函數(shù)】 注冊(cè)到宏微任務(wù)上了(或者說(shuō)存放進(jìn) callbacks 中)。
正在等待JS棧為空后,就可以執(zhí)行更新。直到所有watcher 更新完畢,才重置為 false
flushing為 true 表示 watcher 更新隊(duì)列正在執(zhí)行更新(就是開(kāi)始遍歷 watcher 隊(duì)列,逐個(gè)調(diào)用 watcher 更新了)
直到所有watcher 更新完畢,才重置為 false
queueWatcher 源碼不算很復(fù)雜,主要做兩件事
1、處理watcher 更新隊(duì)列 queue
2、注冊(cè) 【watcher 更新隊(duì)列 執(zhí)行函數(shù)】進(jìn)宏微任務(wù)
處理 watcher 更新隊(duì)列 queue當(dāng) flushing 為 false時(shí),表示 queue 還沒(méi)有開(kāi)始遍歷執(zhí)行,直接 push
當(dāng) flushing 為 true,表示 queue 已經(jīng)開(kāi)始遍歷,執(zhí)行其中的 watcher 更新了
然后,做了一個(gè)很特殊的插入操作(為了方便看,把上面的源碼截取了)
我還是沒(méi)有看懂這是為什么? 直到我看到了 flushSchedulerQueue 的 源碼!
因?yàn)樵?flushSchedulerQueue 執(zhí)行的時(shí)候(此時(shí)設(shè)置了 flushing = true),內(nèi)部把 queue 升序排列了!
所以在 flushing 的時(shí)候,queue已經(jīng)是有序狀態(tài),中途進(jìn)來(lái)的 watcher,當(dāng)然也要按順序來(lái)
所以,這一段的作用就是給 新來(lái)的 watcher 排序!
其中 index 表示 現(xiàn)在正遍歷到第幾個(gè) watcher(在 flushSchedulerQueue 中設(shè)置)
所以,也必然是排到已經(jīng)執(zhí)行過(guò)的 watcher 后面的(不然就遍歷不到這個(gè)watcher 了?。?/p> 注冊(cè) 【watcher 更新隊(duì)列 執(zhí)行函數(shù)】進(jìn)宏微任務(wù)
已經(jīng)講到 flushSchedulerQueue 了,他就是 注冊(cè)宏微任務(wù)的異步回調(diào)
直接存放進(jìn) 異步任務(wù)隊(duì)列 callbacks 中的
關(guān)于 nextTick 的 異步任務(wù)隊(duì)列 ,可以看
NextTick - 源碼版 之 獨(dú)立自身
接下來(lái),就看 flushSchedulerQueue
執(zhí)行更新隊(duì)列function flushSchedulerQueue() { flushing = true; var watcher; // 升序排列 queue.sort(function(a, b) { return a.id - b.id; }); for (index = 0; index < queue.length; index++) { watcher = queue[index]; has[watcher.id] = null; watcher.run(); } // 所有watcher 完成更新,重置狀態(tài) queue.length = 0; has = {}; waiting = flushing = false; }flushSchedulerQueue 的作用
1、升序排列 watcher 更新隊(duì)列
2、遍歷 watcher 更新隊(duì)列,然后逐個(gè)調(diào)用 watcher 更新
3、watcher 更新隊(duì)列執(zhí)行完畢,重置狀態(tài)
其他我都看得明白,唯獨(dú)我不懂一個(gè)問(wèn)題
為什么要把 queue 按照 watcher.id 升序排列??
首先,watcher.id 越大,表示這個(gè) watcher 越年輕,實(shí)例是越后面生成的
vue 的官方回答This ensures that:
Components are updated from parent to child. (because parent is always created before the child)
A component"s user watchers are run before its render watcher (because user watchers are created before the render watcher)
If a component is destroyed during a parent component"s watcher run, its watchers can be skipped.
我只挑一點(diǎn)
先更新父組件,再更新子組件(因?yàn)楦附M件比子組件先創(chuàng)建)為什么先更新父組件,再更新子組件,我還是想不通???
個(gè)人認(rèn)為,因?yàn)楦附M件跟子組件是有聯(lián)系的,什么聯(lián)系呢?
比如 props
當(dāng) 父組件傳給子組件的數(shù)據(jù)變化的時(shí)候,父組件需要把 變化后的數(shù)據(jù) 傳給 子組件,子組件才能知道數(shù)據(jù)變了
那么 子組件才能更新組件內(nèi)使用 props 的地方
所以,父組件必須先更新,把最新數(shù)據(jù)傳給 子組件,子組件再更新,此時(shí)才能獲取最新的數(shù)據(jù)
不然你子組件更新了,父組件再傳數(shù)據(jù)過(guò)來(lái),那就不會(huì)子組件就不會(huì)顯示最新的數(shù)據(jù)了啊
至于 父組件更新時(shí)怎么傳 數(shù)據(jù)給子組件的?
【Vue原理】Props - 白話版
最后,走個(gè)簡(jiǎn)單流程數(shù)據(jù)變化,通知 watcher 更新,watcher.update
queueWatcher 把 watcher 添加進(jìn) 【queue 更新隊(duì)列】
把 flushSchedulerQueue 注冊(cè)進(jìn)宏微任務(wù)
JS 主棧執(zhí)行完,開(kāi)始執(zhí)行異步代碼
flushSchedulerQueue 遍歷 queue ,逐個(gè)調(diào)用 watcher 更新
完成更新
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/110259.html
摘要:盡量把所有異步代碼放在一個(gè)宏微任務(wù)中,減少消耗加快異步代碼的執(zhí)行。我們知道,如果一個(gè)異步代碼就注冊(cè)一個(gè)宏微任務(wù)的話,那么執(zhí)行完全部異步代碼肯定慢很多避免頻繁地更新。中就算我們一次性修改多次數(shù)據(jù),頁(yè)面還是只會(huì)更新一次。 寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟專(zhuān)注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5...
摘要:這么講,有點(diǎn)籠統(tǒng),準(zhǔn)確地說(shuō),應(yīng)該是事件回調(diào)執(zhí)行過(guò)程中,在主線程為空之后,異步代碼執(zhí)行之前,所有通過(guò)注冊(cè)的異步代碼都是用宏任務(wù)。 寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟專(zhuān)注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【...
摘要:通常會(huì)做很多判斷來(lái)選擇存在的類(lèi)型,比如判斷等是否存在,而選擇他為微任務(wù)類(lèi)型但是可能宏微任務(wù)最后都是,因?yàn)樗潜J丶嫒萏幚怼? 寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟專(zhuān)注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【V...
摘要:哪吒別人的看法都是狗屁,你是誰(shuí)只有你自己說(shuō)了才算,這是爹教我的道理。哪吒去他個(gè)鳥(niǎo)命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰(shuí)和你做朋友太乙真人人是否能夠改變命運(yùn),我不曉得。我只曉得,不認(rèn)命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
摘要:項(xiàng)目地址和的區(qū)別其實(shí)和最大的區(qū)別就是多了一個(gè)虛擬,其他的區(qū)別都是很小的。 項(xiàng)目地址 Vue1和Vue2的區(qū)別 其實(shí)Vue1和Vue2最大的區(qū)別就是Vue2多了一個(gè)虛擬DOM,其他的區(qū)別都是很小的。所以理解了Vue1的源碼,就相當(dāng)于理解了Vue2,中間差了一個(gè)虛擬DOM的Diff算法 文檔 數(shù)據(jù)雙向綁定 Vue主流程走向 組件 nextTick異步更新 MVVM 先來(lái)科普一下MVVM...
閱讀 2668·2023-04-26 00:42
閱讀 2815·2021-09-24 10:34
閱讀 3826·2021-09-24 09:48
閱讀 4163·2021-09-03 10:28
閱讀 2584·2019-08-30 15:56
閱讀 2780·2019-08-30 15:55
閱讀 3271·2019-08-29 12:46
閱讀 2251·2019-08-28 17:52