首页 web前端 js教程 vue的diff算法知识点总结

vue的diff算法知识点总结

May 28, 2018 am 11:26 AM
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数组中,多退少补而已

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

Angular5给组件本身的标签添加样式class的方法

vue-cli开发环境实现跨域请求的方法

详解Vue-cli webpack移动端自动化构建rem问题

以上是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脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
2 周前 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)

Python速查表大全,你掌握了哪些知识点? Python速查表大全,你掌握了哪些知识点? Apr 26, 2023 pm 10:49 PM

Python作为当下最大众化的编程语言,相信每天都会有大量的新手朋友进入学习大军的行列。但是无论一门语言是多么的容易学习,其基本概念、基础知识还是比较多的,对于小白来说,一时间要掌握这么多还是有些吃力。今天精选收集了众多Python相关的知识速查表,可以说是包罗万象,以后妈妈再也不用担心大家记不住任何知识点了!Python基础Pythonbasics该速查表包含了所有的Python基本知识,从变量数据类型到列表字符串,从环境安装到常用库的使用,可以说面面俱到。Beginner'sPytho

总结Linux系统中system()函数的用法 总结Linux系统中system()函数的用法 Feb 23, 2024 pm 06:45 PM

Linux下system()函数的总结在Linux系统中,system()函数是一个非常常用的函数,它可以用于执行命令行命令。本文将对system()函数进行详细的介绍,并提供一些具体的代码示例。一、system()函数的基本用法system()函数的声明如下:intsystem(constchar*command);其中,command参数是一个字符

用vimdiff替代svn diff:比较代码的工具 用vimdiff替代svn diff:比较代码的工具 Jan 09, 2024 pm 07:54 PM

在linux下,直接使用svndiff命令查看代码的修改是很吃力的,于是在网上搜索到了一个比较好的解决方案,就是让vimdiff作为svndiff的查看代码工具,尤其对于习惯用vim的人来说真的是很方便。当使用svndiff命令比较某个文件的修改前后时,例如执行以下命令:$svndiff-r4420ngx_http_limit_req_module.c那么实际会向默认的diff程序发送如下命令:-u-Lngx_http_limit_req_module.c(revision4420)-Lngx_

揭秘HTML缓存机制:不可或缺的知识要点 揭秘HTML缓存机制:不可或缺的知识要点 Jan 23, 2024 am 08:51 AM

HTML缓存机制大揭秘:必备的知识点,需要具体代码示例在Web开发中,性能一直是一个重要的考量因素。而HTML缓存机制是提升Web页面性能的关键之一。本文将揭秘HTML缓存机制的原理和实践技巧,并提供具体的代码示例。一、HTML缓存机制的原理Web页面访问过程中,浏览器通过HTTP协议请求服务器获取HTML页面。HTML缓存机制就是将HTML页面缓存在浏览器

MySQL数据类型详解:你需要知道的知识点 MySQL数据类型详解:你需要知道的知识点 Jun 15, 2023 am 08:56 AM

MySQL是世界上最流行的关系型数据库管理系统之一,因其可靠性、高安全性、高扩展性以及相对低的成本而得到了广泛应用。MySQL的数据类型定义了各种数据类型的存储方式,是MySQL的重要组成部分。本文将详解MySQL的数据类型,以及在实际应用中需要注意的一些知识点。一、MySQL的数据类型分类MySQL的数据类型可以分为以下几类:整数类型:包括TINYINT、

网络安全入门:初学者必备知识点是什么? 网络安全入门:初学者必备知识点是什么? Jun 11, 2023 am 09:57 AM

网络安全入门:初学者必备知识点是什么?近年来,随着互联网的高速发展,网络安全越来越引起人们的重视。然而,对于很多人来说,网络安全依然是一片未知的海洋。那么,网络安全入门,初学者需要掌握哪些必备知识点呢?本文将为大家梳理一下。一、网络攻击与威胁首先,了解网络攻击和威胁的类型是入门网络安全所必须掌握的知识点。网络攻击的种类多种多样,如钓鱼攻击、恶意软件、勒索软件

Oracle数据类型大揭秘:你必须了解的知识点 Oracle数据类型大揭秘:你必须了解的知识点 Mar 07, 2024 pm 05:18 PM

Oracle数据类型大揭秘:你必须了解的知识点,需要具体代码示例Oracle作为世界领先的数据库管理系统之一,在数据存储和处理中扮演着重要的角色。在Oracle中,数据类型是非常重要的概念,它定义了数据在数据库中的存储格式、范围和操作方式。本文将揭示Oracle数据类型的各种知识点,并且通过具体的代码示例展示它们的用法和特点。一、常见的数据类型字符型数据类型

Git工作流程管理经验总结 Git工作流程管理经验总结 Nov 03, 2023 pm 06:45 PM

Git工作流程管理经验总结引言:在软件开发中,版本管理是一个非常重要的环节。而Git作为目前最流行的版本管理工具之一,其强大的分支管理能力使得团队协作更加高效灵活。本文将就Git工作流程管理经验进行总结和分享。一、Git工作流程简介Git支持多种工作流程,可以根据团队的实际情况选择合适的工作流程。常见的Git工作流程有集中式工作流、功能分支工作流、GitF

See all articles