摘要:经过这次优化,计算的时间快了那么几毫秒。基于当前这个版本的代码还能做怎样的优化呢,请看下一篇的内容你不知道的四的作用。
欢迎关注我的公众号睿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.: 想看完整代码见这里,如果有必要建一个仓库的话请留言给我:代码
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/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,都不约...
阅读 3616·2023-04-25 23:32
阅读 2038·2019-08-30 15:55
阅读 2650·2019-08-30 15:52
阅读 3108·2019-08-30 10:54
阅读 838·2019-08-29 16:16
阅读 644·2019-08-29 15:09
阅读 3646·2019-08-26 14:05
阅读 1632·2019-08-26 13:22