摘要:實際上,我在看代碼的過程中順手提交了這個,作者眼明手快,當天就進行了修復,現在最新的代碼里已經不是這個樣子了而且狀態機標識由字符串換成了數字常量,解析更準確的同時執行效率也會更高。
最近饒有興致的又把最新版?Vue.js?的源碼學習了一下,覺得真心不錯,個人覺得 Vue.js 的代碼非常之優雅而且精辟,作者本身可能無 (bu) 意 (xie) 提及這些。那么,就讓我來吧:)
程序結構梳理Vue.js 是一個非常典型的 MVVM 的程序結構,整個程序從最上層大概分為
全局設計:包括全局接口、默認選項等
vm 實例設計:包括接口設計 (vm 原型)、實例初始化過程設計 (vm 構造函數)
這里面大部分內容可以直接跟 Vue.js 的官方 API 參考文檔對應起來,但文檔里面沒有且值得一提的是構造函數的設計,下面是我摘出的構造函數最核心的工作內容。
整個實例初始化的過程中,重中之重就是把數據 (Model) 和視圖 (View) 建立起關聯關系。Vue.js 和諸多 MVVM 的思路是類似的,主要做了三件事:
通過 observer 對 data 進行了監聽,并且提供訂閱某個數據項的變化的能力
把 template 解析成一段 document fragment,然后解析其中的 directive,得到每一個 directive 所依賴的數據項及其更新方法。比如?v-text="message"?被解析之后 (這里僅作示意,實際程序邏輯會更嚴謹而復雜):
所依賴的數據項?this.$data.message,以及
相應的視圖更新方法?node.textContent = this.$data.message
通過 watcher 把上述兩部分結合起來,即把 directive 中的數據依賴訂閱在對應數據的 observer 上,這樣當數據變化的時候,就會觸發 observer,進而觸發相關依賴對應的視圖更新方法,最后達到模板原本的關聯效果。
所以整個 vm 的核心,就是如何實現 observer, directive (parser), watcher 這三樣東西
文件結構梳理Vue.js 源代碼都存放在項目的?src?目錄中,我們主要關注一下這個目錄 (事實上?test/unit/specs?目錄也值得一看,它是對應著每個源文件的測試用例)。
src?目錄下有多個并列的文件夾,每個文件夾都是一部分獨立而完整的程序設計。不過在我看來,這些目錄之前也是有更立體的關系的:
首先是?api/*?目錄,這幾乎是最“上層”的接口封裝,實際的實現都埋在了其它文件夾里
然后是?instance/init.js,如果大家希望自頂向下了解所有 Vue.js 的工作原理的話,建議從這個文件開始看起
instance/scope.js:數據初始化,相關的子程序 (目錄) 有?observer/*、watcher.js、batcher.js,而?observer/dep.js?又是數據觀察和視圖依賴相關聯的關鍵
instance/compile.js:視圖初始化,相關的子程序 (目錄) 有?compiler/*、directive.js、parsers/*
其它核心要素:directives/*、element-directives/*、filters/*、transition/*
當然還有?util/*?目錄,工具方法集合,其實還有一個類似的?cache.js
最后是?config.js?默認配置項
篇幅有限,如果大家有意“通讀” Vue.js 的話,個人建議順著上面的整體介紹來閱讀賞析。
接下來是一些自己覺得值得一提的代碼細節
一些不容錯過的代碼/程序細節 this._eventsCount?是什么?一開始看?instance/init.js?的時候,我立刻注意到一個細節,就是?this._eventsCount = {}?這句,后面還有注釋
for $broadcast optimization
非常好奇,然后帶著疑問繼續看了下去,直到看到?api/events.js?中?$broadcast?方法的實現,才知道這是為了避免不必要的深度遍歷:在有廣播事件到來時,如果當前 vm 的?_eventsCount?為?0,則不必向其子 vm 繼續傳播該事件。而且這個文件稍后也有?_eventsCount?計數的實現方式。
這是一種很巧妙同時也可以在很多地方運用的性能優化方法。
數據更新的 diff 機制前陣子有很多關于視圖更新效率的討論,我猜主要是因為 virtual dom 這個概念的提出而導致的吧。這次我詳細看了一下 Vue.js 的相關實現原理。
實際上,視圖更新效率的焦點問題主要在于大列表的更新和深層數據更新這兩方面,而被熱烈討論的主要是前者 (后者是因為需求小還是沒爭議我就不得而知了)。所以這里著重介紹一下?directives/repeat.js?里對于列表更新的相關代碼。
首先?diff(data, oldVms)?這個函數的注釋對整個比對更新機制做了個簡要的闡述,大概意思是先比較新舊兩個列表的 vm 的數據的狀態,然后差量更新 DOM。
第一步:遍歷新列表里的每一項,如果該項的 vm 之前就存在,則打一個?_reused?的標 (這個字段我一開始看?init.js?的時候也是困惑的…… 看到這里才明白意思),如果不存在對應的 vm,則創建一個新的。
第二步:遍歷舊列表里的每一項,如果?_reused?的標沒有被打上,則說明新列表里已經沒有它了,就地銷毀該 vm。
第三步:整理新的 vm 在視圖里的順序,同時還原之前打上的?_reused?標。就此列表更新完成。
順帶提一句 Vue.js 的元素過渡動畫處理 (v-transition) 也設計得非常巧妙,感興趣的自己看吧,就不展開介紹了
組件的?[keep-alive]?特性Vue.js 為其組件設計了一個?[keep-alive]?的特性,如果這個特性存在,那么在組件被重復創建的時候,會通過緩存機制快速創建組件,以提升視圖更新的性能。代碼在?directives/component.js。
數據監聽機制如何監聽某一個對象屬性的變化呢?我們很容易想到?Object.defineProperty?這個 API,為此屬性設計一個特殊的 getter/setter,然后在 setter 里觸發一個函數,就可以達到監聽的效果。
不過數組可能會有點麻煩,Vue.js 采取的是對幾乎每一個可能改變數據的方法進行 prototype 更改:
但這個策略主要面臨兩個問題:
無法監聽數據的?length,導致?arr.length?這樣的數據改變無法被監聽
通過角標更改數據,即類似?arr[2] = 1?這樣的賦值操作,也無法被監聽
為此 Vue.js 在文檔中明確提示不建議直接角標修改數據
同時 Vue.js 提供了兩個額外的“糖方法”?$set?和?$remove?來彌補這方面限制帶來的不便。整體上看這是個取舍有度的設計。我個人之前在設計數據綁定庫的時候也采取了類似的設計 (一個半途而廢的內部項目就不具體獻丑了),所以比較認同也有共鳴。
path 解析器的狀態機設計首先要說?parsers?文件夾里有各種“財寶”等著大家挖掘!認真看一看一定不會后悔的
parsers/path.js?主要的職責是可以把一個 JSON 數據里的某一個“路徑”下的數據取出來,比如:
var path = "a.b[1].v" var obj = { a: { b: [ {v: 1}, {v: 2}, {v: 3} ] } } parse(obj, path) // 2
所以對?path?字符串的解析成為了它的關鍵。Vue.js 是通過狀態機管理來實現對路徑的解析的:
咋一看很頭大,不過如果再稍微梳理一下:
也許看得更清楚一點了,當然也能發現其中有一點小問題,就是源代碼中?inIdent?這個狀態是具有二義性的,它對應到了圖中的三個地方,即?in ident?和兩個?in (quoted) ident。
實際上,我在看代碼的過程中順手提交了這個 bug,作者眼明手快,當天就進行了修復,現在最新的代碼里已經不是這個樣子了:
而且狀態機標識由字符串換成了數字常量,解析更準確的同時執行效率也會更高。
一點自己的思考首先是視圖的解析過程,Vue.js 的策略是把 element 或 template string 先統一轉換成 document fragment,然后再分解和解析其中的子組件和 directives。我覺得這里有一定的性能優化空間,畢竟 DOM 操作相比之余純 JavaScript 運算還是會慢一些。
然后是基于移動端的思考,Vue.js 雖確實已經非常非常小巧了 (min+gzip 之后約 22 kb),但它是否可以更小,繼續抽象出常用的核心功能,同時更快速,也是個值得思考的問題。
第三我非常喜歡通過 Vue.js 進行模塊化開發的模式,Vue 是否也可以借助類似 web components + virtual dom 的形態把這樣的開發模式帶到更多的領域,也是件很有意義的事情。
總結Vue.js 里的代碼細節還不僅于此,比如:
cache.js?里的緩存機制設計和場景運用 (如在?parsers/path.js?中)
parsers/template.js?里的?cloneNode?方法重寫和對 HTML 自動補全機制的兼容
在開發和生產環境分別用注釋結點和不可見文本結點作為視圖的“占位符”等等
自己也在閱讀代碼,了解 Vue.js 的同時學到了很多東西,同時我覺得代碼實現只是 Vue.js 優秀的要素之一,整體的程序設計、API 設計、細節的取舍、項目的工程考量都非常棒!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/40235.html
摘要:實際上,我在看代碼的過程中順手提交了這個,作者眼明手快,當天就進行了修復,現在最新的代碼里已經不是這個樣子了而且狀態機標識由字符串換成了數字常量,解析更準確的同時執行效率也會更高。 最近饒有興致的又把最新版?Vue.js?的源碼學習了一下,覺得真心不錯,個人覺得 Vue.js 的代碼非常之優雅而且精辟,作者本身可能無 (bu) 意 (xie) 提及這些。那么,就讓我來吧:) 程序結構梳...
摘要:有興趣的同學可以查看之前發布的文章學習系列一學習實踐筆記附學習系列二學習實踐筆記附學習系列三和網絡傳輸相關知識的學習實踐學習系列四打包工具的使用學習系列五從來聊聊學習系列項目地址項目暫時有點亂,之后會進行整理優化。 上次學習了vue-router的使用,讓我能夠在各個頁面間切換,將頁面搭建了起來。這次則要學習vue的狀態管理模式——vuex。它類似于redux來應用的全局狀態。 注:本...
閱讀 3066·2023-04-25 18:54
閱讀 2591·2021-11-02 14:40
閱讀 3176·2021-09-23 11:58
閱讀 2424·2019-08-30 13:50
閱讀 1231·2019-08-29 12:46
閱讀 3117·2019-08-28 17:51
閱讀 679·2019-08-26 11:47
閱讀 897·2019-08-23 16:17