来自 娱乐资讯 2019-05-02 00:57 的文章
当前位置: 必发88官网 > 娱乐资讯 > 正文

88必发官网手机版:源码阅读分析

原标题:snabbdom 源码阅读分析

DOM“天生就慢”,所在此以前端各大框架都提供了对DOM操作进行优化的秘诀,Angular中的是脏值检查,React首先提出了Virtual Dom,Vue2.0也参预了Virtual Dom,与React类似。

DOM“天生就慢”,所此前端各大框架都提供了对DOM操作实行优化的办法,Angular中的是脏值检查,React首先建议了Virtual Dom,Vue2.0也投入了Virtual Dom,与React类似。

随着 React Vue 等框架的风靡,Virtual DOM 也愈发火,snabbdom 是个中壹种达成,而且 Vue 二.x 版本的 Virtual DOM 部分也是依据 snabbdom 举行修改的。snabbdom 那么些库主旨代码唯有 200 多行,分外适合想要长远了解Virtual DOM 完成的读者阅读。即让你没据悉过 snabbdom,能够先看看官方文书档案。

本文将对此Vue 二.伍.3本子中央银行使的Virtual Dom实行剖析。

本文将对于Vue 二.5.3本子中应用的Virtual Dom实行辨析。

缘何选择 snabbdom

updataChildren是Diff算法的骨干,所以本文对updataChildren进行了图像和文字的解析。

updataChildren是Diff算法的中央,所以本文对updataChildren实行了图像和文字的解析。

  • 骨干代码唯有 200 行,丰盛的测试用例
  • 强有力的插件系统、hook 系统
  • vue 使用了 snabbdom,读懂 snabbdom 对领悟 vue 的落实有帮助

1.VNode对象


二个VNode的实例包罗了以下属性,那有的代码在src/core/vdom/vnode.js里

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  functionalContext: Component | void; // real context vm for functional nodes
  functionalOptions: ?ComponentOptions; // for SSR caching
  functionalScopeId: ?string; // functioanl scope id support
  • tag: 当前节点的标签字
  • data: 当前节点的多少对象,具体包括怎么样字段能够参见vue源码types/vnode.d.ts中对VNodeData的定义
  • children: 数组类型,包括了当下节点的子节点
  • text: 当前节点的文本,一般文本节点或注释节点会有该属性
  • elm: 当前虚拟节点对应的诚实的dom节点
  • ns: 节点的namespace
  • context: 编写翻译作用域
  • functionalContext: 函数化组件的成效域
  • key: 节点的key属性,用于作为节点的标记,有利于patch的优化
  • componentOptions: 创造组件实例时会用到的选项音讯
  • child: 当前节点对应的组件实例
  • parent: 组件的占位节点
  • raw: raw html
  • isStatic: 静态节点的标记
  • isRootInsert: 是不是作为根节点插入,被
  • isComment: 当前节点是或不是是注释节点
  • isCloned: 当前节点是还是不是为克隆节点
  • isOnce: 当前节点是不是有v-once指令

1.VNode对象


3个VNode的实例包罗了以下属性,那有的代码在src/core/vdom/vnode.js里

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  functionalContext: Component | void; // real context vm for functional nodes
  functionalOptions: ?ComponentOptions; // for SSR caching
  functionalScopeId: ?string; // functioanl scope id support
  • tag: 当前节点的标签名
  • data: 当前节点的数据对象,具体包含怎么样字段能够参考vue源码types/vnode.d.ts中对VNodeData的定义
  • children: 数组类型,包罗了现阶段节点的子节点
  • text: 当前节点的文本,一般文本节点或注释节点会有该属性
  • elm: 当前虚拟节点对应的真实的dom节点
  • ns: 节点的namespace
  • context: 编写翻译作用域
  • functionalContext: 函数化组件的成效域
  • key: 节点的key属性,用于作为节点的标记,有利于patch的优化
  • componentOptions: 创造组件实例时会用到的选项消息
  • child: 当前节点对应的组件实例
  • parent: 组件的占位节点
  • raw: raw html
  • isStatic: 静态节点的标识
  • isRootInsert: 是或不是作为根节点插入,被
  • isComment: 当前节点是不是是注释节点
  • isCloned: 当前节点是或不是为克隆节点
  • isOnce: 当前节点是还是不是有v-once指令

