首页 web前端 js教程 怎样使用vue中diff算法

怎样使用vue中diff算法

Jun 12, 2018 pm 02:25 PM
diff vue

这次给大家带来怎样使用vue中diff算法,使用vue中diff算法的注意事项有哪些,下面就是实战案例,一起来看一下。

虚拟dom

diff算法首先要明确一个概念就是diff的对象是虚拟dom,更新真实dom则是diff算法的结果

Vnode基类

 constructor (
  。。。
 ) {
  this.tag = tag
  this.data = data
  this.children = children
  this.text = text
  this.elm = elm
  this.ns = undefined
  this.context = context
  this.fnContext = undefined
  this.fnOptions = undefined
  this.fnScopeId = undefined
  this.key = data && data.key
  this.componentOptions = componentOptions
  this.componentInstance = undefined
  this.parent = undefined
  this.raw = false
  this.isStatic = false
  this.isRootInsert = true
  this.isComment = false
  this.isCloned = false
  this.isOnce = false
  this.asyncFactory = asyncFactory
  this.asyncMeta = undefined
  this.isAsyncPlaceholder = false
 }
登录后复制

这个部分的代码 主要是为了更好地知道在diff算法中具体diff的属性的含义,当然也可以更好地了解vnode实例

整体过程

核心函数是patch函数

  • isUndef判断(是不是undefined或者null)

  • // empty mount (likely as component), create new root elementcreateElm(vnode, insertedVnodeQueue) 这里可以发现创建节点不是一个一个插入,而是放入一个队列中统一批处理

  • 核心函数sameVnode

function sameVnode (a, b) {
 return (
  a.key === b.key && (
   (
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
   ) || (
    isTrue(a.isAsyncPlaceholder) &&
    a.asyncFactory === b.asyncFactory &&
    isUndef(b.asyncFactory.error)
   )
  )
 )
}
登录后复制

这里是一个外层的比较函数,直接去比较了两个节点的key,tag(标签),data的比较(注意这里的data指的是VNodeData),input的话直接比较type。

export interface VNodeData {
 key?: string | number;
 slot?: string;
 scopedSlots?: { [key: string]: ScopedSlot };
 ref?: string;
 tag?: string;
 staticClass?: string;
 class?: any;
 staticStyle?: { [key: string]: any };
 style?: object[] | object;
 props?: { [key: string]: any };
 attrs?: { [key: string]: any };
 domProps?: { [key: string]: any };
 hook?: { [key: string]: Function };
 on?: { [key: string]: Function | Function[] };
 nativeOn?: { [key: string]: Function | Function[] };
 transition?: object;
 show?: boolean;
 inlineTemplate?: {
  render: Function;
  staticRenderFns: Function[];
 };
 directives?: VNodeDirective[];
 keepAlive?: boolean;
}
登录后复制

这会确认两个节点是否有进一步比较的价值,不然直接替换

替换的过程主要是一个createElm函数 另外则是销毁oldVNode

// destroy old node
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode)
    }
登录后复制

插入过程简化来说就是判断node的type分别调用

createComponent(会判断是否有children然后递归调用)

createComment

createTextNode

创建后使用insert函数

之后需要用hydrate函数将虚拟dom和真是dom进行映射

function insert (parent, elm, ref) {
  if (isDef(parent)) {
   if (isDef(ref)) {
    if (ref.parentNode === parent) {
     nodeOps.insertBefore(parent, elm, ref)
    }
   } else {
    nodeOps.appendChild(parent, elm)
   }
  }
 }
登录后复制

