摘要:經過這次優化,計算的時間快了那么幾毫秒。基于當前這個版本的代碼還能做怎樣的優化呢,請看下一篇的內容你不知道的四的作用。
歡迎關注我的公眾號睿Talk,獲取我最新的文章:
目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染效率。那么,什么是Virtual DOM?它是通過什么方式去提升頁面渲染效率的呢?本系列文章會詳細講解Virtual DOM的創建過程,并實現一個簡單的Diff算法來更新頁面。本文的內容脫離于任何的前端框架,只講最純粹的Virtual DOM。敲單詞太累了,下文Virtual DOM一律用VD表示。
這是VD系列文章的第三篇,以下是本系列其它文章的傳送門:
你不知道的Virtual DOM(一):Virtual Dom介紹
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新優化
你不知道的Virtual DOM(四):key的作用
你不知道的Virtual DOM(五):自定義組件
你不知道的Virtual DOM(六):事件處理&異步更新
本文基于本系列文章的第二篇,對VD的比較過程進行優化。
二、優化一:省略patch對象,直接更新dom在上一個版本的代碼里,我們是通過在diff過程中生成patch對象,然后在利用這個對象更新dom。
function tick(element) { if (state.num > 20) { clearTimeout(timer); return; } const newVDom = view(); // 生成差異對象 const patchObj = diff(preVDom, newVDom); preVDom = newVDom; // 給dom打個補丁 patch(element, patchObj); }
實際上這步是多余的。既然在diff的時候就已經知道要如何操作dom了,那為什么不直接在diff里面更新呢?先來回顧下之前的diff代碼:
function diff(oldVDom, newVDom) { // 新建node if (oldVDom == undefined) { return { type: nodePatchTypes.CREATE, vdom: newVDom } } // 刪除node if (newVDom == undefined) { return { type: nodePatchTypes.REMOVE } } // 替換node if ( typeof oldVDom !== typeof newVDom || ((typeof oldVDom === "string" || typeof oldVDom === "number") && oldVDom !== newVDom) || oldVDom.tag !== newVDom.tag ) { return { type: nodePatchTypes.REPLACE, vdom: newVDom } } // 更新node if (oldVDom.tag) { // 比較props的變化 const propsDiff = diffProps(oldVDom, newVDom); // 比較children的變化 const childrenDiff = diffChildren(oldVDom, newVDom); // 如果props或者children有變化,才需要更新 if (propsDiff.length > 0 || childrenDiff.some( patchObj => (patchObj !== undefined) )) { return { type: nodePatchTypes.UPDATE, props: propsDiff, children: childrenDiff } } } }
diff最終返回的對象是這個數據結構:
{ type, vdom, props: [{ type, key, value }] children }
現在,我們把生成對象的步驟省略掉,直接操作dom。這時候我們需要將父元素,還有子元素的索引傳進來(原patch的邏輯):
function diff(oldVDom, newVDom, parent, index=0) { // 新建node if (oldVDom == undefined) { parent.appendChild(createElement(newVDom)); } const element = parent.childNodes[index]; // 刪除node if (newVDom == undefined) { parent.removeChild(element); } // 替換node if ( typeof oldVDom !== typeof newVDom || ((typeof oldVDom === "string" || typeof oldVDom === "number") && oldVDom !== newVDom) || oldVDom.tag !== newVDom.tag ) { parent.replaceChild(createElement(newVDom), element); } // 更新node if (oldVDom.tag) { // 比較props的變化 diffProps(oldVDom, newVDom, element); // 比較children的變化 diffChildren(oldVDom, newVDom, element); } } function diffProps(oldVDom, newVDom) { const allProps = {...oldVDom.props, ...newVDom.props}; // 獲取新舊所有屬性名后,再逐一判斷新舊屬性值 Object.keys(allProps).forEach((key) => { const oldValue = oldVDom.props[key]; const newValue = newVDom.props[key]; // 刪除屬性 if (newValue == undefined) { element.removeAttribute(key); } // 更新屬性 else if (oldValue == undefined || oldValue !== newValue) { element.setAttribute(key, newValue); } } ) } function diffChildren(oldVDom, newVDom, parent) { // 獲取子元素最大長度 const childLength = Math.max(oldVDom.children.length, newVDom.children.length); // 遍歷并diff子元素 for (let i = 0; i < childLength; i++) { diff(oldVDom.children[i], newVDom.children[i], parent, i); } }
本質上來說,這次的優化是將patch的邏輯整合進diff的過程中了。經過這次優化,JS計算的時間快了那么幾毫秒。雖然性能的提升不大,但代碼比原來的少了80多行,降低了邏輯復雜度,優化的效果還是不錯的。
三、優化二:VD與真實dom融合在之前的版本里面,diff操作針對的是新舊2個VD。既然真實的dom已經根據之前的VD渲染出來了,有沒辦法用當前的dom跟新的VD做比較呢?
答案是肯定的,只需要按需獲取dom中不同的屬性就可以了。比如,當比較tag的時候,使用的是nodeType和tagName,比較文本的時候用的是nodeValue。
function tick(element) { if (state.num > 20) { clearTimeout(timer); return; } const newVDom = view(); // 比較并更新節點 diff(newVDom, element); // diff(preVDom, newVDom, element); // preVDom = newVDom; } function diff(newVDom, parent, index=0) { const element = parent.childNodes[index]; // 新建node if (element == undefined) { parent.appendChild(createElement(newVDom)); return; } // 刪除node if (newVDom == undefined) { parent.removeChild(element); return; } // 替換node if (!isSameType(element, newVDom)) { parent.replaceChild(createElement(newVDom), element); return; } // 更新node if (element.nodeType === Node.ELEMENT_NODE) { // 比較props的變化 diffProps(newVDom, element); // 比較children的變化 diffChildren(newVDom, element); } } // 比較元素類型是否相同 function isSameType(element, newVDom) { const elmType = element.nodeType; const vdomType = typeof newVDom; // 當dom元素是文本節點的情況 if (elmType === Node.TEXT_NODE && (vdomType === "string" || vdomType === "number") && element.nodeValue == newVDom ) { return true; } // 當dom元素是普通節點的情況 if (elmType === Node.ELEMENT_NODE && element.tagName.toLowerCase() == newVDom.tag) { return true; } return false; }
為了方便屬性的比較,提高效率,我們將VD的props存在dom元素的__preprops_字段中:
const ATTR_KEY = "__preprops_"; // 創建dom元素 function createElement(vdom) { // 如果vdom是字符串或者數字類型,則創建文本節點,比如“Hello World” if (typeof vdom === "string" || typeof vdom === "number") { return doc.createTextNode(vdom); } const {tag, props, children} = vdom; // 1. 創建元素 const element = doc.createElement(tag); // 2. 屬性賦值 setProps(element, props); // 3. 創建子元素 children.map(createElement) .forEach(element.appendChild.bind(element)); return element; } // 屬性賦值 function setProps(element, props) { // 屬性賦值 element[ATTR_KEY] = props; for (let key in props) { element.setAttribute(key, props[key]); } }
進行屬性比較的時候再取出來:
// 比較props的變化 function diffProps(newVDom, element) { let newProps = {...element[ATTR_KEY]}; const allProps = {...newProps, ...newVDom.props}; // 獲取新舊所有屬性名后,再逐一判斷新舊屬性值 Object.keys(allProps).forEach((key) => { const oldValue = newProps[key]; const newValue = newVDom.props[key]; // 刪除屬性 if (newValue == undefined) { element.removeAttribute(key); delete newProps[key]; } // 更新屬性 else if (oldValue == undefined || oldValue !== newValue) { element.setAttribute(key, newValue); newProps[key] = newValue; } } ) // 屬性重新賦值 element[ATTR_KEY] = newProps; }
通過這種方式,我們不再需要用變量preVDom將上一次生成的VD存下來,而是直接跟真實的dom進行比較,靈活性更強。
四、總結本文基于上一個版本的代碼,簡化了頁面渲染的過程(省略patch對象),同時提供了更靈活的VD比較方法(直接跟dom比較),可用性越來越強了。基于當前這個版本的代碼還能做怎樣的優化呢,請看下一篇的內容:你不知道的Virtual DOM(四):key的作用。
P.S.: 想看完整代碼見這里,如果有必要建一個倉庫的話請留言給我:代碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97149.html
摘要:最后里面沒有第四個元素了,才會把蘋果從移除。四總結本文基于上一個版本的代碼,加入了對唯一標識的支持,很好的提高了更新數組元素的效率。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染...
摘要:變化的只有種更新和刪除。頁面的元素的數量隨著而變。四總結本文詳細介紹如何實現一個簡單的算法,再根據計算出的差異去更新真實的。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React 和 Vue,都不約而同的借助 Virtual DOM 技術提高頁面的渲染...
摘要:現在流行的前端框架都支持自定義組件,組件化開發已經成為提高前端開發效率的銀彈。二對自定義組件的支持要想正確的渲染組件,第一步就是要告訴某個標簽是自定義組件。下面的例子里,就是一個自定義組件。解決了識別自定義標簽的問題,下一步就是定義標簽了。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、...
摘要:如果列表是空的,則存入組件后將異步刷新任務加入到事件循環當中。四總結本文基于上一個版本的代碼,加入了事件處理功能,同時通過異步刷新的方法提高了渲染效率。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DO...
摘要:不同的框架對這三個屬性的命名會有點差別,但表達的意思是一致的。它們分別是標簽名屬性和子元素對象。我們先來看下頁面的更新一般會經過幾個階段。元素有可能是數組的形式,需要將數組解構一層。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約...
閱讀 3630·2023-04-25 23:32
閱讀 2044·2019-08-30 15:55
閱讀 2659·2019-08-30 15:52
閱讀 3115·2019-08-30 10:54
閱讀 844·2019-08-29 16:16
閱讀 655·2019-08-29 15:09
閱讀 3659·2019-08-26 14:05
閱讀 1640·2019-08-26 13:22