什么是 Virtual DOM

2.VNode的分类


VNode能够知道为VueVirtual Dom的四个基类,通过VNode构造函数生成的VNnode实例可为如下几类:

  • EmptyVNode: 未有内容的解说节点
  • TextVNode: 文本节点
  • ElementVNode: 普通成分节点
  • ComponentVNode: 组件节点
  • CloneVNode: 克隆节点,能够是以上自便档案的次序的节点,唯1的界别在于isCloned属性为true

2.VNode的分类


VNode能够了解为VueVirtual Dom的多少个基类,通过VNode构造函数生成的VNnode实例可为如下几类:

  • EmptyVNode: 未有内容的申明节点
  • TextVNode: 文本节点
  • ElementVNode: 普通成分节点
  • ComponentVNode: 组件节点
  • CloneVNode: 克隆节点,能够是上述任性档案的次序的节点,唯一的区分在于isCloned属性为true

snabbdom 是 Virtual DOM 的壹种完结,所以从前,你须要先清楚怎样是 Virtual DOM。通俗的说,Virtual DOM 正是二个 js 对象,它是真性 DOM 的指雁为羹,只保留部分灵光的音讯,更轻量地讲述 DOM 树的构造。 举例在 snabbdom 中,是如此来定义2个 VNode 的:

三.Create-Element源码解析


那部分代码在src/core/vdom/create-element.js里,笔者就直接粘代码加上自个儿的疏解了

export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode {
  // 兼容不传data的情况
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  // 如果alwaysNormalize是true
  // 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  // 调用_createElement创建虚拟节点
  return _createElement(context, tag, data, children, normalizationType)
}

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode {

  /**
   * 如果存在data.__ob__,说明data是被Observer观察的数据
   * 不能用作虚拟节点的data
   * 需要抛出警告,并返回一个空节点
   *
   * 被监控的data不能被用作vnode渲染的数据的原因是:
   * data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作
   */
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}n`  
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // 当组件的is属性被设置为一个falsy的值
    // Vue将不会知道要把这个组件渲染成什么
    // 所以渲染一个空节点
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // key为非原始值警告
  // warn against non-primitive key
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    warn(
      'Avoid using non-primitive value as key, '  
      'use string/number value instead.',
      context
    )
  }
  // 作用域插槽
  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  // 根据normalizationType的值,选择不同的处理方法
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  // 如果标签名是字符串类型
  if (typeof tag === 'string') {
    let Ctor
    // 获取标签的命名空间
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    // 如果是保留标签
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      // 就创建这样一个vnode
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
      // 如果不是保留字标签,尝试从vm的components上查找是否有这个标签的定义
    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      // 如果找到,就创建虚拟组件节点
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      // 兜底方案,创建一个正常的vnode
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // 当tag不是字符串的时候,我们认为tag是组件的构造类
    // 所以直接创建
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (isDef(vnode)) {
    // 应用命名空间
    if (ns) applyNS(vnode, ns)
    return vnode
  } else {
    // 返回一个空节点
    return createEmptyVNode()
  }
}

function applyNS (vnode, ns, force) {
  vnode.ns = ns
  if (vnode.tag === 'foreignObject') {
    // use default namespace inside foreignObject
    ns = undefined
    force = true
  }
  if (isDef(vnode.children)) {
    for (let i = 0, l = vnode.children.length; i < l; i  ) {
      const child = vnode.children[i]
      if (isDef(child.tag) && (isUndef(child.ns) || isTrue(force))) {
        applyNS(child, ns, force)
      }
    }
  }
}

叁.Create-Element源码解析


那有些代码在src/core/vdom/create-element.js里,小编就一向粘代码加上笔者的申明了

export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode {
  // 兼容不传data的情况
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  // 如果alwaysNormalize是true
  // 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  // 调用_createElement创建虚拟节点
  return _createElement(context, tag, data, children, normalizationType)
}

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode {

  /**
   * 如果存在data.__ob__,说明data是被Observer观察的数据
   * 不能用作虚拟节点的data
   * 需要抛出警告,并返回一个空节点
   *
   * 被监控的data不能被用作vnode渲染的数据的原因是:
   * data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作
   */
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}n`  
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // 当组件的is属性被设置为一个falsy的值
    // Vue将不会知道要把这个组件渲染成什么
    // 所以渲染一个空节点
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // key为非原始值警告
  // warn against non-primitive key
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    warn(
      'Avoid using non-primitive value as key, '  
      'use string/number value instead.',
      context
    )
  }
  // 作用域插槽
  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  // 根据normalizationType的值,选择不同的处理方法
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  // 如果标签名是字符串类型
  if (typeof tag === 'string') {
    let Ctor
    // 获取标签的命名空间
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    // 如果是保留标签
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      // 就创建这样一个vnode
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
      // 如果不是保留字标签,尝试从vm的components上查找是否有这个标签的定义
    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      // 如果找到,就创建虚拟组件节点
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      // 兜底方案,创建一个正常的vnode
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // 当tag不是字符串的时候,我们认为tag是组件的构造类
    // 所以直接创建
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (isDef(vnode)) {
    // 应用命名空间
    if (ns) applyNS(vnode, ns)
    return vnode
  } else {
    // 返回一个空节点
    return createEmptyVNode()
  }
}

