Vue中的 DOM与Diff详情

目录
  • DOM Diff
  • 整体思路
  • 处理简单情况
    • 比对优化
    • 尾部新增元素
  • 头部新增元素
  • 开始元素移动到末尾
  • 末尾元素移动到开头
  • 乱序比对
  • 写在最后

DOM Diff

Vue创建视图分为俩种情况:

  • 首次渲染,会用组件template转换成的真实DOM来替换应用中的根元素
  • 当数据更新后,视图重新渲染,此时并不会重新通过组件template对应的虚拟节点来创建真实DOM,而是会用老的虚拟节点和新的虚拟节点进行比对,根据比对结果来更新DOM

第二种情况就是Vue中经常谈到的DOM Diff,接下来我们将详细介绍新老节点的比对过程。

整体思路

老的虚拟节点和新的虚拟节点是俩棵树,会对俩棵树每层中的虚拟节点进行比对操作:

在每一层进行对比时,会分别为老节点和新节点设置头尾指针:

整体的孩子节点比对思路如下:

  • 在老的虚拟节点和新的虚拟节点的头尾指针之间都有元素时进行遍历
  • 对以下情况进行优化
  • 老节点的头指针和新节点的头指针相同
  • 老节点的尾指针和新节点的尾指针相同
  • 老节点的头指针和新节点的尾指针相同
  • 老节点的尾指针和新节点的头指针相同
  • 乱序排列时,要用新节点的头节点到老节点中查找,如果能找到,对其复用并移动到相应的位置。如果没有找到,将其插入到真实节点中
  • 遍历完成后,将新节点头指针和尾指针之间的元素插入到真实节点中,老节点头指针和尾指针之间的元素删除

在我们渲染视图之前,需要保存当前渲染的虚拟节点。在下一次渲染视图时,它就是老的虚拟节点,要和新的虚拟节点进行对比:

// src/lifecycle.js
Vue.prototype._update = function (vNode) {
  const vm = this;
  const preVNode = vm._vNode;
  vm._vNode = vNode;
  if (!preVNode) { // 首次渲染,没有前一次的虚拟节点
    vm.$el = patch(vm.$el, vNode);
  } else { // vm._vNode中存储了前一次的虚拟节点,进行dom diff
    patch(preVNode, vNode);
  }
};

下面我们实现patch方法中的逻辑

处理简单情况

patch方法中,首先会判断oldVNode是否为真实DOM。如果不是,会进行DOM diff

如果新的虚拟节点和老的虚拟节点标签不一样,直接用新的虚拟节点创建真实节点,然后替换老的真实节点即可:

const vm1 = new Vue();
const html1 = `
  <div id="app">
    111
  </div>
`;
// 将模板编译为render函数
const render1 = compileToFunctions(html1);
const vNode1 = render1.call(vm1);
// 当oldVNode为DOM元素时,会用新节点直接替换老节点
patch(document.getElementById('app'), vNode1);
const html2 = `
  <span id="app">
    333
  </span>
`;
// 将新的模本编译为render函数
const render2 = compileToFunctions(html2);
// 生成新的虚拟节点
const vNode2 = render2.call(vm1);
// 老节点和新节点进行对比
patch(vNode1, vNode2);

上述代码会直接通过新的虚拟节点创建的真实节点来替换老的真实节点,patch中的代码如下:

export function patch (oldVNode, vNode) {
  if (oldVNode.nodeType) { // 旧的节点为真实节点
    // some code...
  } else { // 新旧节点都为虚拟节点,要进行dom diff
    if (oldVNode.tag !== vNode.tag) { // 标签不相同,直接用新节点替换老节点
      const newEle = createElement(vNode);
      replaceChild(newEle, oldVNode.el);
      return newEle;
    }
  }
}

如果老节点和新节点都是文本标签,那么直接用新节点的文本替换老节点即可:

// 老的模板
const html1 = `
  <div id="app">
    111
  </div>
`;
// 新的模板
const html2 = `
  <div id="app">
    333
  </div>
`;

上例中的新的文本333会替换掉老的文本111patch中的实现如下:

