Vue2.x-虚拟DOM和diff算法(笔记)

源码链接

Vue2.x-虚拟DOM和diff算法(笔记)

简述

  • diff 算法
  以装修房子为例,如果我们仅需要在客厅新添一座沙发或者将卧室的床换个位置。那么将整个房子重新翻修显然是不切实际的,我们通常的做法是在原先装修的基础上做微小的改动即可。
  对于 DOM 树来讲,也是同样的道理,如果仅仅是新增了一个标签或者修改了某一个标签的属性或内容。那么引起整个 DOM 树的重新渲染显然是对性能和资源的极大浪费,虽然我们的计算机每秒能进行上亿次的计算。实际上,我们只需要找出新旧 DOM 树存在差异的地方,只针对这一块区域进行重新渲染就可以了。
  所以 Diff 算法应运而生,diff 取自 different (不同的),Diff算法的作用,总结来说,就是:精细化对比,最小量更新。
  • diff 实例
<div class="box">
  <h3>我是一个标题</h3>
  <ul>
    <li>牛奶</li>
    <li>咖啡</li>
    <li>可乐</li>
  </ul>
</div>
<div class="box">
  <h3>我是一个标题</h3>
  <span>我是一个新的span</span>
  <ul>
    <li>牛奶</li>
    <li>咖啡</li>
    <li>可乐</li>
    <li>雪碧</li>
  </ul>
</div>
  如上:从第一个 DOM 树变为第二个 DOM 树,我们不需要将整个 DOM 推倒重来,而只需要在原有 DOM 树的基础上,在适当的位置新增一个 span 标签和一个 li 标签即可。
  • 真实 DOM
<div class="box">
  <h3>我是一个标题</h3>
  <ul>
    <li>牛奶</li>
    <li>咖啡</li>
    <li>可乐</li>
  </ul>
</div>
  • 虚拟 DOM
{
   
  "sel": "div",
  "data": {
   
    "class": {
    "box": true }
  },
  "children": [
    {
   
      "sel": "h3",
      "data": {
   },
      "text": "我是一个标题"
    },
    {
   
      "sel": "ul",
      "data": {
   },
      "children": [
        {
    "sel": "li", "data": {
   }, "text": "牛奶" },
        {
    "sel": "li", "data": {
   }, "text": "咖啡" },
        {
    "sel": "li", "data": {
   }, "text": "可乐" }
      ]
    }
  ]
}

snabbdom

简介

  • snabbdom 是瑞典语单词,单词原意为“速度”
  • snabbdom 是著名的虚拟 DOM 库,是 diff 算法的鼻祖,Vue 源码就是借鉴了 snabbdom
  • 官方 Githttps://github.com/snabbdom/snabbdom

安装

  • git 上的 snabbdom 源码是用 TypeScript 写的,git 上并不提供编译好的 JavaScript 版本
  • 如果要直接使用 build 出来的 JavaScript 版的 snabbdom 库,可以从 npm 上下载:npm i -S snabbdom
  • 学习库底层时,建议大家阅读原汁原味的代码,最好带有库作者原注释。这样对你的源码阅读能力会有很大的提升。