function applyNS (vnode, ns, force) {
  vnode.ns = ns
  if (vnode.tag === 'foreignObject') {
    // use default namespace inside foreignObject
    ns = undefined
    force = true
  }
  if (isDef(vnode.children)) {
    for (let i = 0, l = vnode.children.length; i < l; i  ) {
      const child = vnode.children[i]
      if (isDef(child.tag) && (isUndef(child.ns) || isTrue(force))) {
        applyNS(child, ns, force)
      }
    }
  }
}

export interface VNode { sel: string | undefined; data: VNodeData | undefined; children: Array<VNode | string> | undefined; elm: Node | undefined; text: string | undefined; key: Key | undefined;}export interface VNodeData { props?: Props; attrs?: Attrs; class?: Classes; style?: VNodeStyle; dataset?: Dataset; on?: On; hero?: Hero; attachData?: AttachData; hook?: Hooks; key?: Key; ns?: string; // for SVGs fn?: () => VNode; // for thunks args?: Array<any>; // for thunks [key: string]: any; // for any other 三rd party module} 复制代码

4.Patch原理


patch函数的概念在src/core/vdom/patch.js中,patch逻辑比较轻松,就不粘代码了

patch函数接收5个参数:

  • oldVnode: 旧的虚构节点或旧的诚实dom节点
  • vnode: 新的虚构节点
  • hydrating: 是不是要跟真是dom混合
  • removeOnly: 特殊flag,用于
  • parentElm: 父节点
  • refElm: 新节点将插入到refElm在此以前

4.Patch原理


patch函数的概念在src/core/vdom/patch.js中,patch逻辑相比较简单,就不粘代码了

patch函数接收五个参数:

  • oldVnode: 旧的杜撰节点或旧的安分守己dom节点
  • vnode: 新的杜撰节点
  • hydrating: 是还是不是要跟真是dom混合
  • removeOnly: 特殊flag,用于
  • parentElm: 父节点
  • refElm: 新节点将插入到refElm从前

从上面的定义大家得以见到,大家得以用 js 对象来说述 dom 结构,那大家是否能够对八个情景下的 js 对象开始展览自己检查自纠,记录出它们的差别,然后把它使用到确实的 dom 树上啊?答案是足以的,这正是 diff 算法,算法的骨干步骤如下:

patch的逻辑是:

  1. if vnode不存在可是oldVnode存在,表达来意是要绝迹老节点,那么就调用invokeDestroyHook(oldVnode)来实行销
  2. if oldVnode不设有可是vnode存在,说明来意是要创建新节点,那么就调用createElm来创制新节点
  3. else 当vnode和oldVnode都设有时

    • if oldVnode和vnode是同二个节点,就调用patchVnode来进行patch
    • 当vnode和oldVnode不是同1个节点时,假诺oldVnode是真性dom节点或hydrating设置为true,要求用hydrate函数将虚拟dom和真是dom进行映射,然后将oldVnode设置为对应的虚拟dom,找到oldVnode.elm的父节点,依据vnode创立2个实打实dom节点并插入到该父节点中oldVnode.elm的任务

patch的逻辑是:

  1. if vnode不设有然而oldVnode存在,表明来意是要绝迹老节点,那么就调用invokeDestroyHook(oldVnode)来开始展览销
  2. if oldVnode不存在但是vnode存在,表达来意是要创制新节点,那么就调用createElm来创立新节点
  3. else 当vnode和oldVnode都设有时

