Vue編譯優(yōu)化實(shí)現(xiàn)流程詳解
動(dòng)態(tài)節(jié)點(diǎn)收集與補(bǔ)丁標(biāo)志
1.傳統(tǒng)diff算法的問題
對(duì)于一個(gè)普通模板文件,如果只是標(biāo)簽中的內(nèi)容發(fā)生了變化,那么最簡(jiǎn)單的更新方法很明顯是直接替換標(biāo)簽中的文本內(nèi)容。但是diff算法很明顯做不到這一點(diǎn),它會(huì)重新生成一棵虛擬DOM樹,然后對(duì)兩棵虛擬DOM樹進(jìn)行比較。很明顯,與直接替換標(biāo)簽中的內(nèi)容相比,傳統(tǒng)diff算法需要做很多無意義的操作,如果能夠去除這些無意義的操作,將會(huì)省下一筆很大的性能開銷。其實(shí),只要在模板編譯時(shí),標(biāo)記出哪些節(jié)點(diǎn)是動(dòng)態(tài)的,哪些是靜態(tài)的,然后再通過虛擬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屬性,以此來標(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值來表示:
- 數(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è)枚舉類型來表示這些值:
enum PatchFlags {
TEXT: 1,
CLASS,
STYLE,
OTHER
}
這樣我們就在虛擬DOM的創(chuàng)建階段,將動(dòng)態(tài)節(jié)點(diǎn)提取出來:
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)類型,因此我們可以針對(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樹
組件的根節(jié)點(diǎn)必須作為Block角色,這樣,從根節(jié)點(diǎn)開始的所有動(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樹。
靜態(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)容可以通過innerHTML直接設(shè)置,在性能上具有一定優(yōu)勢(shì)
- 減少創(chuàng)建虛擬節(jié)點(diǎn)帶來的額外開銷
- 減少內(nèi)存占用
緩存內(nèi)聯(lián)事件處理函數(shù)
當(dāng)為組件添加內(nèi)聯(lián)事件時(shí),每次新建一個(gè)組件,都會(huì)為該組件重新創(chuàng)建并綁定一個(gè)新的內(nèi)聯(lián)事件函數(shù),為了避免這方面的無意義開銷,我們可以對(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í)通過setBlockTracking(-1)阻止該VNode被Block收集。
v-once的優(yōu)點(diǎn):
- 避免組件更新時(shí)重新創(chuàng)建虛擬DOM帶來的性能開銷
- 避免無用的Diff開銷
到此這篇關(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īng)用場(chǎng)景中,可能需要在網(wǎng)頁上添加水印以保護(hù)版權(quán)或標(biāo)識(shí)信息,本文將介紹如何在Vue項(xiàng)目中添加滿屏水印并實(shí)現(xiàn)去除水印的功能,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-07-07
解決新建一個(gè)vue項(xiàng)目過程中遇到的問題
這篇文章主要介紹了解決新建一個(gè)vue項(xiàng)目過程中遇到的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Vuex數(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-05
Vue3.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-12
vue常用的數(shù)字孿生可視化的自適應(yīng)方案
這篇文章主要為大家介紹了vue常用的數(shù)字孿生可視化的自適應(yīng)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
vue中watch和computed的區(qū)別與使用方法
這篇文章主要給大家介紹了關(guān)于vue中watch和computed的區(qū)別與使用方法的相關(guān)資料,文中通過實(shí)例代碼結(jié)束的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
vue框架編輯接口頁面下拉級(jí)聯(lián)選擇并綁定接口所屬模塊
這篇文章主要為大家介紹了vue框架編輯接口頁面實(shí)現(xiàn)下拉級(jí)聯(lián)選擇以及綁定接口所屬模塊,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05

