您好,登錄后才能下訂單哦!
本篇內容主要講解“Vue編譯優化的實現流程是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Vue編譯優化的實現流程是什么”吧!
對于一個普通模板文件,如果只是標簽中的內容發生了變化,那么最簡單的更新方法很明顯是直接替換標簽中的文本內容。但是diff算法很明顯做不到這一點,它會重新生成一棵虛擬DOM樹,然后對兩棵虛擬DOM樹進行比較。很明顯,與直接替換標簽中的內容相比,傳統diff算法需要做很多無意義的操作,如果能夠去除這些無意義的操作,將會省下一筆很大的性能開銷。其實,只要在模板編譯時,標記出哪些節點是動態的,哪些是靜態的,然后再通過虛擬DOM傳遞給渲染器,渲染器就能根據這些信息,直接修改對應節點,從而提高運行時性能。
對于一個傳統的模板:
<div> <div> foo </div> <p> {{ bar }} </p> </div>
在這個模板中,只用{{ bar }}是動態內容,因此在bar變量發生變化時,只需要修改p標簽內的內容就行了。因此我們在這個模板對于的虛擬DOM中,加入patchFlag屬性,以此來標簽模板中的動態內容。
const vnode = { tag: 'div', children: [ { tag: 'div', children: 'foo' }, { tag: 'p', children: ctx.bar, patchFlag: 1 }, ] }
對于不同的數值綁定,我們分別用不同的patch值來表示:
數字1,代表節點有動態的textContent
數字2,代表節點有動態的class綁定
數字3,代表節點有動態的style綁定
數字4,其他…
我們可以新建一個枚舉類型來表示這些值:
enum PatchFlags { TEXT: 1, CLASS, STYLE, OTHER }
這樣我們就在虛擬DOM的創建階段,將動態節點提取出來:
const vnode = { tag: 'div', children: [ { tag: 'div', children: 'foo' }, { tag: 'p', children: ctx.bar, patchFlag: PatchFlags.TEXT }, ], dynamicChildren: [ { tag: 'p', children: ctx.bar, patchFlag: PatchFlags.TEXT }, ] }
首先我們創建收集動態節點的邏輯。
const dynamicChildrenStack = []; // 動態節點棧 let currentDynamicChildren = null; // 當前動態節點集合 function openBlock() { // 創建一個新的動態節點棧 dynamicChildrenStack.push((currentDynamicChildren = [])); } function closeBlock() { // openBlock創建的動態節點集合彈出 currentDynamicChildren = dynamicChildrenStack.pop(); }
然后,我們在創建虛擬節點的時候,對動態節點進行收集。
function createVNode(tag, props, children, flags) { const key = props && props.key; props && delete props.key; const vnode = { tag, props, children, key, patchFlags: flags } if(typeof flags !== 'undefined' && currentDynamicChildren) { currentDynamicChildren.push(vnode); } return vnode; }
然后我們修改組件渲染函數的邏輯。
render() { return (openBlock(), createBlock('div', null, [ createVNode('p', { class: 'foo' }, null, 1), createVNode('p', { class: 'bar' }, null) ])); } function createBlock(tag, props, children) { const block = createVNode(tag, props, children); block.dynamicChildren = currentDynamicChildren; closeBlock(); return block; }
function patchElement(n1, n2) { const el = n2.el = n1.el; const oldProps = n1.props; const newProps = n2.props; // ... if(n2.dynamicChildren) { // 如果有動態節點數組,直接更新動態節點數組 patchBlockChildren(n1, n2); } else { patchChildren(n1, n2, el); } } function pathcBlockChildren(n1, n2) { for(let i = 0; i < n2.dynamicChildren.length; i++) { patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i]); } }
由于我們標記了不同的動態節點類型,因此我們可以針對性的完成靶向更新。
function patchElement(n1, n2) { const el = n2.el = n1.el; const oldProps = n1.props; const newProps = n2.props; if(n2.patchFlags) { if(n2.patchFlags === 1) { // 只更新內容 } else if(n2.patchFlags === 2) { // 只更新class } else if(n2.patchFlags === 3) { // 只更新style } else { // 更新所有 for(const k in newProps) { if(newProps[key] !== oldProps[key]) { patchProps(el, key, oldProps[k], newProps[k]); } } for(const k in oldProps) { if(!key in newProps) { patchProps(el, key, oldProps[k], null); } } } } patchChildren(n1, n2, el); }
組件的根節點必須作為Block角色,這樣,從根節點開始的所有動態子代節點都會被收集到根節點的dynamicChildren數組中。除了根節點外,帶有v-if、v-for這種結構化指令的節點,也會被作為Block角色,這些Block角色共同構成一棵Block樹。
假設有以下模板
<div> <p> static text </p> <p> {{ title }} </p> </div>
默認情況下,對應的渲染函數為:
function render() { return (openBlock(), createBlock('div', null, [ createVNode('p', null, 'static text'), createVNode('p', null, ctx.title, 1 /* TEXT */) ])) }
在這段代碼中,當ctx.title屬性變化時,內容為靜態文本的p標簽節點也會跟著渲染一次,這很明顯式不必要的。因此,我們可以使用“靜態提升”,即將靜態節點,提取到渲染函數之外,這樣渲染函數在執行的時候,只是保持了對靜態節點的引用,而不會重新創建虛擬節點。
const hoist1 = createVNode('p', null, 'static text'); function render() { return (openBlock(), createBlock('div', null, [ hoist1, createVNode('p', null, ctx.title, 1 /* TEXT */) ])) }
除了靜態節點,對于靜態props我們也可以將其進行靜態提升處理。
const hoistProps = { foo: 'bar', a: '1' }; function render() { return (openBlock(), createBlock('div', null, [ hoist1, createVNode('p', hoistProps, ctx.title, 1 /* TEXT */) ])) }
除了對節點進行靜態提升外,我們還可以對于純靜態的模板進行預字符化。對于這樣一個模板:
<templete> <p></p> <p></p> <p></p> <p></p> <p></p> ... <p></p> <p></p> <p></p> <p></p> </templete>
我們完全可以將其預處理為:
const hoistStatic = createStaticVNode('<p></p><p></p><p></p><p></p>...<p></p><p></p><p></p><p></p>'); render() { return (openBlock(), createBlock('div', null, [ hoistStatic ])); }
這么做的優勢:
大塊的靜態內容可以通過innerHTML直接設置,在性能上具有一定優勢
減少創建虛擬節點帶來的額外開銷
減少內存占用
當為組件添加內聯事件時,每次新建一個組件,都會為該組件重新創建并綁定一個新的內聯事件函數,為了避免這方面的無意義開銷,我們可以對內聯事件處理函數進行緩存。
function render(ctx, cache) { return h(Comp, { onChange: cache[0] || cache[0] = ($event) => (ctx.a + ctx.b); }) }
v-once指令可以是組件只渲染一次,并且即使該組件綁定了動態參數,也不會更新。它與內聯事件一樣,也是使用了緩存,同時通過setBlockTracking(-1)阻止該VNode被Block收集。
v-once的優點:
避免組件更新時重新創建虛擬DOM帶來的性能開銷
避免無用的Diff開銷
到此,相信大家對“Vue編譯優化的實現流程是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。