国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

vue源碼閱讀之數據渲染過程

AlphaGooo / 3317人閱讀

摘要:圖在中應用三數據渲染過程數據綁定實現邏輯本節正式分析從到數據渲染到頁面的過程,在中定義了一個的構造函數。

一、概述 vue已是目前國內前端web端三分天下之一,也是工作中主要技術棧之一。在日常使用中知其然也好奇著所以然,因此嘗試閱讀vue源碼并進行總結。本文旨在梳理初始化頁面時data中的數據是如何渲染到頁面上的。本文將帶著這個疑問一點點“追究”vue的"思路"。總體來說vue模版渲染大致流程如圖1所示:

圖1:vue模版渲染流程

從圖中可以看到模版渲染過程經歷了數據處理(initState)、模版編譯(compileToFunctions)生成渲染函數(render)、render函數生成虛擬dom、虛擬dom映射為真實DOM (patch)掛載到頁面這幾個過程。上述幾個函數在數據渲染過程中起到了關鍵作用。因此本文就從這幾個函數出發,深入研究vue數據渲染到頁面上的原理。

二、什么是Virtual DOM "); vue利用虛擬DOM技術來提高頁面渲染和更新的速度。因此在正式分析數據渲染過程之前,有必要先了解一下什么是Virtual DOM,以及Virtual DOM的優勢。 2.1 virtual dom 產生的原因 Virtual DOM 產生的前提是瀏覽器中的 DOM操作 是很“昂貴"的,為了更直觀的感受,我把一個簡單的 div 元素的屬性都打印出來,如圖2所示:

圖2:dom元素屬性

可以看到,瀏覽器把 DOM 設計的非常復雜、非常龐大。在瀏覽器當中,dom的實現和ECMAScript的實現是分離的。因此當我們頻繁的去做 DOM 更新,就是頻繁通過js代碼調用dom的接口,就相當于兩個相互獨立的模塊發生了交互。這樣,相比于在同一個模塊當中互相調用,這種跨模塊的調用它的性能損耗是非常高的。并且dom操作導致瀏覽器的重繪(repaint)和重排(reflow)會帶來更大的性能損耗。只要在渲染過程中進行一次 DOM 更新,整個渲染流程都會重做一遍。如圖3所示:

圖3:瀏覽器渲染流程

而 Virtual DOM 就是用一個原生的 JS 對象去描述一個 DOM 節點,所以它比創建一個 DOM 的代價要小很多。圖4所示為vitrual dom結構:

圖4:Virtual DOM實例

上述的virtual dom最后會生成真實的dom結構。如圖5所示:

圖5:Virtual DOM映射成真實dom

在 Vue.js 中,Virtual DOM 是用 VNode 這么一個 Class 去描述, 是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標簽名、數據、子節點、鍵值等。由于 VNode 只是用來映射到真實 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常輕量和簡單的。當數據發生改變時是一次性渲染到頁面,同時vue內部通過diff算法減少頁面的重繪和重排,從而提高了頁面渲染的速度。 2.2 Virtual DOM 主要思想 VirtualDOM的主要思想就是模擬DOM的樹狀結構,在內存中創建保存映射DOM信息的節點數據,在由于交互等因素需要視圖更新時,先通過對節點數據進行diff得到差異結果后,再一次性對DOM進行批量更新操作,這就好比在內存中創建了一個平行世界,瀏覽器中DOM樹的每一個節點與屬性數據都在這個平行世界中存在著另一個版本的虛擬DOM樹,所有復雜曲折的更新邏輯都在平行世界中的VirtualDOM處理完成,只將最終的更新結果發送給瀏覽器中的DOM樹執行,這樣就避免了冗余瑣碎的DOM樹操作負擔,進而有效提高了性能。基于 Virtual DOM 的數據更新與UI同步機制:初始渲染時,首先將數據渲染為 Virtual DOM,然后由 Virtual DOM 生成 DOM。

