寫在前面
因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js源碼,并做了總結與輸出。
文章的原地址:https://github.com/answershuto/learnVue。
在學習過程中,為Vue加上了中文的注釋https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以對其他想學習Vue源碼的小伙伴有所幫助。
可能會有理解存在偏差的地方,歡迎提issue指出,共同學習,共同進步。
操作DOM
在使用vue.js的時候,有時候因為一些特定的業務場景,不得不去操作DOM,比如這樣:
<template> <div> <div ref="test">{{test}}</div> <button @click="handleClick">tet</button> </div></template>export default { data () { return { test: 'begin' }; }, methods () { handleClick () { this.test = 'end'; console.log(this.$refs.test.innerText);//打印“begin” } }}打印的結果是begin,為什么我們明明已經將test設置成了“end”,獲取真實DOM節點的innerText卻沒有得到我們預期中的“end”,而是得到之前的值“begin”呢?
Watcher隊列
帶著疑問,我們找到了Vue.js源碼的Watch實現。當某個響應式數據發生變化的時候,它的setter函數會通知閉包中的Dep,Dep則會調用它管理的所有Watch對象。觸發Watch對象的update實現。我們來看一下update的實現。
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { /*同步則執行run直接渲染視圖*/ this.run() } else { /*異步推送到觀察者隊列中,下一個tick時調用。*/ queueWatcher(this) }}我們發現Vue.js默認是使用異步執行DOM更新。
當異步執行update的時候,會調用queueWatcher函數。
/*將一個觀察者對象push進觀察者隊列,在隊列中已經存在相同的id則該觀察者對象將被跳過,除非它是在隊列被刷新時推送*/export function queueWatcher (watcher: Watcher) { /*獲取watcher的id*/ const id = watcher.id /*檢驗id是否存在,已經存在則直接跳過,不存在則標記哈希表has,用于下次檢驗*/ if (has[id] == null) { has[id] = true if (!flushing) { /*如果沒有flush掉,直接push到隊列中即可*/ 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. let i = queue.length - 1 while (i >= 0 && queue[i].id > watcher.id) { i-- } queue.splice(Math.max(i, index) + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } }}查看queueWatcher的源碼我們發現,Watch對象并不是立即更新視圖,而是被push進了一個隊列queue,此時狀態處于waiting的狀態,這時候會繼續會有Watch對象被push進這個隊列queue,等待下一個tick時,這些Watch對象才會被遍歷取出,更新視圖。同時,id重復的Watcher不會被多次加入到queue中去,因為在最終渲染時,我們只需要關心數據的最終結果。
新聞熱點
疑難解答
圖片精選