    • if oldVnode和vnode是同2个节点,就调用patchVnode来开始展览patch
    • 当vnode和oldVnode不是同一个节点时,如果oldVnode是实际dom节点或hydrating设置为true,需求用hydrate函数将虚拟dom和真是dom举办映射,然后将oldVnode设置为相应的虚拟dom,找到oldVnode.elm的父节点,根据vnode创造2个实打实dom节点并插入到该父节点中oldVnode.elm的岗位
  • 用 js 对象来叙述 dom 树结构,然后用那些 js 对象来创造一棵真正的 dom 树,插入到文书档案中
  • 当状态更新时,将新的 js 对象和旧的 js 对象开展相比,获得四个目的之间的反差
  • 将距离应用到确实的 dom 上

patchVnode的逻辑是:

  1. 1经oldVnode跟vnode完全壹致,那么没有必要做其余专门的学业
  2. 倘使oldVnode跟vnode都以静态节点,且具有同等的key,当vnode是克隆节点或者v-once指令调节的节点时,只供给把oldVnode.elm和oldVnode.child都复制到vnode上,也不用再有别的操作
  3. 要不,假如vnode不是文本节点或注释节点

    • 举个例子oldVnode和vnode都有子节点,且二方的子节点不完全壹致,就实践updateChildren
    • 1经只有oldVnode有子节点,那就把那些节点都剔除
    • 如若只有vnode有子节点,那就创立那么些子节点
    • 若是oldVnode和vnode都尚未子节点,不过oldVnode是文本节点或注释节点,就把vnode.elm的文书设置为空字符串
  4. 若是vnode是文件节点或注释节点,可是vnode.text != oldVnode.text时,只必要立异vnode.elm的公文内容就能够

代码如下:

  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    // 如果新旧节点一致,什么都不做
    if (oldVnode === vnode) {
      return
    }

    // 让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化
    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
    }

    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    // 如果新旧都是静态节点,并且具有相同的key
    // 当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上
    // 也不用再有其他操作
    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)
    }
    // 如果vnode不是文本节点或者注释节点
    if (isUndef(vnode.text)) {
      // 并且都有子节点
      if (isDef(oldCh) && isDef(ch)) {
        // 并且子节点不完全一致,则调用updateChildren
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)

        // 如果只有新的vnode有子节点
      } else if (isDef(ch)) {
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // elm已经引用了老的dom节点,在老的dom节点上添加子节点
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)

        // 如果新vnode没有子节点,而vnode有子节点,直接删除老的oldCh
      } else if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)

        // 如果老节点是文本节点
      } else if (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, '')
      }

      // 如果新vnode和老vnode是文本节点或注释节点
      // 但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以
    } 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)
    }
  }

patchVnode的逻辑是:

  1. 若是oldVnode跟vnode完全一致,那么无需做别的业务
  2. 要是oldVnode跟vnode都以静态节点,且独具一样的key,当vnode是仿造节点恐怕v-once指令调控的节点时,只须要把oldVnode.elm和oldVnode.child都复制到vnode上,也不用再有别的操作
  3. 要不,倘若vnode不是文件节点或注释节点

    • 一经oldVnode和vnode都有子节点,且二方的子节点不完全一致,就进行updateChildren
    • 固然唯有oldVnode有子节点,那就把那些节点都剔除
    • 设若只有vnode有子节点,那就创办这个子节点
    • 假若oldVnode和vnode都未有子节点,可是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串
  4. 例如vnode是文本节点或注释节点,可是vnode.text != oldVnode.text时,只必要更新vnode.elm的文书内容就足以

代码如下:

  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    // 如果新旧节点一致,什么都不做
    if (oldVnode === vnode) {
      return
    }

    // 让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化
    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
    }

    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    // 如果新旧都是静态节点,并且具有相同的key
    // 当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上
    // 也不用再有其他操作
    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)
    }
    // 如果vnode不是文本节点或者注释节点
    if (isUndef(vnode.text)) {
      // 并且都有子节点
      if (isDef(oldCh) && isDef(ch)) {
        // 并且子节点不完全一致,则调用updateChildren
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)

        // 如果只有新的vnode有子节点
      } else if (isDef(ch)) {
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // elm已经引用了老的dom节点,在老的dom节点上添加子节点
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)

        // 如果新vnode没有子节点,而vnode有子节点,直接删除老的oldCh
      } else if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)

        // 如果老节点是文本节点
      } else if (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, '')
      }

      // 如果新vnode和老vnode是文本节点或注释节点
      // 但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以
    } 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)
    }
  }