export function patch (oldVNode, vNode) {
  if (oldVNode.nodeType) { // 旧的节点为真实节点
    // some code ...
  } else { // 新旧节点都为虚拟节点,要进行dom diff
    if (oldVNode.tag !== vNode.tag) { // 不相等直接替换
      // some code ...
    }
    if (!oldVNode.tag) { // 文本节点,tag相同,都为undefined
      oldVNode.el.textContent = vNode.text;
      return oldVNode.el;
    }
  }
}

当老节点和新节点的标签相同时,要更新标签对应真实元素的属性,更新规则如下:

  • 用新节点中的属性替换老节点中的属性
  • 删除老节点中多余的属性
function updateProperties (vNode, oldProps = {}) { // 老节点和新节点的属性
  const { el, props } = vNode;
  // 用新节点替换老节点中的属性
  for (const key in props) { // 为真实DOM设置新节点的所有属性
    if (props.hasOwnProperty(key)) {
      const value = props[key];
      if (key === 'style') {
        for (const styleKey in value) {
          if (value.hasOwnProperty(styleKey)) {
            el.style[styleKey] = value[styleKey];
          }
        }
      } else {
        el.setAttribute(key, value);
      }
    }
  }
  // 如果老节点中有,而新节点中没有,需要将其删除
  for (const key in oldProps) {
    if (oldProps.hasOwnProperty(key) && !props.hasOwnProperty(key)) {
      el.removeAttribute(key);
    }
  }
  const style = oldProps.style || {};
  const newStyle = props.style || {};
  // 删除老节点中多余的样式
  for (const key in style) {
    if (!newStyle.hasOwnProperty(key) && style.hasOwnProperty(key)) {
      el.style[key] = '';
    }
  }
}

在比对完当前节点后,要继续比对孩子节点。孩子节点可能有以下情况:

  • 老节点孩子为空,新节点有孩子:将新节点的每一个孩子节点创建为真实节点,插入到老节点对应的真实父节点中
  • 老节点有孩子,新节点孩子为空:将老节点的父节点的孩子节点清空
  • 老节点和新节点都有孩子: 采用双指针进行对比

patch中对应的代码如下:

export function patch (oldVNode, vNode) {
  if (oldVNode.nodeType) { // 旧的节点为真实节点
    // some code ...
  } else { // 新旧节点都为虚拟节点,要进行dom diff
    // 元素相同,需要比较子元素
    const el = vNode.el = oldVNode.el;
    // 更新属性
    updateProperties(vNode, oldVNode.props);
    const oldChildren = oldVNode.children;
    const newChildren = vNode.children;
    // 老的有,新的没有,将老的设置为空
    // 老的没有,新的有,为老节点插入多有的新节点
    // 老的和新的都有,遍历每一个进行比对
    if (!oldChildren.length && newChildren.length) {
      for (let i = 0; i < newChildren; i++) {
        const child = newChildren[i];
        el.appendChild(createElement(child));
      }
      return;
    }
    if (oldChildren.length && !newChildren.length) {
      return el.innerHTML = '';
    }
    if (oldChildren.length && newChildren.length) {
      updateChildren(oldChildren, newChildren, el);
    }
    return el;
  }
}

下面我们的逻辑便到了updateChildren中。

比对优化

在对孩子节点的比对中,对一些常见的DOM操作通过双指针进行了优化:

  • 列表尾部新增元素
  • 列表头部新增元素
  • 列表开始元素移动到末尾
  • 列表结尾元素移动到开头

我们在代码中先声明需要的变量:

function updateChildren (oldChildren, newChildren, parent) {
  let oldStartIndex = 0, // 老孩子的开始索引
    oldStartVNode = oldChildren[0], // 老孩子的头虚拟节点
    oldEndIndex = oldChildren.length - 1, // 老孩子的尾索引
    oldEndVNode = oldChildren[oldEndIndex]; // 老孩子的尾虚拟节点
  let newStartIndex = 0, // 新孩子的开始索引
    newStartVNode = newChildren[0], // 新孩子的头虚拟节点
    newEndIndex = newChildren.length - 1, // 新孩子的尾索引
    newEndVNode = newChildren[newEndIndex]; // 新孩子的尾虚拟节点
}

当节点的tagkey都相同时,我们认为这俩个节点是同一个节点,可以进行复用:

function isSameVNode (oldVNode, newVNode) {
  return oldVNode.key === newVNode.key && oldVNode.tag === newVNode.tag;
}

下面我们分别来讲解对应的优化逻辑

尾部新增元素

我们在老节点孩子的末尾新增一个元素作为新节点,其对应的template如下:

const template1 = `
  <div id="app">
    <ul>
      <li key="A" style="color:red">A</li>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
      <li key="D" style="color:green">D</li>
    </ul>
  </div>
`;
const template2 = `
  <div id="app">
    <ul>
      <li key="A" style="color:red">A</li>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
      <li key="D" style="color:green">D</li>
      <li key="E" style="color:purple">E</li>
    </ul>
  </div>
`;

此时oldChildren中的头节点和newChildren中的头节点相同,其比对逻辑如下:

  • 继续对oldStartVNodenewStartVNode执行patch方法,比对它们的标签、属性、文本以及孩子节点
  • oldStartVNodenewStartVNode同时后移,继续进行比对
  • 遍历完老节点后,循环停止
  • 将新节点中剩余的元素插入到老的虚拟节点的尾节点对应的真实节点的下一个兄弟节点oldEndVNode.el.nextSibling之前

画图演示下详细的比对逻辑:

代码如下:

function updateChildren (oldChildren, newChildren, parent) {
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (isSameVNode(oldStartIndex, newStartIndex)) { // 头和头相等
      // 1. 可能是文本节点:需要继续比对文本节点
      // 2. 可能是元素:先比对元素的属性,然后再比对子节点
      patch(oldStartVNode, newStartVNode);
      oldStartVNode = oldChildren[++oldStartIndex];
      newStartVNode = newChildren[++newStartIndex];
    }
  }
  // 新节点中剩余元素插入到真实节点中
  for (let i = newStartIndex; i <= newEndIndex; i++) {
    const child = newChildren[i];
    const refEle = oldChildren[oldEndIndex + 1] || null;
    parent.insertBefore(createElement(child), refEle);
  }
}

头部新增元素

老节点的孩子的头部新增元素E,此时新老节点的template结构如下:

const template1 = `
  <div id="app">
    <ul>
      <li key="A" style="color:red">A</li>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
      <li key="D" style="color:green">D</li>
    </ul>
  </div>
`;
const template2 = `
  <div id="app">
    <ul>
      <li key="E" style="color:purple">E</li>
      <li key="A" style="color:red">A</li>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
      <li key="D" style="color:green">D</li>
    </ul>
  </div>
`;

其比对逻辑和尾部新增类似,只不过此时是oldEndVNodenewEndVNode相同:

  • 继续通过patch比对oldEndVNodenewEndVNode的标签、属性、文本及孩子节点
  • 此时要将oldEndVNodenewEndVNode同时前移,继续进行比对
  • 遍历完老节点后,循环停止
  • 将新节点中剩余的元素插入到老的虚拟节点的尾节点对应的真实节点的下一个兄弟节点oldEndVNode.el.nextSibling之前

该逻辑的示意图如下:

patch中新增代码如下:

function updateChildren (oldChildren, newChildren, parent) {
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (isSameVNode(oldStartIndex, newStartIndex)) { // 头和头相等
      // some code ...
    } else if (isSameVNode(oldEndVNode, newEndVNode)) { // 尾和尾相等
      patch(oldEndVNode, newEndVNode);
      oldEndVNode = oldChildren[--oldEndIndex];
      newEndVNode = newChildren[--newEndIndex];
    }
  }
  // some code ...
}

开始元素移动到末尾

在新节点中,我们将开始元素A移动到末尾,对应的template如下:

const template1 = `
  <div id="app">
    <ul>
      <li key="A" style="color:red">A</li>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
      <li key="D" style="color:green">D</li>
    </ul>
  </div>
`;
const template2 = `
  <div id="app">
    <ul>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
      <li key="D" style="color:green">D</li>
      <li key="A" style="color:red">A</li>
    </ul>
  </div>
`;