核心函数

 function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  if (oldVnode === vnode) {
   return
  }
  const elm = vnode.elm = oldVnode.elm
  if (isTrue(oldVnode.isAsyncPlaceholder)) {
   if (isDef(vnode.asyncFactory.resolved)) {
    hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
   } else {
    vnode.isAsyncPlaceholder = true
   }
   return
  }
  if (isTrue(vnode.isStatic) &&
   isTrue(oldVnode.isStatic) &&
   vnode.key === oldVnode.key &&
   (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
   vnode.componentInstance = oldVnode.componentInstance
   return
  }
  let i
  const data = vnode.data
  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
   i(oldVnode, vnode)
  }
  const oldCh = oldVnode.children
  const ch = vnode.children
  if (isDef(data) && isPatchable(vnode)) {
   for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
   if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  }
  if (isUndef(vnode.text)) {
   if (isDef(oldCh) && isDef(ch)) {
    if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
   } else if (isDef(ch)) {
    if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, &#39;&#39;)
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
   } else if (isDef(oldCh)) {
    removeVnodes(elm, oldCh, 0, oldCh.length - 1)
   } else if (isDef(oldVnode.text)) {
    nodeOps.setTextContent(elm, &#39;&#39;)
   }
  } else if (oldVnode.text !== vnode.text) {
   nodeOps.setTextContent(elm, vnode.text)
  }
  if (isDef(data)) {
   if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
  }
 }
登录后复制

const el = vnode.el = oldVnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。

  1. 比较二者引用是否一致

  2. 之后asyncFactory不知道是做什么的,所以这个比较看不懂

  3. 静态节点比较key,相同后也不做重新渲染,直接拷贝componentInstance(once命令在此生效)

  4. 如果vnode是文本节点或注释节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以

  5. children的比较

  • 如果只有oldVnode有子节点,那就把这些节点都删除

  • 如果只有vnode有子节点,那就创建这些子节点,这里如果oldVnode是个文本节点就把vnode.elm的文本设置为空字符串

  • 都有则updateChildren,这个之后详述

  • 如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串

updateChildren

这部分重点还是关注整个算法

首先四个指针,oldStart,oldEnd,newStart,newEnd,两个数组,oldVnode,Vnode。

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  let oldStartIdx = 0
  let newStartIdx = 0
  let oldEndIdx = oldCh.length - 1
  let oldStartVnode = oldCh[0]
  let oldEndVnode = oldCh[oldEndIdx]
  let newEndIdx = newCh.length - 1
  let newStartVnode = newCh[0]
  let newEndVnode = newCh[newEndIdx]
  let oldKeyToIdx, idxInOld, vnodeToMove, refElm
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
   if (isUndef(oldStartVnode)) {
    oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
   } else if (isUndef(oldEndVnode)) {
    oldEndVnode = oldCh[--oldEndIdx]
   } else if (sameVnode(oldStartVnode, newStartVnode)) {
    patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
    oldStartVnode = oldCh[++oldStartIdx]
    newStartVnode = newCh[++newStartIdx]
   } else if (sameVnode(oldEndVnode, newEndVnode)) {
    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
    oldEndVnode = oldCh[--oldEndIdx]
    newEndVnode = newCh[--newEndIdx]
   } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
    patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
    canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
    oldStartVnode = oldCh[++oldStartIdx]
    newEndVnode = newCh[--newEndIdx]
   } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
    patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
    canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
    oldEndVnode = oldCh[--oldEndIdx]
    newStartVnode = newCh[++newStartIdx]
   } else {
    if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    idxInOld = isDef(newStartVnode.key)
     ? oldKeyToIdx[newStartVnode.key]
     : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
    if (isUndef(idxInOld)) { // New element
     createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
    } else {
     vnodeToMove = oldCh[idxInOld]
     if (sameVnode(vnodeToMove, newStartVnode)) {
      patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
      oldCh[idxInOld] = undefined
      canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
     } else {
      // same key but different element. treat as new element
      createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
     }
    }
    newStartVnode = newCh[++newStartIdx]
   }
  }
  if (oldStartIdx > oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx > newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
 }
登录后复制

一个循环比较的几种情况和处理(以下的++ --均指index的++ --)比较则是比较的node节点,简略写法 不严谨 比较用的是sameVnode函数也不是真的全等

