VUE-虚拟DOM

708 阅读2分钟

虚拟DOM

真实DOM 浏览器渲染机制: 构建DOM->构建rule styles->构建render树->布局layout->绘制 虚拟DOM:使用对象模拟真实的DOM树。

为什么需要虚拟DOM 因为操作真实DOM的成本太高。假设要更新DOM树上的一个节点,那么就需要从上到下完全遍历一遍,如果有10个更改点,那么就更改十次DOM。 如果用虚拟DOM的话,一次性更新十个更改点,提升了效率减少了开销。

虚拟DOM的优点在于DIFF算法,什么是DIFF算法?就是比较更新的DOM结点与原来的DOM节点之间的差异。然后更快更准的添加/删除/替换节点。

可以写个DIFF算法吗?暂时还不会,手打复制一次加深下记忆吧。

1.假设真实DOM

<div id="virtual-dom">
<p>Virtual DOM</p>
<ul id="list">
  <li class="item">Item 1</li>
  <li class="item">Item 2</li>
  <li class="item">Item 3</li>
</ul>
<div>Hello World</div>
</div> 

2.使用JS对象代表节点,tagName,props表示节点的一些属性

/**
 * Element virdual-dom 对象定义
 * @param {String} tagName - dom 元素名称
 * @param {Object} props - dom 属性
 * @param {Array<Element|String>} - 子节点
 */
function Element(tagName, props, children) {
    this.tagName = tagName
    this.props = props
    this.children = children
    // dom 元素的 key 值,用作唯一标识符
    if(props.key){
       this.key = props.key
    }
    var count = 0
    children.forEach(function (child, i) {
        if (child instanceof Element) {
            count += child.count
        } else {
            children[i] = '' + child
        }
        count++
    })
    // 子元素个数
    this.count = count
}

function createElement(tagName, props, children){
 return new Element(tagName, props, children);
}

module.exports = createElement;

3.根据element对象的设定,可以将DOM对象设定为:

var el = require("./element.js");
var ul = el('div',{id:'virtual-dom'},[
  el('p',{},['Virtual DOM']),
  el('ul', { id: 'list' }, [
	el('li', { class: 'item' }, ['Item 1']),
	el('li', { class: 'item' }, ['Item 2']),
	el('li', { class: 'item' }, ['Item 3'])
  ]),
  el('div',{},['Hello World'])
]) 

DIFF算法代码: DIFF有三种不同:1. patch_text(文本节点替换) 2.patch_replace(直接替换结点) 3.patch_props(节点相同,属性不同) DIFF算法就是收集patch。

// diff 函数,对比两棵树
function diff(oldTree, newTree) {
  var index = 0 // 当前节点的标志
  var patches = {} // 用来记录每个节点差异的对象
  dfsWalk(oldTree, newTree, index, patches)
  return patches
}

// 对两棵树进行深度优先遍历
function dfsWalk(oldNode, newNode, index, patches) {
  var currentPatch = []
  if (typeof (oldNode) === "string" && typeof (newNode) === "string") {
    // 文本内容改变
    if (newNode !== oldNode) {
      currentPatch.push({ type: patch.TEXT, content: newNode })
    }
  } else if (newNode!=null && oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
    // 节点相同,比较属性
    var propsPatches = diffProps(oldNode, newNode)
    if (propsPatches) {
      currentPatch.push({ type: patch.PROPS, props: propsPatches })
    }
    // 比较子节点,如果子节点有'ignore'属性,则不需要比较
    if (!isIgnoreChildren(newNode)) {
      diffChildren(
        oldNode.children,
        newNode.children,
        index,
        patches,
        currentPatch
      )
    }
  } else if(newNode !== null){
    // 新节点和旧节点不同,用 replace 替换
    currentPatch.push({ type: patch.REPLACE, node: newNode })
  }

  if (currentPatch.length) {
    patches[index] = currentPatch
  }
} 

知识点都来自于vue核心之虚拟DOM。参考文章地址juejin.cn/post/684490…