摘要:表示虛擬節(jié)點,為什么叫虛擬節(jié)點呢,因為不是真的節(jié)點。因為是對象,不管還是瀏覽器,都可以統(tǒng)一操作,從而獲得了服務端渲染原生渲染手寫渲染函數(shù)等能力減少操作。
寫文章不容易,點個贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧
【Vue原理】VNode - 源碼版
今天就來探索 VNode 的源碼,VNode 是 Vue2 渲染機制中很重要的一部分,是深入Vue 必須了解的部分
我們以4個問題來開始我們的探索
1、vnode 是什么及其作用 2、vnode 什么時候生成 3、vnode 怎么生成 4、vnode 存放什么信息 5、vnode 存放在哪里
文章很長,看之前值做好準備
VNode是什么及作用首先,第一個問題已經(jīng)很爛了,網(wǎng)上有很多相關的內(nèi)容,為了內(nèi)容的完整性,所以也放上來哈哈。
VNode 表示 虛擬節(jié)點 Virtual DOM,為什么叫虛擬節(jié)點呢,因為不是真的 DOM 節(jié)點。
他只是用 javascript 對象來描述真實 DOM,這么描述,把DOM標簽,屬性,內(nèi)容都變成 對象的屬性
就像用 JavaScript 對象描述一個人一樣
{sex:"男", name:"神仙朱", salary:5000,children:null}
過程就是,把你的 template 模板 描述成 VNode,然后一系列操作之后通過 VNode 形成真實DOM進行掛載
是什么?
JavaScript 對象
什么用?
1、兼容性強,不受執(zhí)行環(huán)境的影響。VNode 因為是 JS 對象,不管 Node 還是 瀏覽器,都可以統(tǒng)一操作, 從而獲得了服務端渲染、原生渲染、手寫渲染函數(shù)等能力
2、減少操作 DOM。任何頁面的變化,都只使用 VNode 進行操作對比,只需要在最后一步掛載更新DOM,不需要頻繁操作DOM,從而提高頁面性能
VNode怎么生成在 Vue 源碼中,vnode 是通過一個構造函數(shù)生成的,構造函數(shù)看起來挺簡單的
本來以為很多內(nèi)容,帶著沉重的心情去探索,然后看到之后就放松了下來,看了一會,心情再次沉重了起來
其中涉及的內(nèi)容還是挺多的....不然哪里來開篇的那么多問題
行了,看下 VNode 的構造函數(shù)
function VNode( tag, data, children, text, elm, context, componentOptions ) { this.tag = tag; // 標簽名 this.data = data; this.children = children; // 子元素 this.text = text; // 文本內(nèi)容 this.elm = elm; // Dom 節(jié)點 this.context = context; this.componentOptions = componentOptions; this.componentInstance = undefined; this.parent = undefined; this.isStatic = false; // 是否靜態(tài)節(jié)點 this.isComment = false; // 是否是注釋節(jié)點 this.isCloned = false; // 是否克隆節(jié)點 };
看完上面,先不要糾結(jié)都是什么東西,先來走一遍
比如我們使用 vnode 去描述這樣一個template
111111
使用 VNode 構造函數(shù)就可以生成下面的 VNode
{ tag: "div", data: { attrs:{href:"2222"} staticClass: "parent", staticStyle: { height: "0" } }, children: [{ tag: undefined, text: "111111" }] }
這個 JS 對象,就已經(jīng)囊括了整個模板的所有信息,完全可以根據(jù)這個對象來構造真實DOM了
至于其中都是什么意思,請看下個問題
VNode存放什么信息新建一個 vnode 的時候,包含了非常多的屬性,每個屬性都是節(jié)點的描述的一部分
我們只撿一些屬性來探索一下,了解主體即可
普通屬性1、data
1、存儲節(jié)點的屬性,class,style 等
2、存儲綁定的事件
3、....其他
2、elm
真實DOM 節(jié)點
生成VNode 的時候,并不存在真實 DOM
elm 會在需要創(chuàng)建DOM 時完成賦值,具體函數(shù)在 createElm 中
賦值語句就是一句(簡化了源碼)
3、context
渲染這個模板的上下文對象
意思就是,template 里面的動態(tài)數(shù)據(jù)要從這個 context 中獲取,而 context 就是 Vue 實例
如果是頁面,那么context 就是本頁面的實例,如果是組件,context則是組件的實例
4 isStatic
是否是靜態(tài)節(jié)點
當一個節(jié)點被標記為靜態(tài)節(jié)點的時候,說明這個節(jié)點可以不用去更新它了,當數(shù)據(jù)變化的時候,可以忽略去比對他,以提高比對效率
組件相關屬性
1、parent
這個parent 表示是組件的外殼節(jié)點
額,什么是外殼節(jié)點,舉個栗子先吧
1、存在這樣一個組件 test
2、頁面中使用這個組件
誒,到這里就有意思了,組件其實應有兩種 VNode
這兩種VNode 名義上都是對的,都有理,誰是正牌不好說
最后尤大判定第一個 VNode 是 第二個 VNode 的爸爸,也就是外殼節(jié)點
外殼節(jié)點通常是 父組件和 子組件的 關聯(lián),用于保存一些父組件傳給子組件的數(shù)據(jù) 等
2 componentInstance
這個顧名思義,就是組件生成的實例,保存在這里
上面 test 組件的外殼節(jié)點中的 componentInstance
3 componentOptions
這個就存儲一些 父子組件 PY 交易的證據(jù)
比如 props,事件,slot 什么的,打印看下
其中 children 保存的就是 slot,listeners 保存 事件,propsData 保存 props
VNode怎么生成在初始化完選項,解析完模板之后,就需要掛載 DOM了。此時就需要生成 VNode,才能根據(jù) VNode 生成 DOM 然后掛載
掛載 DOM 第一步,就是先執(zhí)行渲染函數(shù),得到整個模板的 VNode
比如有以下渲染函數(shù),執(zhí)行會返回 VNode,就是 _c 返回的VNode
function (){ with(this){ return _c("div",{attrs:{"href":"xxxx"}},["1111"]). } }
渲染函數(shù)會綁定上下文對象,加上 with 的作用,_c 其實就是 vm._c
現(xiàn)在就來看 vm._c 是什么東西
vm._c = function(a, b, c, d) { return createElement(vm, a, b, c, d, false); };
function createElement( context, tag, data, children, normalizationType ) { var vnode; if (tag是正常html標簽) { vnode = new VNode( tag, data, children, undefined, undefined, context ); } else if (tag 是組件) { vnode = createComponent( Ctor, data, context, children, tag ); } return vnode }
我們可以看到,正常標簽 和 組件會走不同流程
1 、正常標簽
比如有這樣一個正常標簽模板
解析成渲染函數(shù)如下
function (){ with(this){ return _c("div",{ attrs:{"href":"xxxx"}}, ["1111"] ) } }
看上面_c 源碼,可以知道經(jīng)過 _c 把參數(shù)傳導,這樣去構建 VNode
new VNode(tag, data, children, undefined, undefined, context);
這樣就保存了 tag,data,children 和 context
2、組件
比如頁面使用了test組件
解析成渲染函數(shù)如下
with(this){ return _c("div",[ _c("test", {attrs:{"name":name}}, ["1111"] ) ],1) }
看上面 _c 代碼知道 ,_c 會先調(diào)用 createComponent
createComponent(Ctor, data, context, children, tag);}
createComponent 中也會調(diào)用 VNode 構造函數(shù),生成VNode 并返回
function createComponent( Ctor, data, context, children, tag ) { // extractPropsFromVNodeData 作用是把傳入data的 attr 中屬于 props的篩選出來 var propsData = extractPropsFromVNodeData(data, Ctor, tag); var vnode = new VNode( ("vue-component-" + (Ctor.cid) + tag), data, undefined, undefined, undefined, context, { Ctor: Ctor, // 父組件給子組件綁定的props propsData: propsData, // 父組件給子組件綁定的事件 listeners: listeners, tag: tag, children: children }); return vnode }VNode存放在哪里
那么創(chuàng)建出來的 VNode 是否有被存起來,毫無疑問,肯定是要的啊
主要是三個位置存了 vnode,分別是
parent ,_vnode ,$vnode
parent 上面已經(jīng)說過,就先不提了,剩下兩個全部是掛在 Vue 實例一級屬性上的
打印一下組件的實例,可以很清楚看到這兩個屬性
下面來說說這兩個東西
1、_vnode_vnode 存放表示當前節(jié)點的 VNode
什么叫當前,也就是可以通過這個VNode 直接映射成 當前真實DOM
他的作用是什么呢?
用來比對更新,比如你的數(shù)據(jù)變化了,此時會生成一個新的 VNode,然后再拿到保存的_vnode 對比,就可以得到最小區(qū)域,從而只用更新這部分
所以, _vnode 存放的可以說是當前節(jié)點,也可以說是舊節(jié)點
另外,_vnode 中保存有一個 parent,這個parent 就是外殼節(jié)點,上面說 vnode 的時候已經(jīng)說過了
在哪里賦值?
我們來完整地走一遍流程,涉及源碼很多,但是我已經(jīng)非常精簡了,大概了解個流程
function Vue() { ...初始化組件選項等 mountComponent() } function mountComponent() { ....解析模板,生成渲染函數(shù) // 用于生成VNode,生成DOM,掛載DOM updateComponent = function() { vm._update(vm._render()); }; // 新建 watcher,保存updateComponent為更新函數(shù),新建的時候會立即執(zhí)行一遍 new Watcher(vm, updateComponent) } function Watcher(vm, expOrFn) { this.getter = expOrFn ; this.getter() } // 執(zhí)行前面解析得到的渲染函數(shù),返回生成的 VNode Vue.prototype._render = () {} // 根據(jù)vnode,生成DOM 掛載 Vue.prototype._update = function(vnode) { var prevVnode = vm._vnode; vm._vnode = vnode; if (不存在舊節(jié)點) { ...使用vnode創(chuàng)建DOM并直接掛載 } else { ...存在舊節(jié)點,開始比對舊節(jié)點和新節(jié)點,然后創(chuàng)建DOM并掛載 } }2、$vnode
$vnode 只有組件實例才有,因為 $vnode 存放的是外殼節(jié)點,頁面實例中是不存在 $vnode 的
本來也想走下流程的,無奈兜兜轉(zhuǎn)轉(zhuǎn)太多,涉及源碼更多
在哪里進行賦值?
我就放最后一步 updateChildComponent
updateChildComponent 會在上個 _vnode 提到的 vm._update 執(zhí)行流程中調(diào)用
function updateChildComponent( vm, parentVnode ) { vm.$options._parentVnode = parentVnode; vm.$vnode = parentVnode; if (vm._vnode) { vm._vnode.parent = parentVnode; } }最后
鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當?shù)牡胤?,歡迎后臺聯(lián)系本人,有重謝
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/105388.html
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之綁定組件事件上一篇已經(jīng) 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之創(chuàng)建組件今天就要開啟我 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之綁定標簽事件這里的綁定 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之綁定組件自定義事件組件 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之掛載組件由這篇文章從模 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于...
閱讀 1841·2021-10-13 09:39
閱讀 3250·2021-10-12 10:11
閱讀 659·2021-09-28 09:36
閱讀 2776·2019-08-30 15:55
閱讀 1484·2019-08-30 13:04
閱讀 710·2019-08-29 17:08
閱讀 1995·2019-08-29 14:14
閱讀 3495·2019-08-28 18:23