virtualdom(虚拟dom)(代码片段)

X可乐 X可乐     2023-03-24     430

关键词:

文章目录

什么是 Virtual DOM

  • Virtual DOM(虚拟 DOM)是由普通的 JS 对象来描述 DOM 对象
  • 真实 DOM 成员
    let element = document.querySelector('#app')
    let s = ''
    for (var key in element) 
        s += key + ','
    
    console.log(s)
    
  • 使用 Virtual DOM 来描述真实 DOM
    
        sel: 'div',
        data: ,
        children: undefined,
        text: 'hi Virtual DOM',
        elm: undefined,
        key: undefined
    
    

为什么要使用 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)
    • 通过模块可扩展
    • 源码使用 TS 开发
    • 最快的 Virtual DOM 之一
  • virtual-dom

Snabbdom 基本使用

安装 parcel

md snabbdom-demo
cd snabbdom-demo
npm init -y
npm install parcel-bundler -D

配置 scripts

"scripts": 
    "dev": "parcel index.html --open",
    "build": "parcel build index.html"

目录结构

  • snabbdom-demo
    • index.html
    • package.json
    • src(文件目录)
      • 01-basicusage.js

导入 Snabbdom

Snabbdom 文档

  • 看文档的意义
    • 学习任何一个库都要先看文档
    • 通过文档了解库的作用
    • 看文档中提供的实例,自己快速实现一个 demo
    • 通过文档查看 API
  • Snabbdom 文档
    • https://github.com/snabbdom/snabbdom
    • 当前版本 v2.1.0
  • 安装 Snabbdom
    • npm i Snabbdom@2.1.0
  • 导入 Snabbdom
    • Snabbdom 的两个核心函数 init 和 h()
      • init() 是一个高阶函数,赶回 patch()
      • h() 返回虚拟节点 VNode,这个函数我们在使用 Vue.js 的时候见过
    • 文档中导入的方式
      import  init  from 'snabbdom/init'
      import  h  from 'snabbdom/h'
      const patch = init([])
      
    • 实际导入的方式
      • parcel/webpack 4 不支持 package.json 中的 exports 字段
        import  init  from 'snabbdom/build/package/init'
        import  h  from 'snabbdom/build/package/h'
        

基本使用

import  init  from 'snabbdom/build/package/init'
import  h  from 'snabbdom/build/package/h'

// 1. 通过 h 函数创建 VNode
let vNode = h('div#box.container', '新内容')

// 获取挂载元素
const dom = document.querySelector('#app')

// 2. 通过 init 函数 得到 patch 函数
const patch = init([])

// 3. 通过 patch,将 vNode 渲染到 DOM
let oldVNode = patch(dom, vNode)
/*
    patch 接收两个参数
        1. 旧节点
        2. 新节点

    存在多个 vNode 状态的时候可以做新旧节点对比

    参数1 也可以传 DOM,传入 DOM 时,会先将 DOM 转换为 vNode,然后在做对比

    最后返回更新以后的 vNode 值
*/

// 4. 创建新的 VNode 更新给 oldVNode
vNode = h('p#text.ptext', '这是 P 标签的内容')
patch(oldVNode, vNode)

包含子节点

import  init  from 'snabbdom/build/package/init'
import  h  from 'snabbdom/build/package/h'

const patch = init([])

// 创建包含子节点的 VNode
let vNode = h('div#box', [
    h('h1', '标题文本'),
    h('p', '内容文本')
])
/*
    h 函数 的第二个参数为字符串时,直接作为当前创建元素的内容

    如果是数组,代表传入的是子节点的列表,内部应该传入 vNode
*/

// 获取挂载元素
const dom = document.querySelector('#app')

// 渲染 vNode
const oldVNode = patch(dom, vNode)

// 生成一个注释节点,替换当前节点,用来清空节点
patch(oldVNode, h('!'))

Snabbdom 中的模块

模块的作用

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

官方提供的模块

  • attributes
  • props
  • dataset
  • class
  • style
  • eventlisteners

模块的使用步骤

  • 导入需要的模块
  • init() 中注册模块
  • h() 函数的第二个参数处使用模块

模块代码演示

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. 注册模块(为 patch 函数添加模块对应的能力)
const patch = init([
    styleModule,
    eventListenersModule
])

let vNode = h('div#box', 
    style: 
        width: '200px',
        height: '200px',
        lineHeight: '100px',
        textAlign: 'center',
        backgroundColor: 'green'
    ,
    on: 
        click() 
            console.log('div被点击了')
        
    
, [
    h('h1.text', 
        style: 
            color: 'white'
        ,
        on: 
            click() 
                console.log('h1被点击了')
            
        
    ,'标题文本'),
    h('p.text', '标题文本')
])

const dom = document.querySelector('#app')

patch(dom, vNode)

