【虚拟DOM】【key值】 【DOM diff】 布满荆棘的人生 2022-11-19 13:59 170阅读 0赞 **目录** 虚拟DOM 操作DOM VDOM建模 差量更新 DOM diff【判断DOM发生了变化,并找到这个变化】 虚拟DOM的优点 key值的用处 -------------------- ## 虚拟DOM ## DOM:将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。 VDOM:也叫虚拟DOM,它是仅存于内存中的DOM,因为还未展示到页面中,所以称为VDOM。 var a = document.createElement("div"); 如上,就是一个VDOM。 如果让VDOM变成真实的DOM呢?其实很简单,只需将节点append到页面中 var a = document.createElement("div"); document.body.append(a); > Virtual DOM 其实就是一棵以 JavaScript 对象 (VNode 节点) 作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。 > > 在 JavaScript 里面,虚拟 DOM 表现为一个 Object 对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性。 > > 不过总的来讲,Virtual DOM 对象的节点跟 DOM Tree 每个位置的属性一一对应的,因为人们创造出虚拟 DOM 就是为了更好地将虚拟节点渲染到视图上,也就是把虚拟DOM变成真实的 DOM 节点,提高视图的渲染性能。 ## 操作DOM ## 我们日常中常见的DOM操作有哪些? 事实上,就三类:增、删、改。对应的DOM操作如下: 1. 增加一个节点 => appendChild 2. 删除一个节点 => removeChild 3. 更改一个节点 => replaceChild ## VDOM建模 ## 说是建模,简单点说就是用一个JS对象来表示VDOM。 如果我们可以用一个JS对象来表示VDOM,那么这个对象上多一个属性(增加节点),少一个属性(删除节点),或者属性值变了(更改节点),就一目了然了! 比如这一段 HTML 代码对应的 DOM: <div> <div> <span>hello</span> </div> <span>world</span> </div> 节点无非都是由以下三部分组成: 1. tag : 元素类型 2. attrs : 元素属性 3. children : 子元素集合 我们用另外的一个对象来表示它: let nodesData = { tag: 'div', children: [ { tag: 'div', children: [ { tag: 'span', children: [ { tag: '#text', text: 'hello' } ] } ] }, { tag: 'span', children: [ { tag: '#text', text: 'world' } ] } ] } 用这个对象来表示 DOM 结构,我们可以根据这个对象来构建真正的 DOM。 现在我们需要写一个函数,将这个虚假的 DOM 转化为真实的 DOM。 function vNode({tag, children, text}){ this.tag = tag this.children = children this.text = text } vNode.prototype.render = function(){ if(this.tag === '#text'){ return document.createTextNode(this.text) } let el = document.createElement(this.tag) this.children.map((vChild) => { el.appendChild(new vNode(vChild).render()) }) return el } 调用上面的这个函数可以将我们用来表示 DOM 的对象(虚假 DOM)变成真正的 DOM。 let node = new vNode(nodesData) node.render() 这样,就化假 DOM 为真 DOM 了。 当我们的需要改变 DOM 时,只需要改变其对应的虚假 DOM,再调用一下 render 函数,就可以改变真实 DOM,不需要我们亲自用 JavaScript 去操作页面中的 DOM。 ## 差量更新 ## 上面虽然实现了从虚假 DOM 到真实 DOM 的转化,但是也有一个问题,那就是每次转化都会遍历所有的 DOM 结构,通通的全部转化一遍。如果只有一个小地方发生了改变,也需要将全部的 DOM 更新一遍,那这样就太耗费性能了,我们应该比较虚假 DOM 的变化,只更新变化的地方。比如你加了一个节点,那么我就只更新这个节点,我无需整个模板替换。这样一来,效率就提高了。 ## DOM diff【判断DOM发生了变化,并找到这个变化】 ## DOM diff 即比较两颗虚拟 DOM 树区别的算法。 diff 算法仅在两个树的同级的虚拟节点之间做比较,递归地进行比较,最终实现整个 DOM 树的更新。 diff 算法主要包括几个步骤: * 用 JS 对象的方式来表示 DOM 树的结构,然后根据这个对象构建出真实的 DOM 树,插到文档中。 * 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树的差异 * 最后把所记录的差异应用到所构建的真正的DOM树上,视图更新 通过递归children的方式,就可以判断不同的children并对其操作。有以下几种情况: 1. 没有旧的节点,则创建新的节点,并插入父节点。 2. 如果没有新的节点,则摧毁旧的节点。 3. 如果节点发生了变化,则用replaceChild改变节点信息 4. 如果节点没有变化,则对比该节点的子节点进行判断,使用递归调用 ## 虚拟DOM的优点 ## * **减少 DOM 操作的次数** 虚拟 DOM 的一个优点表现在它可以减少 DOM 操作。 在一个 `<ul>` 下面有1000个 `<li>` ,如果改变了其中10个,如果没有虚拟 DOM,简单直接的做法就是整个 `<ul>` 替换掉重新渲染,这里面其实就存在了很多不必要的 DOM 操作。 DOM 操作慢是慢在浏览器渲染的过程里,改变一行数据就要全部重新渲染,在大多数情况下虚拟 DOM 比 DOM 快,是因为需要更新的 DOM 节点要比原生 DOM 操作更新的节点少,浏览器重绘的时间更短。而且虚拟 DOM 的优势不在于单次的操作,用对比的算法,它可以将多次操作合并成一次操作,在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。 * 减少DOM操作的范围 虚拟DOM借助DOM diff可以把多余的操作省掉,比如添加1000个div,通过对比区分出哪些是新增的、哪些是重复的,如果只有10个是新增的就只渲染这10个。 * **跨平台** 虚拟 DOM 的另一个优点就是可以跨平台。 由于虚拟 DOM 是以 JavaScript 对象为基础的,它的本质就是一个 JavaScript 对象,并不依赖真实平台环境,所以使它具有了跨平台的能力。它在浏览器上可以变成 DOM,在其他平台里也可以变成相应的渲染对象。 ## key值的用处 ## key的作用主要是为了高效的更新虚拟DOM。 diff算法是从左往右进行同层级对比的,如果发现元素相同但是内容不相同,会直接修改内容。解决的方法就是加上唯一的 key,让 Diff 知道就算是同类型的组件,也是有名字区分的,更新视图就不会出错。 **两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。** **同一层级的一组节点,他们可以通过唯一的id进行区分。** > * 首先,在Vue中,存在一个DOM复用机制,会尽量的回收DOM元素进行复用,而这个机制本身是高效的,但很多时候也会造成不可预知的Bug,而在加了key值后,元素就有了一个标识,复用机制不会复用带key值的元素。而React也存在类似的机制。 > * 反之,若使用相同的key值,可以使组件复用,也有可能导致渲染内容缺失。 > * 因此,key值一般来说,最好是`独一无二`的。 > * 除此之外,虚拟DOM在使用Diff算法进行对比时,若存在key值,可以更高效更迅速。 **一:如果没有key值,就会根据就地复用的原则,一个一个对比,然后修改渲染,场景:在同一层级的某一堆节点中插入一个新节点。如果有key,diff算法就可以通过对比找到正确的位置插入新节点,而key值相同的dom节点就不要去比较。 二:如果key值用index,假如我在数组中间插入一项的时候,此时从这一项开始的key值就全部都变了,都需要重新对比渲染。因此,复杂的列表不要用index。**
还没有评论,来说两句吧...