圖6:vitual dom生成dom示意圖

比較兩個 DOM 樹的差異是 Virtual DOM 算法最核心的部分,這也是所謂的 Virtual DOM 的diff 算法。diff算法的核心是比較只會在同層級進行, 不會跨層級比較。而不像逐層逐層搜索遍歷的方式,時間復雜度將會達到 O(n^3)的級別,代價非常高,而只比較同層級的方式時間復雜度可以降低到O(n)。可以用一張圖示意:

圖7:virtual dom更新示意圖

數據更新時,渲染得到新的 Virtual DOM,與上一次的 Virtual DOM 進行 diff,得到所有需要在 DOM 上進行的變更,然后在 patch 過程中應用到 DOM 上實現UI的同步更新。因此 Virtual DOM算法主要包括這幾步: 初始化視圖的時候,用原生JS對象表示DOM樹,生成一個對象樹,然后根據這個對象樹來生成一個真正的DOM樹,插入到文檔中。 當狀態更新的時候,重新生成一個對象樹,將新舊兩個對象樹做對比,記錄差異。 把記錄的差異應用到第一步生成的真正的DOM樹上,視圖更新完成。 在vue中也是采用了virtual dom的diff算法如下圖,具體diff算法過程在patch函數執行。

圖8:vitrual dom在vue中應用

三、數據渲染過程 3.1 數據綁定實現邏輯 ---- initState 本節正式分析從new vue()到數據渲染到頁面的過程,在src/core/instance/index.js 中定義了一個Vue的構造函數。當執行new Vue(options)時就會執行this._init(options)這個函數。

圖9: vue構造函數

以一個簡單的實例開始。定義如下模版和js代碼:

圖10: 實例

在調用Vue構造函數時候傳入el和data。此時傳入this._init(options)中的options = { el: "#app", data: { message: "我是一條信息"}}。在_init函數中會執行一系列初始化操作:初始化生命周期、初始化事件、初始化數據等。其中初始化數據是本節關心的內容,跟數據綁定關聯最大的是 initState。因此我們現在重點研究一下initState(vm)。入口是src/core/instance/state.js。

圖11: initState函數

傳入data后會調用initData(vm)函數,對data進行處理。initData函數將當前傳入的data賦值給vm._data。vm是當前vue實例。然后會執行代理函數proxy。

圖12: proxy函數

proxy函數的原理是通過 Object.defineProperty()函數在實例對象vm上定義與data數據字段同名的訪問器屬性,并且這些屬性代理的值是vm._data上對應屬性的值。當我們訪問vm[key] 就會通過get方法去訪問vm[sourceKey][key] 即vm._data[key]。也就是說vm.message 就會去訪問vm._data.message也就是vm.data.message。所以 this.message就是this._data.message,只不過_data是vue內部使用的。這也就是我們通過this.message就能訪問到data里面的message對應的值。即"我是一條信息‘。

圖13: 數據綁定過程

同理,當我們設置一個屬性值時會通過set方法去設置vm._data[key]的值。到這一步我們已經可以獲取傳入的data里面的數據了。那么message是如何渲染到頁面視圖層的呢?下一節就深入研究vue的掛載過程。 3.2 渲染函數 ---- render 3.2.1 對el的處理 _init函數執行完上述的初始化過程后會判斷是否傳入el,若傳入就執行掛載函數$mount。

圖14: $mount函數

$mount首先會通過query(el)函數對傳入的el如下處理:

圖15: query函數

