• 什么是虚拟DOM

      • 虚拟DOM,是由普通的JS对象来描述DOM对象
      • 使用虚拟DOM来描述真实DOM,创建虚拟DOM开销要比真实DOM小很多
    • 为什么要使用Virtual DOM

      • 前端开发刀耕火种的时代
      • MVVM框架解决视图和状态同步问题
      • 模板引擎可以简化视图操作,没办法跟踪状态
      • 虚拟DOM跟踪状态变化
      • 参考github上virtual-dom的动机描述

        • 虚拟DOM可以维护程序的状态,跟踪上一次的状态
        • 通过比较前后两次状态差异更新真实DOM
    • 虚拟DOM的作用

      • 维护视图和状态的关系
      • 复杂视图情况下提升渲染性能
      • 跨平台

        • 浏览器平台渲染DOM
        • 服务端渲染SSR(Nuxt.js/Next.js)
        • 原生应用(Weex/React Native)
        • 小程序(mpvue/uni-app)等
    • 虚拟DOM库

      • Snabbdom

        • Vue.js 2.x内部使用的虚拟DOM就是改造的Snabbdom
        • 大约200 SLOC(single line of code)
        • 通过模块可扩展
        • 源码使用TypeScript开发
        • 最快的Virtual DOM之一
      • virtual-dom
    • 创建项目

      • 安装parcel
      • 配置scripts
      • 目录结构
    • 安装parcel

      • md snabbdom-demo
      • cd snabbdom-demo
      • npm init -y
      • npm install parcel-bundler -D
      • Snabbdom文档

        • 学习任何一个库都要先看文档
        • 通过文档了解库的作用
        • 看文档中提供的示例,自己快速实现一个demo
        • 通过文档查看API的使用
      • Snabbdom文档

    • 模块的作用

      • Snabbdom的核心库并不能处理DOM元素的属性/样式/事件等,可以通过注册Snabbdom默认提供的模块来实现
      • Snabbdom中的模块可以用来扩展Snabbdom的功能
      • Snaddom中的模块的实现是通过注册全局的钩子函数来实现的
    • 模块的使用步骤

      • 导入需要的模块
      • init()中注册模块
      • h()函数的第二个参数 ```javascript import { init } from ‘snabbdom/build/package/init’ import { h } from ‘snabbdom/build/package/h’

      // 1. 导入模块 import { styleModule } from ‘snabbdom/build/package/modules/style’ import { eventListenersModule } from ‘snabbdom/build/package/modules/eventlisteners’

      // 2. 注册模块 const patch = init([ styleModule, eventListenersModule ])

      // 3. 使用h() 函数的第二个参数传入模块中使用的数据(对象) let vnode = h(‘div’, [ h(‘h1’, { style: { backgroundColor: ‘red’ } }, ‘Hello World’), h(‘p’, { on: { click: eventHandler } }, ‘Hello P’) ])

      function eventHandler () { console.log(‘别点我,疼’) }

      let app = document.querySelector(‘#app’) patch(app, vnode) ```

    • 如何学习源码

      • 宏观了解
      • 带着目标看源码
      • 看源码的过程要不甚解
      • 调试
      • 参考资料
    • Snabbdom的核心

      • init()设置模块,创建patch()函数
      • 使用h()函数创建JavaScript对象(VNode)描述真实DOM
      • patch()比较新旧两个Vnode
      • 把变化的内容更新到真实DOM树
    • h函数介绍

      • 作用: 创建VNode对象
      • Vue中的h函数
      • h函数最早见于hyperscript,使用JavaScript创建超文本
    • 函数重载

      • 参数个数或参数类型不同的函数
      • JavaScript中没有重载的概念
      • TypeScript中有重载,不过重载的实现还是通过代码调整参数
    • vscode常用快捷键

      • F12 或者 Ctrl + 左鼠标 能快速定位到函数的定义位置
      • Alt + <- 能立马回到刚才过来的位置
      • Alt + -> 能回到前面的位置
    • patch整体过程分析

      • patch(oldVnode, newVnode)
      • 把新节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理的旧节点
      • 对比新旧VNode是否相同节点(节点的key和sel相同)
      • 如果不是相同节点,删除之前的内容,重新渲染
      • 如果是相同节点,再判断新的VNode是否有text,如果有并且和oldVNode的text不同,直接更新文本内容
      • 如果新的VNode有children,判断子节点是否有变化
    • Diff 算法

      • 虚拟DOM中的Diff算法

        • 查找两颗树每一个节点的差异,传统的会把根节点与另一树上每一个节点做比较
        • Snbbdom根据DOM的特点对传统的diff算法做了优化

          • DOM操作时候很少会跨级级操作节点
          • 只比较同级别的节点
      • 开始和结束节点

        • 如果新旧开始节点是sameVnode(key和sel相同)

          • 调用patchVnode()对比和更新节点
          • 把旧开始和新开始索引往后移动 oldStartIdx++/oldEndIdex++
        • 旧开始节点/新结束节点

          • 调用patchVnode()对比和更新节点
          • 把oldStartVnode对应的DOM元素,移动到右边,更新索引
          • 把oldEndVnode对应的DOM元素,移动到左边,更新索引
        • 循环结束

          • 当老节点的所有子节点先遍历完(oldStartIdx > oldEndIdx),循环结束
          • 新节点的所有子节点先遍历完(newStartIdx > newEndIdx),循环结束
      • 设置key的意义

        • 需要给所有有相同父元素的子元素,设置具有唯一值的key,否则的话,有可能造成渲染错误
        • 当数据变化时,执行updateChild,内部对比子节点的时候,会判断新旧VNode的key是否相等,如果当前没有key或者相同,会重用当前的DOM元素,标签里的文字内容会更新,但不会产生新的DOM元素