最近饒有興致的又把最新版 Vue.js 的源碼學習了一下,以爲真心不錯,我的以爲 Vue.js 的代碼很是之優雅並且精闢,做者自己可能無 (bu) 意 (xie) 說起這些。那麼,就讓我來吧:)vue
Vue.js 是一個很是典型的 MVVM 的程序結構,整個程序從最上層大概分爲node
這裏面大部份內容能夠直接跟 Vue.js 的官方 API 參考文檔對應起來,但文檔裏面沒有且值得一提的是構造函數的設計,下面是我摘出的構造函數最核心的工做內容。git
整個實例初始化的過程當中,重中之重就是把數據 (Model) 和視圖 (View) 創建起關聯關係。Vue.js 和諸多 MVVM 的思路是相似的,主要作了三件事:github
把 template 解析成一段 document fragment,而後解析其中的 directive,獲得每個 directive 所依賴的數據項及其更新方法。好比 v-text="message"
被解析以後 (這裏僅做示意,實際程序邏輯會更嚴謹而複雜):web
this.$data.message
,以及node.textContent = this.$data.message
因此整個 vm 的核心,就是如何實現 observer, directive (parser), watcher 這三樣東西api
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 的話,我的建議順着上面的總體介紹來閱讀賞析。dom
接下來是一些本身以爲值得一提的代碼細節
this._eventsCount
是什麼?一開始看 instance/init.js
的時候,我馬上注意到一個細節,就是 this._eventsCount = {}
這句,後面還有註釋
for $broadcast optimization
很是好奇,而後帶着疑問繼續看了下去,直到看到 api/events.js
中 $broadcast
方法的實現,才知道這是爲了不沒必要要的深度遍歷:在有廣播事件到來時,若是當前 vm 的 _eventsCount
爲 0
,則沒必要向其子 vm 繼續傳播該事件。並且這個文件稍後也有 _eventsCount
計數的實現方式。
這是一種很巧妙同時也能夠在不少地方運用的性能優化方法。
前陣子有不少關於視圖更新效率的討論,我猜主要是由於 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
來彌補這方面限制帶來的不便。總體上看這是個取捨有度的設計。我我的以前在設計數據綁定庫的時候也採起了相似的設計 (一個半途而廢的內部項目就不具體獻醜了),因此比較認同也有共鳴。
首先要說 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 設計、細節的取捨、項目的工程考量都很是棒!