<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
對於一個普通模板檔案,如果只是標籤中的內容發生了變化,那麼最簡單的更新方法很明顯是直接替換標籤中的文字內容。但是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值來表示:
我們可以新建一個列舉型別來表示這些值:
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 ])); }
這麼做的優勢:
當為元件新增內聯事件時,每次新建一個元件,都會為該元件重新建立並繫結一個新的內聯事件函數,為了避免這方面的無意義開銷,我們可以對內聯事件處理常式進行快取。
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的優點:
到此這篇關於Vue編譯優化實現流程詳解的文章就介紹到這了,更多相關Vue編譯優化內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45