此时oldStartVNodenewEndVNode相同:

  • 继续通过patch比对oldStartVNodenewEndVNode的标签、属性、文本及孩子节点
  • oldStartVNode对应的真实节点插入到oldEndVNode对应的真实节点之后
  • oldStartVNode后移,newEndVNode前移
  • 遍历完新老节点后,循环停止,此时元素已经移动到了正确的位置

用图来演示该过程:

在patch方法中编写代码:

function updateChildren (oldChildren, newChildren, parent) {
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (isSameVNode(oldStartIndex, newStartIndex)) { // 头和头相等
      // some code ...
    } else if (isSameVNode(oldEndVNode, newEndVNode)) { // 尾和尾相等
      // some code ...
    } else if (isSameVNode(oldStartVNode, newEndVNode)) { // 将开头元素移动到了末尾:尾和头相同
      // 老节点:需要将头节点对应的元素移动到尾节点之后
      parent.insertBefore(oldStartVNode, oldEndVNode.el.nextSibling);
      patch(oldStartVNode, newEndVNode);
      oldStartVNode = oldChildren[++oldStartIndex];
      newEndVNode = newChildren[--newEndIndex];
    }
  }
}

末尾元素移动到开头

讲解到这里,大家可以先停下阅读的脚步,参考一下之前的逻辑,想想这里会如何进行比对?

在新节点中,我们将末尾元素D移动到开头,对应的template如下:

const template1 = `
  <div id="app">
    <ul>
      <li key="A" style="color:red">A</li>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
      <li key="D" style="color:green">D</li>
    </ul>
  </div>
`;
const template2 = `
  <div id="app">
    <ul>
      <li key="D" style="color:green">D</li>
      <li key="A" style="color:red">A</li>
      <li key="B" style="color:yellow">B</li>
      <li key="C" style="color:blue">C</li>
    </ul>
  </div>
`;

此时oldEndVNodenewStartVNode相同:

  • 继续通过patch比对oldEndVNodenewStartVNode的标签、属性、文本及孩子节点
  • oldEndVNode对应的真实节点插入到oldStartVNode对应的真实节点之前
  • oldEndVNode前移,newStartVNode后移
  • 遍历完新老节点后,循环停止,此时元素已经移动到了正确的位置

画图来演示该过程:

patch方法中添加处理该逻辑的代码:

function updateChildren (oldChildren, newChildren, parent) {
  // 更新子节点:
  //  1. 一层一层进行比较,如果发现有一层不一样,直接就会用新节点的子集来替换父节点的子集。
  //  2. 比较时会采用双指针,对常见的操作进行优化
  let oldStartIndex = 0,
    oldStartVNode = oldChildren[0],
    oldEndIndex = oldChildren.length - 1,
    oldEndVNode = oldChildren[oldEndIndex];
  let newStartIndex = 0,
    newStartVNode = newChildren[0],
    newEndIndex = newChildren.length - 1,
    newEndVNode = newChildren[newEndIndex];

  function makeMap () {
    const map = {};
    for (let i = 0; i < oldChildren.length; i++) {
      const child = oldChildren[i];
      child.key && (map[child.key] = i);
    }
    return map;
  }

  // 将老节点的key和索引进行映射,之后可以直接通过key找到索引,然后通过索引找到对应的元素
  // 这样提前做好映射关系,可以将查找的时间复杂度降到O(1)
  const map = makeMap();
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (isSameVNode(oldStartIndex, newStartIndex)) { // 头和头相等
      // some code ...
    } else if (isSameVNode(oldEndVNode, newEndVNode)) { // 尾和尾相等
      // some code ...
    } else if (isSameVNode(oldStartVNode, newEndVNode)) { // 将开头元素移动到了末尾:尾和头相同
      // some code ...
    } else if (isSameVNode(oldEndVNode, newStartVNode)) { // 将结尾元素移动到了开头
      // 老节点: 将尾指针元素插入到头指针之前
      parent.insertBefore(oldEndVNode.el, oldStartVNode.el);
      patch(oldEndVNode, newStartVNode);
      oldEndVNode = oldChildren[--oldEndIndex];
      newStartVNode = newChildren[++newStartIndex];
    }
  }
}

到这里,patch方法中已经完成了所有的优化操作,下面我们来看下如何对比乱序的孩子节点

乱序比对