整体循环不结束的条件oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx

  1. oldStart === newStart,oldStart++ newStart++

  2. oldEnd === newEnd,oldEnd-- newEnd--

  3. oldStart === newEnd, oldStart插到队伍末尾 oldStart++ newEnd--

  4. oldEnd === newStart, oldEnd插到队伍开头 oldEnd-- newStart++

  5. 剩下的所有情况都走这个处理简单的说也就两种处理,处理后newStart++

  • newStart在old中发现一样的那么将这个移动到oldStart前

  • 没有发现一样的那么创建一个放到oldStart之前

循环结束后并没有完成

还有一段判断才算完

if (oldStartIdx > oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx > newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }

简单的说就是循环结束后,看四个指针中间的内容,old数组中和new数组中,多退少补而已

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

微信小程序开发中怎样实现搜索内容高亮功能

如何在项目中使用js中存储键值

以上是怎样使用vue中diff算法的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1676
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
vue中怎么用bootstrap vue中怎么用bootstrap Apr 07, 2025 pm 11:33 PM

在 Vue.js 中使用 Bootstrap 分为五个步骤:安装 Bootstrap。在 main.js 中导入 Bootstrap。直接在模板中使用 Bootstrap 组件。可选:自定义样式。可选:使用插件。

vue怎么给按钮添加函数 vue怎么给按钮添加函数 Apr 08, 2025 am 08:51 AM

可以通过以下步骤为 Vue 按钮添加函数:将 HTML 模板中的按钮绑定到一个方法。在 Vue 实例中定义该方法并编写函数逻辑。

vue中的watch怎么用 vue中的watch怎么用 Apr 07, 2025 pm 11:36 PM

Vue.js 中的 watch 选项允许开发者监听特定数据的变化。当数据发生变化时,watch 会触发一个回调函数,用于执行更新视图或其他任务。其配置选项包括 immediate,用于指定是否立即执行回调,以及 deep,用于指定是否递归监听对象或数组的更改。

vue返回上一页的方法 vue返回上一页的方法 Apr 07, 2025 pm 11:30 PM

Vue.js 返回上一页有四种方法:$router.go(-1)$router.back()使用 &lt;router-link to=&quot;/&quot;&gt; 组件window.history.back(),方法选择取决于场景。

React与Vue:Netflix使用哪个框架? React与Vue:Netflix使用哪个框架? Apr 14, 2025 am 12:19 AM

NetflixusesAcustomFrameworkcalled“ Gibbon” BuiltonReact,notReactorVueDirectly.1)TeamExperience:selectBasedAsedonFamiliarity.2)ProjectComplexity:vueforsimplerprojects,vueforsimplerprojects,reactforforforecomplexones.3)cocatizationNeedsneeds:reactofficatizationneedneeds:reactofferizationneedneedneedneeds:reactoffersizatization needeffersefersmoreflexiblesimore.4)ecosyaka

vue多页面开发是啥意思 vue多页面开发是啥意思 Apr 07, 2025 pm 11:57 PM

Vue 多页面开发是一种使用 Vue.js 框架构建应用程序的方法,其中应用程序被划分为独立的页面:代码维护性:将应用程序拆分为多个页面可以使代码更易于管理和维护。模块化:每个页面都可以作为独立的模块,便于重用和替换。路由简单:页面之间的导航可以通过简单的路由配置来管理。SEO 优化:每个页面都有自己的 URL,这有助于搜索引擎优化。

vue遍历怎么用 vue遍历怎么用 Apr 07, 2025 pm 11:48 PM

Vue.js 遍历数组和对象有三种常见方法:v-for 指令用于遍历每个元素并渲染模板;v-bind 指令可与 v-for 一起使用,为每个元素动态设置属性值;.map 方法可将数组元素转换为新数组。

vue.js怎么引用js文件 vue.js怎么引用js文件 Apr 07, 2025 pm 11:27 PM

在 Vue.js 中引用 JS 文件的方法有三种:直接使用 &lt;script&gt; 标签指定路径;利用 mounted() 生命周期钩子动态导入;通过 Vuex 状态管理库进行导入。

See all articles