接下去大家来分析那整个经过的贯彻。

5.updataChildren原理


5.updataChildren原理


源码分析

updateChildren的逻辑是:

  1. 独家获取oldVnode和vnode的firstChild、lastChild,赋值给oldStartVnode、oldEndVnode、newStartVnode、newEndVnode
  2. 万1oldStartVnode和newStartVnode是同一节点,调用patchVnode进行patch,然后将oldStartVnode和newStartVnode都设置为下1个子节点,重复上述流程
    88必发官网手机版 1
  3. 要是oldEndVnode和newEndVnode是同1节点,调用patchVnode进行patch,然后将oldEndVnode和newEndVnode都安装为上二个子节点,重复上述流程
    88必发官网手机版 2
  4. 假定oldStartVnode和newEndVnode是同1节点,调用patchVnode实行patch,假如removeOnly是false,那么能够把oldStartVnode.elm移动到oldEndVnode.elm之后,然后把oldStartVnode设置为下二个节点,newEndVnode设置为上1个节点,重复上述流程
    88必发官网手机版 3
  5. 若果newStartVnode和oldEndVnode是同壹节点,调用patchVnode进行patch,要是removeOnly是false,那么能够把oldEndVnode.elm移动到oldStartVnode.elm此前,然后把newStartVnode设置为下二个节点,oldEndVnode设置为上贰个节点,重复上述流程
    88必发官网手机版 4
  6. 假若上述都不包容,就尝试在oldChildren中追寻跟newStartVnode具备一样key的节点,如若找不到同一key的节点,表达newStartVnode是四个新节点,就创办1个,然后把newStartVnode设置为下三个节点
  7. 若果上一步找到了跟newStartVnode同样key的节点,那么通过此外属性的相比较来剖断那1个节点是还是不是是同二个节点,纵然是,就调用patchVnode实行patch,要是removeOnly是false,就把newStartVnode.elm插入到oldStartVnode.elm以前,把newStartVnode设置为下贰个节点,重复上述流程
    88必发官网手机版 5
  8. 假定在oldChildren中一向不寻找到newStartVnode的同壹节点,那就创办1个新节点,把newStartVnode设置为下1个节点,重复上述流程
  9. 1旦oldStartVnode跟oldEndVnode重合了,并且newStartVnode跟newEndVnode也重合了,那几个轮回就甘休了

切实代码如下:

  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0 // 旧头索引
    let newStartIdx = 0 // 新头索引
    let oldEndIdx = oldCh.length - 1 // 旧尾索引
    let newEndIdx = newCh.length - 1 // 新尾索引
    let oldStartVnode = oldCh[0] // oldVnode的第一个child
    let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最后一个child
    let newStartVnode = newCh[0] // newVnode的第一个child
    let newEndVnode = newCh[newEndIdx] // newVnode的最后一个child
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,证明diff完了,循环结束
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      // 如果oldVnode的第一个child不存在
      if (isUndef(oldStartVnode)) {
        // oldStart索引右移
        oldStartVnode = oldCh[  oldStartIdx] // Vnode has been moved left

      // 如果oldVnode的最后一个child不存在
      } else if (isUndef(oldEndVnode)) {
        // oldEnd索引左移
        oldEndVnode = oldCh[--oldEndIdx]

      // oldStartVnode和newStartVnode是同一个节点
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // patch oldStartVnode和newStartVnode, 索引左移,继续循环
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[  oldStartIdx]
        newStartVnode = newCh[  newStartIdx]

      // oldEndVnode和newEndVnode是同一个节点
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // patch oldEndVnode和newEndVnode,索引右移,继续循环
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]

      // oldStartVnode和newEndVnode是同一个节点
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // patch oldStartVnode和newEndVnode
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        // 如果removeOnly是false,则将oldStartVnode.eml移动到oldEndVnode.elm之后
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        // oldStart索引右移,newEnd索引左移
        oldStartVnode = oldCh[  oldStartIdx]
        newEndVnode = newCh[--newEndIdx]

      // 如果oldEndVno