Snabbdom 源码解析

  • 如何学习源码
    • 宏观了解
    • 带着目标看源码
    • 看源码的过程要不求甚解
    • 调试
    • 参考资料
  • Snabbdom 的核心
    • init() 设置模块,创建 patch() 函数
    • 使用 h() 函数创建 JS 对象(VNode)描述真实 DOM
    • patch() 比较新旧两个 VNode
    • 把变化的内容更新到真实 DOM 树
  • Snabbdom 源码
    • 地址:https://github.com/snabbdom/snabbdom
    • 当前版本:v2.1.0
    • 克隆代码:git clone -b v2.1.0 --depth=1 https://github.com/snabbdom/snabbdom.git

h 函数

  • 作用:创建 VNode 对象
  • Vue 中的 h 函数
new Vue(
  router,
  store,
  render: h => h(App)
).$mount('#app')
  • 函数重载
    • 参数个数或参数类型不同的函数
    • JS 中没有重载的概念
    • TS 中有重载,不过重载的实现还是通过代码调整参数
  • 函数重载—参数个数
    function add (a: number, b: number) 
      console.log(a + b)
    
    function add (a: number, b: number, c: number) 
      console.log(a + b + c)
    
    add(1, 2)
    add(1, 2, 3)
    
  • 函数重载—参数类型
    function add (a: number, b: number) 
      console.log(a + b)
    
    function add (a: number, b: string) 
      console.log(a + b)
    
    add(1, 2)
    add(1, '2')
    

patch 整体过程分析

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

Diff 算法

  • 虚拟 DOM 中的 Diff 算法
    • 查找两颗树每一个节点的差异
  • Snabbdom 根据 DOM 的特点对传统的 diff 算法做了优化
    • DOM 操作时候很少会跨级别操作
    • 只比较同级别的节点

执行过程

  • 在对开始和结束节点比较的时候,总共有四种情
    • oldStartVnode / newStartVnode (旧开始节点 / 新开始节点)
    • oldEndVnode / newEndVnode (旧结束节点 / 新结束节点)
    • oldStartVnode / newEndVnode (旧开始节点 / 新结束节点)
    • oldEndVnode / newStartVnode (旧结束节点 / 新开始节点)

新旧开始节点

  • 如果新旧开始节点是 sameVnode (key 和 sel 相同)
    • 调用 patchVnode() 对比和更新节点
    • 把旧开始和新开始索引往后移动 oldStartIdx++ / newStartIdx++

旧开始节点 / 新结束节点

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

旧结束节点 / 新开始节点

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

非上述四种情况

  • 遍历新节点,使用 newStartNode 的 key 在老节点数组中找相同节点
  • 如果没有找到,说明 newStartNode 是新节点
    • 创建了新节点对应的 DOM 元素,插入到 DOM 树种
  • 如果找到了
    • 判断新节点和找到的老节点的 sel 选择器是否相同
    • 如果不相同,说明节点被修改了
      • 重新创建对应的 DOM 元素,插入到 DOM 树中
    • 如果相同,把 elmToMove 对应的 DOM 元素,移动到左边

循环结束

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

oldStartIdx > oldEndIdx

  • 如果老节点的数组先遍历完(oldStartIdx > oldEndIdx)
    • 说明新节点有剩余,把剩余节点批量插入到右边

newStartIdx > newEndIdx

  • 如果新节点的数组先遍历完(newStartIdx > newEndIdx)
  • 说明老节点有剩余,把剩余节点批量删除

虚拟dom(代码片段)

...DOM节点,结果仅仅是轻量级的JavaScript对象,我们称之为virtualDOM。虚拟DOM是React的一大亮点,具有batching(批处理)和高效的Diff算法。这让我们可以无需担心性能问题而”毫无顾忌”的随时“刷新”整个页面,由虚拟DOM来确保只对界... 查看详情

为什使用虚拟dom(代码片段)

...m当说起vue和react时候,大家都不免会提到一个概念,就是VirtualDOM(虚拟Dom)。那么,这个虚拟Dom到底是个什么东西,为什么这两个伟大的框架都要使用呢。首先VirtualDOM是一个映射真实DOM的JavaScript对象,如果需要改变任何元素的... 查看详情

为什使用虚拟dom(代码片段)

...m当说起vue和react时候,大家都不免会提到一个概念,就是VirtualDOM(虚拟Dom)。那么,这个虚拟Dom到底是个什么东西,为什么这两个伟大的框架都要使用呢。首先VirtualDOM是一个映射真实DOM的JavaScript对象,如果需要改变任何元素的... 查看详情

深度剖析:如何实现一个virtualdom算法#13(代码片段)

...m/livoras/blog/issues/13目录:1前言2对前端应用状态管理思考3VirtualDOM算法4算法实现4.1步骤一:用JS对象模拟DOM树4.2步骤二:比较两棵虚拟DOM树的差异4.3步骤三:把差异应用到真正的DOM树上5结语6References1前言本文会在教你怎么用300~400... 查看详情

如何理解vue,virtualdom(代码片段)

Vue.js2.0窥探之VirtualDOM到底是什么?Vue.js2.0已经发布,并且在其中新添加如了一些新功能。其中一个功能就是“VirtualDOM”。 VirtualDOM是什么?在之前,React和Ember早就开始用虚拟DOM技术来提高页面更新的速度了。若想了解它是... 查看详情