当进行比对的元素不满足优化条件时,就要进行乱序对比。下面是俩个乱序的template,看下它们的具体比对过程:

const html1 = `
  <div id="app">
    <ul>
      <li key="D" style="color:red">D</li>
      <li key="B" style="color:yellow">B</li>
      <li key="Z" style="color:blue">Z</li>
      <li key="F" style="color:green">F</li>
    </ul>
  </div>
`;
const html2 = `
  <div id="app">
    <ul>
      <li key="E" style="color:green">E</li>
      <li key="F" style="color:red">F</li>
      <li key="D" style="color:yellow">D</li>
      <li key="Q" style="color:blue">Q</li>
      <li key="B" style="color:#252a34">B</li>
      <li key="M" style="color:#fc5185">M</li>
    </ul>
  </div>
`;

乱序比对的逻辑如下:

  • 用新节点中的头节点的key在老节点中进行查找
  • 如果在老节点中找到key相同的元素,将对应的真实节点移动到oldStartVNode.el(老虚拟头节点对应的真实节点)之前,并且将其对应的虚拟节点设置为null,之后遇到null跳过即可,不再对其进行比对。
  • 继续通过patch方法比对移动的节点和newStartVNode的标签、属性、文本以及孩子节点
  • 如果在老节点中没有找到key相同的元素,会为新节点的头节点创建对应的真实节点,将其插入到oldStartVNode.el之前
  • 遍历完成后,将老节点中头指针和尾指针之间多余的元素删除

画图演示下template中节点的比对过程:

在比对开始之前,我们要先遍历老的孩子节点,生成key与索引对应的map:

function updateChildren (oldChildren, newChildren, parent) {
  function makeMap () {
    const map = {};
    for (let i = 0; i < oldChildren.length; i++) {
      const child = oldChildren[i];
      child.key && (map[child.key] = i);
    }
    return map;
  }
  const map = makeMap();
}

有了map之后,便可以很方便的通过key来找到老孩子节点的索引,然后通过索引直接找到对应的孩子节点,而不用再次进行遍历操作。

接下来书写处理乱序节点的代码:

function updateChildren (oldChildren, newChildren, parent) {
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (oldStartVNode == null) { // 老节点null时跳过该次循环
      oldStartVNode = oldChildren[++oldStartIndex];
      continue;
    } else if (oldEndVNode == null) {
      oldEndVNode = oldChildren[--oldEndIndex];
      continue;
    } else if (isSameVNode(oldStartIndex, newStartIndex)) { // 头和头相等
      // some code ...
    } else if (isSameVNode(oldStartVNode, newEndVNode)) { // 将开头元素移动到了末尾:尾和头相同
      // some code ...
    } else if (isSameVNode(oldEndVNode, newStartVNode)) { // 将结尾元素移动到了开头
      // some code ...
    } else {
      // 1. 用key来进行寻找,找到将其移动到头节点之前
      // 2. 没有找到,将新头节点插入到老头节点之前
      let moveIndex = map[newStartVNode.key]; // 通过key在map中找到相同元素的索引
      if (moveIndex != null) { // 找到了
        const moveVNode = oldChildren[moveIndex];
        parent.insertBefore(moveVNode.el, oldStartVNode.el);
        oldChildren[moveIndex] = null; // 将移动这项标记为null,之后跳过,不再进行比对
        // 还有对其属性和子节点再进行比较
        patch(moveVNode, newStartVNode);
      } else {
        // 为新头节创建对应的真实节点并插入到老节点的头节点之前
        parent.insertBefore(createElement(newStartVNode), oldStartVNode.el);
      }
      newStartVNode = newChildren[++newStartIndex];
    }
  }
  // some code ...
  // 老节点中从头指针到尾指针为多余的元素,需要删除掉
  for (let i = oldStartIndex; i <= oldEndIndex; i++) {
    const child = oldChildren[i];
    parent.removeChild(child.el);
  }
}

当新节点在老节点中存在时,我们会将找到的真实节点移动到相应的位置。此时老节点中的该节点不需要再被遍历,为了防止数组塌陷,便将该节点设置为null。之后再遍历时,如果发现节点的值为null,便跳过本次循环。

