寫文章不容易,點個讚唄兄弟
專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧
研究基於 Vue版本 【2.5.17】
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧javascript
【Vue原理】VNode - 源碼版 html
今天就來探索 VNode 的源碼,VNode 是 Vue2 渲染機制中很重要的一部分,是深刻Vue 必須瞭解的部分vue
咱們以4個問題來開始咱們的探索java
一、vnode 是什麼及其做用 二、vnode 何時生成 三、vnode 怎麼生成 四、vnode 存放什麼信息 五、vnode 存放在哪裏
文章很長,看以前值作好準備node
首先,第一個問題已經很爛了,網上有不少相關的內容,爲了內容的完整性,因此也放上來哈哈。瀏覽器
VNode 表示 虛擬節點 Virtual DOM,爲何叫虛擬節點呢,由於不是真的 DOM 節點。函數
他只是用 javascript 對象來描述真實 DOM,這麼描述,把DOM標籤,屬性,內容都變成 對象的屬性性能
就像用 JavaScript 對象描述一我的同樣學習
{sex:'男', name:'神仙朱', salary:5000,children:null}
過程就是,把你的 template 模板 描述成 VNode,而後一系列操做以後經過 VNode 造成真實DOM進行掛載this
是什麼?
JavaScript 對象
什麼用?
一、兼容性強,不受執行環境的影響。VNode 由於是 JS 對象,無論 Node 仍是 瀏覽器,均可以統一操做, 從而得到了服務端渲染、原生渲染、手寫渲染函數等能力
二、減小操做 DOM。任何頁面的變化,都只使用 VNode 進行操做對比,只須要在最後一步掛載更新DOM,不須要頻繁操做DOM,從而提升頁面性能
在 Vue 源碼中,vnode 是經過一個構造函數生成的,構造函數看起來挺簡單的
原本覺得不少內容,帶着沉重的心情去探索,而後看到以後就放鬆了下來,看了一會,心情再次沉重了起來
其中涉及的內容仍是挺多的....否則哪裏來開篇的那麼多問題
行了,看下 VNode 的構造函數
function VNode( tag, data, children, text, elm, context, componentOptions ) { this.tag = tag; // 標籤名 this.data = data; this.children = children; // 子元素 this.text = text; // 文本內容 this.elm = elm; // Dom 節點 this.context = context; this.componentOptions = componentOptions; this.componentInstance = undefined; this.parent = undefined; this.isStatic = false; // 是否靜態節點 this.isComment = false; // 是不是註釋節點 this.isCloned = false; // 是否克隆節點 };
看完上面,先不要糾結都是什麼東西,先來走一遍
好比咱們使用 vnode 去描述這樣一個template
<div class="parent" style="height:0" href="2222"> 111111 </div>
使用 VNode 構造函數就能夠生成下面的 VNode
{ tag: 'div', data: { attrs:{href:"2222"} staticClass: "parent", staticStyle: { height: "0" } }, children: [{ tag: undefined, text: "111111" }] }
這個 JS 對象,就已經囊括了整個模板的全部信息,徹底能夠根據這個對象來構造真實DOM了
至於其中都是什麼意思,請看下個問題
新建一個 vnode 的時候,包含了很是多的屬性,每一個屬性都是節點的描述的一部分
咱們只撿一些屬性來探索一下,瞭解主體便可
一、data
一、存儲節點的屬性,class,style 等
二、存儲綁定的事件
三、....其餘
二、elm
真實DOM 節點
生成VNode 的時候,並不存在真實 DOM
elm 會在須要建立DOM 時完成賦值,具體函數在 createElm 中
賦值語句就是一句(簡化了源碼)
三、context
渲染這個模板的上下文對象
意思就是,template 裏面的動態數據要從這個 context 中獲取,而 context 就是 Vue 實例
若是是頁面,那麼context 就是本頁面的實例,若是是組件,context則是組件的實例
4 isStatic
是不是靜態節點
當一個節點被標記爲靜態節點的時候,說明這個節點能夠不用去更新它了,當數據變化的時候,能夠忽略去比對他,以提升比對效率
組件相關屬性
一、parent
這個parent 表示是組件的外殼節點
額,什麼是外殼節點,舉個栗子先吧
一、存在這樣一個組件 test
二、頁面中使用這個組件
誒,到這裏就有意思了,組件其實應有兩種 VNode
這兩種VNode 名義上都是對的,都有理,誰是正牌很差說
最後尤大斷定第一個 VNode 是 第二個 VNode 的爸爸,也就是外殼節點
外殼節點一般是 父組件和 子組件的 關聯,用於保存一些父組件傳給子組件的數據 等
2 componentInstance
這個顧名思義,就是組件生成的實例,保存在這裏
上面 test 組件的外殼節點中的 componentInstance
3 componentOptions
這個就存儲一些 父子組件 PY 交易的證據
好比 props,事件,slot 什麼的,打印看下
其中 children 保存的就是 slot,listeners 保存 事件,propsData 保存 props
在初始化完選項,解析完模板以後,就須要掛載 DOM了。此時就須要生成 VNode,才能根據 VNode 生成 DOM 而後掛載
掛載 DOM 第一步,就是先執行渲染函數,獲得整個模板的 VNode
好比有如下渲染函數,執行會返回 VNode,就是 _c 返回的VNode
function (){ with(this){ return _c('div',{attrs:{"href":"xxxx"}},["1111"]). } }
渲染函數會綁定上下文對象,加上 with 的做用,_c 其實就是 vm._c
如今就來看 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 、正常標籤
好比有這樣一個正常標籤模板
解析成渲染函數以下
function (){ with(this){ return _c('div',{ attrs:{"href":"xxxx"}}, ["1111"] ) } }
看上面_c 源碼,能夠知道通過 _c 把參數傳導,這樣去構建 VNode
new VNode(tag, data, children, undefined, undefined, context);
這樣就保存了 tag,data,children 和 context
二、組件
好比頁面使用了test組件
解析成渲染函數以下
with(this){ return _c('div',[ _c('test', {attrs:{"name":name}}, ["1111"] ) ],1) }
看上面 _c 代碼知道 ,_c 會先調用 createComponent
createComponent(Ctor, data, context, children, tag);}
createComponent 中也會調用 VNode 構造函數,生成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 是否有被存起來,毫無疑問,確定是要的啊
主要是三個位置存了 vnode,分別是
parent ,_vnode ,$vnode
parent 上面已經說過,就先不提了,剩下兩個所有是掛在 Vue 實例一級屬性上的
打印一下組件的實例,能夠很清楚看到這兩個屬性
下面來講說這兩個東西
_vnode 存放表示當前節點的 VNode
什麼叫當前,也就是能夠經過這個VNode 直接映射成 當前真實DOM
他的做用是什麼呢?
用來比對更新,好比你的數據變化了,此時會生成一個新的 VNode,而後再拿到保存的_vnode 對比,就能夠獲得最小區域,從而只用更新這部分
因此, _vnode 存放的能夠說是當前節點,也能夠說是舊節點
另外,_vnode 中保存有一個 parent,這個parent 就是外殼節點,上面說 vnode 的時候已經說過了
在哪裏賦值?
咱們來完整地走一遍流程,涉及源碼不少,可是我已經很是精簡了,大概瞭解個流程
function Vue() { ...初始化組件選項等 mountComponent() } function mountComponent() { ....解析模板,生成渲染函數 // 用於生成VNode,生成DOM,掛載DOM updateComponent = function() { vm._update(vm._render()); }; // 新建 watcher,保存updateComponent爲更新函數,新建的時候會當即執行一遍 new Watcher(vm, updateComponent) } function Watcher(vm, expOrFn) { this.getter = expOrFn ; this.getter() } // 執行前面解析獲得的渲染函數,返回生成的 VNode Vue.prototype._render = () {} // 根據vnode,生成DOM 掛載 Vue.prototype._update = function(vnode) { var prevVnode = vm._vnode; vm._vnode = vnode; if (不存在舊節點) { ...使用vnode建立DOM並直接掛載 } else { ...存在舊節點,開始比對舊節點和新節點,而後建立DOM並掛載 } }
$vnode 只有組件實例纔有,由於 $vnode 存放的是外殼節點,頁面實例中是不存在 $vnode 的
原本也想走下流程的,無奈兜兜轉轉太多,涉及源碼更多
在哪裏進行賦值?
我就放最後一步 updateChildComponent
updateChildComponent 會在上個 _vnode 提到的 vm._update 執行流程中調用
function updateChildComponent( vm, parentVnode ) { vm.$options._parentVnode = parentVnode; vm.$vnode = parentVnode; if (vm._vnode) { vm._vnode.parent = parentVnode; } }
鑑於本人能力有限,不免會有疏漏錯誤的地方,請你們多多包涵,若是有任何描述不當的地方,歡迎後臺聯繫本人,有重謝