如果el是字符串則通過document.querySelector(el)方法查找該字符串對應的dom元素。若沒找到,則通過方document.createElement("div")方法動態創建一個div,若傳入的el是dom元素。那么就返回該元素。最終都是用一個dom元素來掛載實例。值得注意的是Vue 不能掛載在 body、html 這樣的根節點上。原因是vue在掛載是會將對應的dom對象替換成新的div,但body和html是不適合替換的。如果el是body或者html就會拋出警告,這也就是為什么平時我們通常會用“#app“或者“div“掛載實例的原因。 3.2.2 模版內容提取 判斷是否傳入render函數,如果渲染函數存在會直接調用運行時版 $mount 函數,我們知道運行時版 $mount 僅有兩句代碼,且真正的掛載是通過調用 mountComponent 函數完成的,所以可想而知 mountComponent 完成掛載所需的必要條件就是:提供渲染函數給 mountComponent。 render分為用戶手寫和模版編譯兩種形式,手寫render函數格式如下。render函數的好處是,不會有在html中直接使用插值時,在實際掛載前出現{{message}}這樣的內容。只有在render函數執行完成后才會把message替換到頁面上去。這樣會有更好的體驗。下面的render函數最終會渲染成一個id為app1,內容為‘我是一條信息"的div元素,替換掉之前的掛載節點el。所以這也是為什么不使用body或者html進行掛載的原因,因為我們不能覆蓋到整個body或者html。

圖16: 手寫render函數

在 Vue 2.0 版本中,所有 Vue 的組件的渲染最終都需要 render 方法,無論我們是用單文件 .vue 方式開發組件,還是寫了 el 或者 template 屬性,最終都會轉換成 render 方法。我們的例子中沒有傳入render函數,因此需要來研究一下在沒有傳入render函數的情況下如何通過模版編譯成render函數。

圖17: template處理

(1)如果存在template并且傳入的template是字符串,且以#開頭,如下:

圖18: template第一種形式

那么就找到id為#app的元素innerHtml();獲取該innerHtml()是在idToTemplate函數中進行的。也是調用query函數獲取對應的dom元素的innerHTML。并保存在template變量中。

圖19: idToTemplate函數

(2)如果傳入的template是dom節點。如下:那么直接將該節點的innerHtml賦值給template 變量。

圖20: template第二種形式

(3)如果options中沒有template,但是有el,那么就獲取el對應元素的所有內容。

圖21: el元素內容提取

getOuterHTML 函數的源碼如下:

圖22: getOuterHTML函數

它接收一個 DOM 元素作為參數,并返回該元素的 outerHTML。該函數首先判斷了 el.outerHTML 是否存在,也就是說一個元素的 outerHTML 屬性未必存在,實際上在 IE9-11 中 SVG 標簽元素是沒有 innerHTML 和 outerHTML 這兩個屬性的,解決這個問題的方案很簡單,可以把 SVG 元素放到一個新創建的 div 元素中,這樣新 div 元素的 innerHTML 屬性的值就等價于 SVG標簽 outerHTML 的值。

我們最初提供的實例子符合第(3)類情況。經過以上邏輯的處理之后,理想狀態下此時 template 變量應該如下所示的一個模板字符串,將用于渲染函數的生成。

圖22: 提取的模版字符串

模版提取過程如圖23所示:

圖23: 模版提取

3.2.3 模版編譯成render函數 拿到模版內容后就會調用compileToFunctions函數將模版編譯成render函數。下面看下compileToFunctions生成render方法的具體實現。編譯主要有三個過程:

圖23: 編譯過程

1.解析模版字符串生成AST ---- parse(template.trim(), options)。 parse 會用正則等方式解析 template模板中的指令、class、style等數據,形成AST樹。AST是一種用Javascript對象的形式來描述整個模版。parse會調用parseHTML函數,由于 parseHTML 的邏輯也非常復雜,因此我也用了偽代碼的方式表達。

圖24: parseHTML偽代碼

整體來說它的邏輯就是循環解析 template ,用正則做各種匹配,對于不同情況分別進行不同的處理,直到整個 template 被解析完畢。 在匹配的過程中會利用 advance 函數不斷前進整個模板字符串,直到字符串末尾。

圖25: advance函數