现在我们便完成了Vue在数组更新时所有的DOM Diff逻辑。

写在最后

文中主要书写了patch方法的代码,其主要功能如下:

希望小伙伴在读完本文之后,可以对VueDOM Diff过程有更深的理解。

到此这篇关于Vue中的 DOM与Diff详情的文章就介绍到这了,更多相关Vue DOM与Diff内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue的虚拟DOM和diff算法你了解吗

    目录 什么是虚拟DOM? 为什么需要虚拟DOM? 总结 在vue 中 数据改变 -> 虚拟DOM(计算变更)-> 操作DOM -> 视图更新 虚拟DOM: js执行速度比较快 什么是虚拟DOM? 用JS模拟DOM结构 为什么需要虚拟DOM? vue中 数据驱动视图,需要用高效方法来控制DOM操作的次数 diff算法: 虚拟DOM的核心 patch函数 两个使用场景: 首次渲染时,判断第一个参数是否是一个真实dom元素,是的话就创建空vnode,并且关联一个DOM元素,然后比较patch函

  • vue中虚拟DOM与Diff算法知识精讲

    目录 前言 知识点: 虚拟DOM(Virtual DOM): 虚拟dom库 diff算法 snabbdom的核心 init函数 h函数 patch函数(核心) 题外话:diff算法简介 传统diff算法 snabbdom的diff算法优化 updateChildren(核中核:判断子节点的差异) 新结束节点和旧结束节点(情况2) 旧结束节点/新开始节点(情况4) 前言 面试官:"你了解虚拟DOM(Virtual DOM)跟Diff算法吗,请描述一下它们"; 我:"额,...鹅

  • Vue3组件更新中的DOM diff算法示例详解

    目录 同步头部节点 同步尾部节点 添加新的节点 删除多余节点 处理未知子序列 移动子节点 建立索引图 更新和移除旧节点 移动和挂载新节点 最长递增子序列 总结 总结 在vue的组件更新过程中,新子节点数组相对于旧子节点数组的变化,无非是通过更新.删除.添加和移动节点来完成,而核心 diff 算法,就是在已知旧子节点的 DOM 结构.vnode 和新子节点的 vnode 情况下,以较低的成本完成子节点的更新为目的,求解生成新子节点 DOM 的系列操作. 举例来说,假说我们有一个如下的列表 <ul>

  • Vue的transition-group与Virtual Dom Diff算法的使用

    开始 这次的题目看上去好像有点奇怪:把两个没有什么关联的名词放在了一起,正如大家所知道的,transition-group就是Vue的内置组件之一主要用在列表的动画上,但是会跟Virtual Dom Diff算法有什么特别的联系吗?答案明显是有的,所以接下来就是代码分解. 缘起 主要是最近对Vue的Virtual Dom Diff算法有点模糊了,然后顺手就打开了电脑准备温故知新:但是很快就留意到代码: // removeOnly is a special flag used only by <t

  • 一篇文章带你搞懂Vue虚拟Dom与diff算法

    前言 使用过Vue和React的小伙伴肯定对虚拟Dom和diff算法很熟悉,它扮演着很重要的角色.由于小编接触Vue比较多,React只是浅学,所以本篇主要针对Vue来展开介绍,带你一步一步搞懂它. 虚拟DOM 什么是虚拟DOM? 虚拟DOM(Virtual   Dom),也就是我们常说的虚拟节点,是用JS对象来模拟真实DOM中的节点,该对象包含了真实DOM的结构及其属性,用于对比虚拟DOM和真实DOM的差异,从而进行局部渲染来达到优化性能的目的. 真实的元素节点: <div id="wr

  • 深入理解Vue2.x的虚拟DOM diff原理

    前言 经常看到讲解Vue2的虚拟Dom diff原理的,但很多都是在原代码的基础上添加些注释等等,这里从0行代码开始实现一个Vue2的虚拟DOM 实现VNode src/core/vdom/Vnode.js export class VNode{ constructor ( tag, //标签名 children,//孩子[VNode,VNode], text, //文本节点 elm //对应的真实dom对象 ){ this.tag = tag; this.children = children

  • Vue中的 DOM与Diff详情

    目录 DOM Diff 整体思路 处理简单情况 比对优化 尾部新增元素 头部新增元素 开始元素移动到末尾 末尾元素移动到开头 乱序比对 写在最后 DOM Diff Vue创建视图分为俩种情况: 首次渲染,会用组件template转换成的真实DOM来替换应用中的根元素 当数据更新后,视图重新渲染,此时并不会重新通过组件template对应的虚拟节点来创建真实DOM,而是会用老的虚拟节点和新的虚拟节点进行比对,根据比对结果来更新DOM 第二种情况就是Vue中经常谈到的DOM Diff,接下来我们将详

  • 浅析Vue中Virtual DOM和Diff原理及实现

    目录 0. 写在开头 1. vdom 2. Diff 0. 写在开头 本文将秉承Talk is cheap, show me the code原则,做到文字最精简,一切交由代码说明! 1. vdom vdom即虚拟DOM,将DOM映射为JS对象,结合diff算法更新DOM 以下为DOM <div id="app"> <div class="home">home</div> </div> 映射成VDOM { tag: '

  • vue 中Virtual Dom被创建的方法

    本文将通过解读render函数的源码,来分析vue中的vNode是如何创建的.在vue2.x的版本中,无论是直接书写render函数,还是使用template或el属性,或是使用.vue单文件的形式,最终都需要编译成render函数进行vnode的创建,最终再渲染成真实的DOM. 如果对vue源码的目录还不是很了解,推荐先阅读下 深入vue -- 源码目录和编译过程. 01  render函数 render方法定义在文件 src/core/instance/render.js 中 Vue.pro

  • 解决vue中虚拟dom,无法实时更新的问题

    碰到的问题:使用jq获取元素节点的个数时一直为0 解决方法:使用vue的nextTick()函数即可解决 原理:nextTick可以在下一次更新dom之后进行回调,我的问题在于,在页面加载完成时无法获取虚拟dom,而使用回调函数后就可以获取到正确的dom数量,所以只需要在nextTick函数中执行jq函数就可以正确获取了. self.$nextTick(function () { // DOM 更新了 $("#myCarousel").carousel(0); }) 以上这篇解决vue

  • vue中Promise的使用方法详情

    目录 一.使用 1.promise是一种异步解决方案 2.asyncawait 简介: promise是什么,它可以说是异步编程的一种解决方法,就拿传统的ajax发请求来说,单个还好,如果是一个请求回来的数据还要被其他请求调用,不断地嵌套,可想而知,代码看起来是很乱的,promise主要是为了解决这种情景而出现的. 一.使用 1.promise是一种异步解决方案 由于ajax异步方式请求数据时,我们不能知道数据具体回来的事件,所以过去只能将一个callback函数传递给ajax封装的方法,当aj

  • vue中的dom节点和window对象

    目录 window对象 dom元素 获取dom节点的3种方式 方式一:(事件源) 方式二:(使用ref) 方式三:(自定义全局指令) window对象 首先window对象是浏览器下的默认对象,也就是全局对象,在没有明确指向的时候this指向window.即使切换路由,window对象里面的属性和方法依旧会保留.因此可以在控制栏直接输入this.window.self都可以直接打印window对象.window对象很很多默认的方法和属性. 一切全局变量和方法都是window的属性和方法,也就是只

  • 如何在Vue中获取DOM元素的实际宽高

    目录 前言 一.获取元素 二.获取元素宽高 补充:vue项目获取dom元素宽高总是不准确 总结 前言 最近使用 D3.js 开发可视化图表,因为移动端做了 rem 适配,所以需要动态计算获取图表容器的宽高,其中涉及到一些原生DOM API的使用,避免遗忘这里总结一下. 一.获取元素 在 Vue 中可以使用 ref 来获取一个真实的 DOM 元素. 为了保险起见,所有的 DOM 操作建议都放在 $nextTick() 方法中. <template> <div class="box

  • 在vue中获取dom元素内容的方法

    在vue中可以通过给标签加ref属性,就可以在js中利用ref去引用它,从而操作该dom元素,以下是个例子,可以当做参考 <template> <div> <div id="box" ref="mybox"> DEMO </div> </div> </template> <script> export default { data () { return { } }, mounted

随机推荐