日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

[Vue源码分析] Virtual DOM

發布時間:2023/12/31 vue 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [Vue源码分析] Virtual DOM 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近小組有個關于vue virtual dom的分享會,提前準備一下…

讀前須知:

本文章涉及源碼版本為Vue 2.5.2,文中涉及到源碼部分,解釋直接寫在源碼中(中文部分為本人添加),截圖盡量放完整代碼,但由于截圖的大小限制,部分只能放關鍵截圖,建議結合源碼閱讀此文章。
附上尤雨溪大佬的github vue倉庫地址:https://github.com/vuejs/vue

為什么使用virtual dom

做一件事一般都先問問為什么,那么為什么使用virtual dom?真正的 DOM 元素是非常龐大的,因為瀏覽器的標準把 DOM 設計的很復雜。如果頻繁地操作 DOM ,會產生一定的性能問題。
舉個例子:創建一個header標簽,并打印dom的描述信息:

可以看到,輸出的信息雖然挺多看不懂,但可以看得出來這是很龐大的一段內容。

相比之下:Virtual DOM 用一個原生的 JS 對象去描述一個 DOM 節點,所以它比創建一個 DOM 的代價要小很多。

virtual dom 的數據結構

virtual dom的定義在src/core/vdom/vnode.js 中,是一個VNode 類,定義如圖:

virtual dom映射到真實的 DOM 實際上要經歷 VNode 的 create、diff、patch 等過程。

virtual dom 的創建

VNode 的 create 是通過createElement 方法創建的,源碼位于src/core/vdom/create-elemenet.js中:

這個方式實際調用的是_createElement()方法,此方法同樣位于src/core/vdom/create-elemenet.js,為了方便閱讀,關鍵代碼解釋已經寫在源碼中,如圖

VNode children的VNode化

VNode化的意思是什么?Virtual DOM 實際上是一個樹狀結構,每一個 VNode 可能會有若干個子節點,這些子節點應該也是 VNode 的類型,由于上邊傳入的children是any類型的,因此需要將children轉為VNode類型。
這個過程用到兩個方法:normalizeChildren(children)、simpleNormalizeChildren(children)
這兩個方法的定義在src/core/vdom/helpers/normalzie-children.js中:

當一個 childrean 包含組件的時候,由于 functional component 函數式組件返回的是一個數組而不是一個根節點,所以需要通過Array.prototype.concat方法把整個 children 轉化為深度只有一層。

如果children是數組類型,normalizeChildren方法實際上調用的是normalizeArrayChildren方法,該方法在同文件下,源碼中已經添加關鍵代碼解釋,如下:

至此,我們知道了Virtual DOM是通過createElement方法創建的,但是createElement方法是什么時候調用的呢?
接下來我們將從主線上分析模板和數據如何渲染成最終的DOM。

new Vue()發生了那些事

我們都知道,vue項目中的入口是src下的main.js,如圖:

new Vue()發生了什么?接下來我們看一下Vue源碼,源碼位于src/core/instance/index.js中

可以看到,Vue實際上是一個類,源碼中判斷了this instanceof Vue,用于限制此類只能通過new關鍵字初始化,這個類看起來很簡單,只是調用了一個this._init()方法,這個方法做了什么?this_init()方法在src/core/instance/init.js中定義,主要是做了一堆初始化操作,關鍵源碼已添加中文注釋:

可以看到this_init()方法在做了一堆初始化操作后調用的是vm.$mount()方法,這個方法時怎么掛載實例的?接下來看一下這個方法的源碼,此方法的定義位于src/platform/web/entry-runtime-with-compiler.js中,關鍵代碼已經添加中文注釋:

逗了一圈,發現這個方法只是對options做了一些規范化的操作,最后調用的還是最初緩存下來的Vue原型上的$mount方法,那么這個方法是什么時候定義的?查找一下,發現此方法位于:src/platform/web/runtime/index.js:

以下為$mount中使用到的query方法的源碼,源碼位于src/platform/web/util/index.js中:


最后$mount方法調用的是mountComponent方法,此方法在src/core/instance/lifecycle.js中定義,關鍵代碼注釋已經添加到源碼中:

從上邊源碼可以看出,mountComponent最核心的方法有三個:vm._render()、vm._update()、new Watcher,Watcher暫時不作介紹。
接下來分析一下vm._render(),此方法在src/core/instance/render.js中定義,如下:

可以看到,這個方法返回的是虛擬dom,而虛擬dom的創建是調用vm.$createElement,vm.$createElement是什么?

可以看到,它正是文章開頭介紹到的createElement方法,createElement方法就是這時候調用的,兜了一大圈,終于解決了之前提到的createElement方法什么時候調用的問題。

vm._update()又是什么?
上邊介紹mountComponent時提到過,vm.update()的作用是把創建好的虛擬dom渲染成真實的dom,vm._update()源碼位于:src/core/instance/lifecycle.js,如圖:

從源碼中可以看到,_update方法在首次渲染和更新時都調用了vm.__patch__方法,只是傳入參數不一樣,可以想象得到vm.__patch__會有兩套邏輯,在這里先分析首次渲染時vm.__patch__做了什么。

vm.__patch__源碼位于src/platforms/web/runtime/index.js,如下:

patch應該就是要將虛擬dom轉換為真實dom的函數,但是noop是什么?從上邊的導入路徑可以得知此方法地址位于src/shared/util.js,如下:

可見,noop是一個空函數,也就是當前如果不是瀏覽器環境的話,vm.__patch__將會是一個空函數。可以得知,在非瀏覽器渲染(服務端渲染)中,不需要把虛擬dom轉換為真實dom。
接下來,重點看一下patch方法,源碼位于src/platforms/web/runtime/patch.js中:

這個函數的定義很簡單,就一句代碼,接受的是一個Function類型的返回值,該值由createPatchFunction返回,createPatchFunction接受一個對象,這個對象包含nodeOps以及modules兩個模塊。

createPatchFunction源碼位于src/core/vdom/patch.js中,這個方法很復雜,但總的來說就是定義了一系列的輔助方法,輔助方法那么多,不可能一開始就全部看一遍,應該先想想應該怎么看。把輔助方法都收縮起來,會發現這個函數最后會返回一個方法——patch,我們可以從這個方法開始閱讀父方法的源碼,從而跟蹤用到了哪些輔助方法,做了些什么。

接著看一些patch函數的具體內容,解釋請看源碼中的中文注釋:

接上圖:

做完了上邊的一波操作之后,調用createElm方法,這個方法位于同文件中,也就是輔助方法,解釋請看源碼中的中文注釋:

createChildren方法位于同文件中,是一個遍歷虛擬節點的操作:

最后會調用insert方法將dom插入父節點中。

insert方法中nodeOps.insertBefore以及nodeOps.appendChild實際調用的是原生的dom操作函數,源碼位于:web/runtime/node-ops.js

至此,從new Vue()到真實dom掛載的整個過程主線分析完畢,最后上個圖。

總結

以上是生活随笔為你收集整理的[Vue源码分析] Virtual DOM的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。