為了更加直觀地說明 advance 的作用,可以通過一副圖表示:

調用 advance 函數:advance(4) 得到結果:

圖26: advance函數執行示意圖

所以在整個html循環中會不斷調用advance函數,達到把這個html解析完畢的目的。詳細過程有興趣的小伙伴自行去了解。那么至此,parse 的過程就分析完了,看似復雜,但我們可以拋開細節理清它的整體流程。

圖27: parse流程圖

parse 的目標是把 template 模板字符串轉換成 AST 樹,它是一種用 JavaScript 對象的形式來描述整個模板。那么整個 parse 的過程是利用正則表達式順序解析模板,當解析到開始標簽、閉合標簽、文本的時候都會分別執行對應的回調函數,來達到構造 AST 樹的目的。個人理解就是把template(模板)解析成一個對象,該對象是包含這個模板所以信息的一種數據,而這種數據瀏覽器是不支持的,為Vue后面的處理template提供基礎數據。本實例中會生成如下AST樹。

圖28: ast樹形結構

2.優化AST語法樹 ---- optimize(ast, options)。 為什么此處會有優化過程?我們知道Vue是數據驅動,是響應式的,但是template模版中并不是所有的數據都是響應式的,也有許多數據是初始化渲染之后就不會有變化的,那么這部分數據對應的DOM也不會發生變化。后面有一個 update 更新界面的過程,在這當中會有一個 patch 的過程, diff 算法會直接跳過靜態節點,從而減少了比較的過程,優化了 patch 的性能。

圖29: optimize流程

3.codegen:將優化后的AST樹轉換成可執行的代碼。

圖30: codegen流程

template模版經歷過parse->optimize->codegen三個過程之后,就可以得到render function函數了。

圖31: 編譯后生成的render函數

從模版提取到render函數的生成的過程總結如下:

圖32: 編譯render函數的過程

3.3 render到VNode的生成 調用 render.call(vm._renderProxy, vm.$createElement)函數并返回生成的虛擬節點(vnode)。可以看到,render 函數中 createElement 方法就是 vm.$createElement 方法。

圖33: initRender函數

vm.$createElement 方法定義是在執行 initRender 方法的時候,可以看到除了 vm.$createElement 方法,還有一個 vm._c 方法,它是被模板編譯成的 render 函數使用,而 vm.$createElement 是用戶手寫 render 方法使用的,這倆個方法支持的參數相同,并且內部都調用了 createElement 方法。

圖34: createElement函數

createElement 方法實際上是對 _createElement 方法的封裝,它允許傳入的參數更加靈活,在處理這些參數后,調用真正創建 VNode 的函數 _crateElement 。_createElement最終實例化VNode,返回vnode或者一個空的vnode。

圖35: vnode實例化

本文例子生成如下的虛擬dom:

圖36:生成的vnode

簡單的梳理了createElement函數流程圖,可以參考下圖:

圖37:createElement函數流程圖

3.3虛擬DOM映射為真實DOM ----patch vm._render 函數的作用是生成的虛擬節點(vnode)。vm._update 函數的作用是把 vm._render 函數生成的虛擬節點渲染成真正的 DOM。

圖38:_update函數

update方法會在兩種情況下被調用,一是new Vue初始化的時候,還有一種情況就是當我們改變data數據,頁面重新渲染時調用。update最終會調用patch方法。patch實際調用的是createPatchFunction({ nodeOps, modules })。這個方法接收兩個參數,nodeOps,modules。摘取一部分nodeOps內容

圖39:nodeOps

可以看到,里面都是一些原生dom操作的封裝,摘取modules一部分內容。

圖40:modules

可以看到,是一些對原生dom特性控制的封裝,以及一些輔助函數, 下面我們回到createPatchFunction,createPatchFunction 方法中首先定義了好多的輔助函數,最后返回了一個函數,即patch,來看下這個patch。在該函數中,第一個參數是dom元素,第二個參數是vnode。