虚拟dom(virtualdom)

一、虚拟DOMVirtualDOM(虚拟DOM)不是真实的DOM对象,它是由普通的JS对象来描述DOM对象。真实DOM对象它的成员非常多,所以创建真实DOM的成本非常高。创建一个虚拟DOM(普通的JavaScript对象)的属性非常少,所以创建一个虚拟DOM的... 查看详情

react虚拟dom浅析(代码片段)

...通常是性能瓶颈产生的原因,为此,React引入了虚拟DOM(VirtualDOM)的机制。什么是虚拟DOM?虚拟DOMVS直接操作原生DOM?虚拟DOMVSMVVM?对React虚拟DOM的误解?一、什么是虚拟DOM?在React中,render执行的结果得到的并不是真正的DOM节点... 查看详情

总结1135-图解虚拟dom之diff算法(代码片段)

1.目录1.相关知识点:2.虚拟DOM(VirtualDOM)2.1.什么是虚拟DOM2.2.为什么要使用虚拟DOM2.3.虚拟dom库3.Diff算法4.snabbdom的核心4.1.init函数4.2.h函数4.3.patch函数(核心)4.4.patchVnode4.5.题外话:diff算法简介4.6.updateChildren(核中核:判断子节点的差异)5.最... 查看详情

总结1135-图解虚拟dom之diff算法(代码片段)

1.目录1.相关知识点:2.虚拟DOM(VirtualDOM)2.1.什么是虚拟DOM2.2.为什么要使用虚拟DOM2.3.虚拟dom库3.Diff算法4.snabbdom的核心4.1.init函数4.2.h函数4.3.patch函数(核心)4.4.patchVnode4.5.题外话:diff算法简介4.6.updateChildren(核中核:判断子节点的差异)5.最... 查看详情

virtualdom算法

...react.js javascript目录:1前言2对前端应用状态管理思考3VirtualDOM算法4算法实现4.1步骤一:用JS对象模拟DOM树4.2步骤二:比较两棵虚拟DOM树的差异4.3步骤三:把差异应用到真正的DOM树上5结语6References1前言本文会在教你怎么用300~400... 查看详情

[react]你知道virtualdom的工作原理吗?

[react]你知道VirtualDOM的工作原理吗?VirtualDOM是什么:虚拟DOM是真实DOM的javascript对象的映射VirtualDOM的工作原理:数据驱动视图更新这个过程中,首先会改变虚拟DOM对象,一个视图的更新周期,首先会简单新... 查看详情

part3-2-2vue.js源码剖析-虚拟dom(代码片段)

什么是虚拟DOM虚拟DOM(VirtualDOM)是使用JavaScript对象来描述DOM,虚拟DOM的本质就是JavaScript对象,使用JavaScript对象来描述DOM的结构。应用的各种状态变化首先作用于虚拟DOM,最终映射到DOM。Vue.js中的虚拟DOM借鉴了Snabbdom... 查看详情

基于vue认识虚拟dom(virtualdom)

参考技术A       VirtualDOM这个概念相信大家不会太陌生,他产生的前提是浏览器中的DOM是很“昂贵”的,为了直观的感受,我们可以把一个简单的div元素属性都打印出来,如下图所示:    &... 查看详情

react的虚拟dom

ReactJs的一大特点就是引进了虚拟dom(VirtualDOM)的概念。为什么我们需要VirtualDOM,VirtualDOM给我们带来了什么优势。首先我们要了解一下浏览器的工作流。当我们从一个服务拿到请求的html时,浏览器会怎么办?(1)创建DOM树一旦... 查看详情

虚拟domvituraldomtree

  提起VirtualDOM,总是给人一种高深莫测的感觉,大家都知道它比DOM快。那么VirtualDOM到底是何方神圣呢?在深入理解VirtualDOM之前,先让我们回顾一下DOM。一、什么DOM?所谓DOM,就是HTML、XML、XHTML的一种抽象描述,它会把这... 查看详情

diff算法看不懂就一起来锤我(带图)(代码片段)

前言面试官:"你了解虚拟DOM(VirtualDOM)跟Diff算法吗,请描述一下它们";我:"额,...鹅,那个",完了????,突然智商不在线,没组织好语言没答好或者压根就答不出来;所以这次我总结一下相关的知识点,让你可以有一个清晰的认知之... 查看详情

如何理解虚拟dom(代码片段)

...来,js对象也可以构建出虚拟的DOM树。“这就是所谓的VirtualDOM算法。包括几个步骤:用JavaScript对象结构表 查看详情

如何实现一个virtualdom及源码分析

如何实现一个VirtualDOM及源码分析VirtualDOM算法  web页面有一个对应的DOM树,在传统开发页面时,每次页面需要被更新时,都需要手动操作DOM来进行更新,但是我们知道DOM操作对性能来说是非常不友好的,会影响页面的重排... 查看详情