Vue編譯優(yōu)化實(shí)現(xiàn)流程詳解
動(dòng)態(tài)節(jié)點(diǎn)收集與補(bǔ)丁標(biāo)志
1.傳統(tǒng)diff算法的問(wèn)題
對(duì)于一個(gè)普通模板文件,如果只是標(biāo)簽中的內(nèi)容發(fā)生了變化,那么最簡(jiǎn)單的更新方法很明顯是直接替換標(biāo)簽中的文本內(nèi)容。但是diff算法很明顯做不到這一點(diǎn),它會(huì)重新生成一棵虛擬DOM樹(shù),然后對(duì)兩棵虛擬DOM樹(shù)進(jìn)行比較。很明顯,與直接替換標(biāo)簽中的內(nèi)容相比,傳統(tǒng)diff算法需要做很多無(wú)意義的操作,如果能夠去除這些無(wú)意義的操作,將會(huì)省下一筆很大的性能開(kāi)銷(xiāo)。其實(shí),只要在模板編譯時(shí),標(biāo)記出哪些節(jié)點(diǎn)是動(dòng)態(tài)的,哪些是靜態(tài)的,然后再通過(guò)虛擬DOM傳遞給渲染器,渲染器就能根據(jù)這些信息,直接修改對(duì)應(yīng)節(jié)點(diǎn),從而提高運(yùn)行時(shí)性能。
2.Block和PatchFlags
對(duì)于一個(gè)傳統(tǒng)的模板:
<div> <div> foo </div> <p> {{ bar }} </p> </div>
在這個(gè)模板中,只用{{ bar }}是動(dòng)態(tài)內(nèi)容,因此在bar變量發(fā)生變化時(shí),只需要修改p標(biāo)簽內(nèi)的內(nèi)容就行了。因此我們?cè)谶@個(gè)模板對(duì)于的虛擬DOM中,加入patchFlag屬性,以此來(lái)標(biāo)簽?zāi)0逯械膭?dòng)態(tài)內(nèi)容。
const vnode = { tag: 'div', children: [ { tag: 'div', children: 'foo' }, { tag: 'p', children: ctx.bar, patchFlag: 1 }, ] }
對(duì)于不同的數(shù)值綁定,我們分別用不同的patch值來(lái)表示:
- 數(shù)字1,代表節(jié)點(diǎn)有動(dòng)態(tài)的textContent
- 數(shù)字2,代表節(jié)點(diǎn)有動(dòng)態(tài)的class綁定
- 數(shù)字3,代表節(jié)點(diǎn)有動(dòng)態(tài)的style綁定
- 數(shù)字4,其他…
我們可以新建一個(gè)枚舉類(lèi)型來(lái)表示這些值:
enum PatchFlags { TEXT: 1, CLASS, STYLE, OTHER }
這樣我們就在虛擬DOM的創(chuàng)建階段,將動(dòng)態(tài)節(jié)點(diǎn)提取出來(lái):
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 }, ] }
3.收集動(dòng)態(tài)節(jié)點(diǎn)
首先我們創(chuàng)建收集動(dòng)態(tài)節(jié)點(diǎn)的邏輯。
const dynamicChildrenStack = []; // 動(dòng)態(tài)節(jié)點(diǎn)棧 let currentDynamicChildren = null; // 當(dāng)前動(dòng)態(tài)節(jié)點(diǎn)集合 function openBlock() { // 創(chuàng)建一個(gè)新的動(dòng)態(tài)節(jié)點(diǎn)棧 dynamicChildrenStack.push((currentDynamicChildren = [])); } function closeBlock() { // openBlock創(chuàng)建的動(dòng)態(tài)節(jié)點(diǎn)集合彈出 currentDynamicChildren = dynamicChildrenStack.pop(); }
然后,我們?cè)趧?chuàng)建虛擬節(jié)點(diǎn)的時(shí)候,對(duì)動(dòng)態(tài)節(jié)點(diǎn)進(jìn)行收集。
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; }
然后我們修改組件渲染函數(shù)的邏輯。
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; }
4.渲染器運(yùn)行時(shí)支持
function patchElement(n1, n2) { const el = n2.el = n1.el; const oldProps = n1.props; const newProps = n2.props; // ... if(n2.dynamicChildren) { // 如果有動(dòng)態(tài)節(jié)點(diǎn)數(shù)組,直接更新動(dòng)態(tài)節(jié)點(diǎn)數(shù)組 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]); } }
由于我們標(biāo)記了不同的動(dòng)態(tài)節(jié)點(diǎn)類(lèi)型,因此我們可以針對(duì)性的完成靶向更新。
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) { // 只更新內(nèi)容 } 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); }
5.Block樹(shù)
組件的根節(jié)點(diǎn)必須作為Block角色,這樣,從根節(jié)點(diǎn)開(kāi)始的所有動(dòng)態(tài)子代節(jié)點(diǎn)都會(huì)被收集到根節(jié)點(diǎn)的dynamicChildren數(shù)組中。除了根節(jié)點(diǎn)外,帶有v-if、v-for這種結(jié)構(gòu)化指令的節(jié)點(diǎn),也會(huì)被作為Block角色,這些Block角色共同構(gòu)成一棵Block樹(shù)。
靜態(tài)提升
假設(shè)有以下模板
<div> <p> static text </p> <p> {{ title }} </p> </div>
默認(rèn)情況下,對(duì)應(yīng)的渲染函數(shù)為:
function render() { return (openBlock(), createBlock('div', null, [ createVNode('p', null, 'static text'), createVNode('p', null, ctx.title, 1 /* TEXT */) ])) }
在這段代碼中,當(dāng)ctx.title屬性變化時(shí),內(nèi)容為靜態(tài)文本的p標(biāo)簽節(jié)點(diǎn)也會(huì)跟著渲染一次,這很明顯式不必要的。因此,我們可以使用“靜態(tài)提升”,即將靜態(tài)節(jié)點(diǎn),提取到渲染函數(shù)之外,這樣渲染函數(shù)在執(zhí)行的時(shí)候,只是保持了對(duì)靜態(tài)節(jié)點(diǎn)的引用,而不會(huì)重新創(chuàng)建虛擬節(jié)點(diǎn)。
const hoist1 = createVNode('p', null, 'static text'); function render() { return (openBlock(), createBlock('div', null, [ hoist1, createVNode('p', null, ctx.title, 1 /* TEXT */) ])) }
除了靜態(tài)節(jié)點(diǎn),對(duì)于靜態(tài)props我們也可以將其進(jìn)行靜態(tài)提升處理。
const hoistProps = { foo: 'bar', a: '1' }; function render() { return (openBlock(), createBlock('div', null, [ hoist1, createVNode('p', hoistProps, ctx.title, 1 /* TEXT */) ])) }
預(yù)字符化
除了對(duì)節(jié)點(diǎn)進(jìn)行靜態(tài)提升外,我們還可以對(duì)于純靜態(tài)的模板進(jìn)行預(yù)字符化。對(duì)于這樣一個(gè)模板:
<templete> <p></p> <p></p> <p></p> <p></p> <p></p> ... <p></p> <p></p> <p></p> <p></p> </templete>
我們完全可以將其預(yù)處理為:
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 ])); }
這么做的優(yōu)勢(shì):
- 大塊的靜態(tài)內(nèi)容可以通過(guò)innerHTML直接設(shè)置,在性能上具有一定優(yōu)勢(shì)
- 減少創(chuàng)建虛擬節(jié)點(diǎn)帶來(lái)的額外開(kāi)銷(xiāo)
- 減少內(nèi)存占用
緩存內(nèi)聯(lián)事件處理函數(shù)
當(dāng)為組件添加內(nèi)聯(lián)事件時(shí),每次新建一個(gè)組件,都會(huì)為該組件重新創(chuàng)建并綁定一個(gè)新的內(nèi)聯(lián)事件函數(shù),為了避免這方面的無(wú)意義開(kāi)銷(xiāo),我們可以對(duì)內(nèi)聯(lián)事件處理函數(shù)進(jìn)行緩存。
function render(ctx, cache) { return h(Comp, { onChange: cache[0] || cache[0] = ($event) => (ctx.a + ctx.b); }) }
v-once
v-once指令可以是組件只渲染一次,并且即使該組件綁定了動(dòng)態(tài)參數(shù),也不會(huì)更新。它與內(nèi)聯(lián)事件一樣,也是使用了緩存,同時(shí)通過(guò)setBlockTracking(-1)阻止該VNode被Block收集。
v-once的優(yōu)點(diǎn):
- 避免組件更新時(shí)重新創(chuàng)建虛擬DOM帶來(lái)的性能開(kāi)銷(xiāo)
- 避免無(wú)用的Diff開(kāi)銷(xiāo)
到此這篇關(guān)于Vue編譯優(yōu)化實(shí)現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)Vue編譯優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue實(shí)現(xiàn)頁(yè)面添加滿(mǎn)屏水印和去除水印功能
在一些特殊的應(yīng)用場(chǎng)景中,可能需要在網(wǎng)頁(yè)上添加水印以保護(hù)版權(quán)或標(biāo)識(shí)信息,本文將介紹如何在Vue項(xiàng)目中添加滿(mǎn)屏水印并實(shí)現(xiàn)去除水印的功能,文中通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-07-07vue中setup語(yǔ)法糖寫(xiě)法實(shí)例
如果你在 vue3 開(kāi)發(fā)中使用了語(yǔ)法的話,對(duì)于組件的name屬性,需要做一番額外的處理,下面這篇文章主要給大家介紹了關(guān)于Vue3 setup語(yǔ)法糖的相關(guān)資料,需要的朋友可以參考下2022-12-12解決新建一個(gè)vue項(xiàng)目過(guò)程中遇到的問(wèn)題
這篇文章主要介紹了解決新建一個(gè)vue項(xiàng)目過(guò)程中遇到的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10Vuex數(shù)據(jù)持久化實(shí)現(xiàn)的思路與代碼
Vuex數(shù)據(jù)持久化可以很好的解決全局狀態(tài)管理,當(dāng)刷新后數(shù)據(jù)會(huì)消失,這是我們不愿意看到的。這篇文章主要給大家介紹了關(guān)于Vuex數(shù)據(jù)持久化實(shí)現(xiàn)的思路與代碼,需要的朋友可以參考下2021-05-05Vue3.0實(shí)現(xiàn)圖片預(yù)覽組件(媒體查看器)功能
最近項(xiàng)目中有個(gè)場(chǎng)景,一組圖片、視頻、音頻、文件數(shù)據(jù),要求點(diǎn)擊圖片可以放大預(yù)覽,左右可以切換音視頻、文件,支持鼠標(biāo)及各種鍵控制?縮放,左右旋轉(zhuǎn),移動(dòng)等功能,這篇文章主要介紹了Vue3.0實(shí)現(xiàn)圖片預(yù)覽組件(媒體查看器),需要的朋友可以參考下2023-12-12vue常用的數(shù)字孿生可視化的自適應(yīng)方案
這篇文章主要為大家介紹了vue常用的數(shù)字孿生可視化的自適應(yīng)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07vue中watch和computed的區(qū)別與使用方法
這篇文章主要給大家介紹了關(guān)于vue中watch和computed的區(qū)別與使用方法的相關(guān)資料,文中通過(guò)實(shí)例代碼結(jié)束的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08vue框架編輯接口頁(yè)面下拉級(jí)聯(lián)選擇并綁定接口所屬模塊
這篇文章主要為大家介紹了vue框架編輯接口頁(yè)面實(shí)現(xiàn)下拉級(jí)聯(lián)選擇以及綁定接口所屬模塊,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05