测试环境搭建

  • snabbdom 库是 DOM 库,当然不能在 nodejs 环境运行,所以我们需要搭建 webpackwebpack-dev-server 开发环境,好消息是不需要安装任何loader
  • 这里需要注意,必须安装最新版 webpack@5,不能安装 webpack@4,这是因为 webpack@4 没有读取身份证(package.json)中 exports 的能力,建议大家使用这样的版本:
    npm i -D webpack@5 webpack-cli@3 webpack-dev-server@3
    
  • 参考 webpack 官网,书写好 webpack.config.js 文件
    // https://webpack.docschina.org/
    const path = require('path')
    
    module.exports = {
         
      // 入口
      entry: './src/index.js',
      // 出口
      output: {
         
        // 虚拟打包路径,就是说文件夹不会真正生成,而是在 8080 端口虚拟生成,不会真正的物理生成
        publicPath: 'xuni',
        // 打包出来的文件名
        filename: 'bundle.js'
      },
      devServer: {
         
        // 端口号
        port: 8080,
        // 静态资源文件夹
        contentBase: 'www'
      }
    }
    
  • 新建目录 src -> 新建文件 src/index.js -> 新建目录 www -> 新建文件 www/index.html
    /** src/index.js */
    alert(123)
    
    <!-- www/index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <h1>你好!!!</h1>
      <script src="xuni/bundle.js"></script>
    </body>
    </html>
    
  • package.json 文件中新增命令:
    {
         
      "scripts": {
         
        "dev": "webpack-dev-server",
      }
    }
    
  • 终端运行 npm run dev
  • 访问:http://localhost:8080/http://127.0.0.1:8080/xuni/bundle.js, 可以看到 www/index.htmlxuni/bundle.js 文件的内容
  • 跑通 snabbdom 官方 git 首页的 demo 程序,即证明调试环境已经搭建成功(将下面代码复制到 src/index.js 中)
    import {
          init } from 'snabbdom/init'
    import {
          classModule } from 'snabbdom/modules/class'
    import {
          propsModule } from 'snabbdom/modules/props'
    import {
          styleModule } from 'snabbdom/modules/style'
    import {
          eventListenersModule } from 'snabbdom/modules/eventlisteners'
    import {
          h } from 'snabbdom/h' // helper function for creating vnodes
    
    var patch = init([
      // Init patch function with chosen modules
      classModule, // makes it easy to toggle classes
      propsModule, // for setting properties on DOM elements
      styleModule, // handles styling on elements with support for animations
      eventListenersModule // attaches event listeners
    ])
    
    var container = document.getElementById('container')
    
    var vnode = h('div#container.two.classes', {
          on: {
          click: function () {
         } } }, [
      h('span', {
          style: {
          fontWeight: 'bold' } }, 'This is bold'),
      ' and this is just normal text',
      h('a', {
          props: {
          href: '/foo' } }, "I'll take you places!")
    ])
    // Patch into empty DOM element – this modifies the DOM as a side effect
    patch(container, vnode)
    
    var newVnode = h(
      'div#container.two.classes',
      {
          on: {
          click: function () {
         } } },
      [
        h(
          'span',
          {
          style: {
          fontWeight: 'normal', fontStyle: 'italic' } },
          'This is now italic type'
        ),
        ' and this is still just normal text',
        h('a', {
          props: {
          href: '/bar' } }, "I'll take you places!")
      ]
    )
    // Second `patch` invocation
    patch(vnode, newVnode) // Snabbdom efficiently updates the old view to the new state
    
  • 不要忘记在 www/index.html 中放置一个 div#container
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div id="container"></div>
      <script src="xuni/bundle.js"></script>
    </body>
    </html>
    
  • 刷新页面,此时页面会出现一段文本
    This is now italic type and this is still just normal textI'll take you places!
    

虚拟 DOM 和 h 函数

虚拟 DOM

  • 虚拟 DOM:用 JavaScript 对象描述 DOM 的层次结构。DOM 中的一切属性都在虚拟 DOM 中有对应的属性。
  • diff 是发生在虚拟 DOM 上的:新虚拟 DOM 和老虚拟 DOM 进行 diff (精细化比较),算出应该如何最小量更新,最后反映到真实的 DOM
  • DOM
    {
         
      "sel": "div",
      "data": {
         
        "class": {
          "box": true }
      },
      "children": [
        {
         
          "sel": "h3",
          "data": {
         },
          "text": "我是一个标题"
        },
        {
         
          "sel": "ul",
          "data": {
         },
          "children": [
            {
          "sel": "li", "data": {
         }, "text": "牛奶" },
            {
          "sel": "li", "data": {
         }, "text": "咖啡" },
            {
          "sel": "li", "data": {
         }, "text": "可乐" }
          ]
        }
      ]
    }
    
  • diff 之后的新 DOM
    {
         
      "sel": "div",
      "data"
  • 29
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值