圖41:patch函數

一系列判斷過后會執行emptyNodeAt()輔助函數,可以看到,emptyNodeAt()函數的功能是創建一個新的vnode。因此oldVnode = emptyNodeAt(oldNode)創新了新的vnode替換,而原來的oldNode(dom節點)可以在該vnode節點的elm元素中訪問到。

圖42:patch函數

取到當前的el對應的dom節點和其父節點后,開始利用createElm函數創建新的dom節點。在最初的實例中,oldElm就是id為app的div。而parentElm就是其父元素,即body。接下來調用createElm方法,這個方法定義于傳入的modules輔助函數中, 這個方法才是真實dom操作的核心所在,它的作用就是將vnode掛載到真實的dom上。我們進入createElm。

圖43:createElm函數

在createElm()函數中,主要完成的功能是將構建dom子節點插入到父節點中,并且一直循環到該節點沒有子節點為止。這個過程createElm()函數和createChildren函數一起完成。

創建子節點

圖44:創建子節點

可以看到,在createChildren()函數中,如果該vnode的子節點是矩陣的話,就會調用createElm()函數。因此兩個函數是相互調用生成dom節點,然后插入到父節點的過程。如果該子節點是最后一個節點,則直接在dom節點后面插入該文本節點。最后調用insert將整個dom樹一次性插入到body中。上面從主線上完成模版和數據的渲染。

圖45:插入節點

此外,因為新建了一個div來渲染視圖,因此應該把原來的就定義的用來掛載的dom節點(一般是個div)刪掉。所以,可以看到,在vue的渲染過程中,會創建新的dom節點替換掉以前的節點,因此我們在初始化的時候不能將節點選擇掛載在html和body上。

圖46:刪除原節點

四、總結 回過頭來看,數據的渲染邏輯并不是特別復雜,核心關鍵的幾步流程還是非常清晰的:

    new Vue,執行初始化,將傳入的data數據綁定到當前實例,就可以通過this.message的形式訪問傳入的數據。這個過程是執行initData()函數完成的。

    掛載$mount方法,通過自定義Render方法、template、el等生成Render函數。如果傳入了模版(template)就將模版里面的內容編譯成render函數,否則將傳入的el對應的元素的內容編譯成render函數。編譯是調用compileToFunctions函數完成的。也可以自己手寫render函數,可以減少編譯這一環節。其中render渲染函數的優先級最高,template次之且需編譯成渲染函數,而掛載點el屬性對應的元素若存在,則在前兩者均不存在時,其outerHTML才會用于編譯與渲染。

    生成render函數后,調用_createElement函數生成vnode。

    將虛擬DOM映射為真實DOM頁面上。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/6775.html

相關文章

  • 前方來報,八月最新資訊--關于vue2&3的最佳文章推薦

    摘要:哪吒別人的看法都是狗屁,你是誰只有你自己說了才算,這是爹教我的道理。哪吒去他個鳥命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰和你做朋友太乙真人人是否能夠改變命運,我不曉得。我只曉得,不認命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...

    izhuhaodev 評論0 收藏0
  • vue-router 實現分析

    摘要:而組件在創建時,又怎么會去調用呢這是由于將自身作為一個插件安裝到了,通過注冊了一個鉤子函數,從而在之后所有的組件創建時都會調用該鉤子函數,給了檢查是否有參數,從而進行初始化的機會。 vue-router 是 Vue.js 官方的路由庫,本著學習的目的,我對 vue-router 的源碼進行了閱讀和分析,分享出來給其他感興趣的同學做個參考吧。 參考 源碼:vuejs/vue-route...

    psychola 評論0 收藏0
  • 關于Vue2一些值得推薦的文章 -- 五、六月份

    摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...

    sutaking 評論0 收藏0
  • 關于Vue2一些值得推薦的文章 -- 五、六月份

    摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...

    khs1994 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<