Vue源码解析--更新中

news2024/11/19 7:31:38

【尚硅谷】Vue源码解析之虚拟DOM和diff算法

【Vue源码】图解 diff算法 与 虚拟DOM-snabbdom-最小量更新原理解析-手写源码-updateChildren]

文章目录

    • 2. snabbdom 简介 及 准备工作
      • 2.1 简介
      • 2.2 搭建初始环境
        • 1. 安装snabbdom
        • 2. 安装webpack5并配置
        • 3. 复制官方demo Example
    • 3. h函数的介绍与使用
      • 3.1 介绍
      • 3.2 使用h函数 创建虚拟节点
      • 3.3 使用patch函数 将虚拟节点上DOM树
      • 3.4 h函数嵌套使用,得到虚拟DOM树(重要)
    • 4. 手写h函数
    • 5. 手写diff算法准备
      • 5.1 diff算法原理
      • 5.2 手写diff预备
        • 5.2.1 源码中如何定义“同一个节点”
        • 5.2.2 源码中创建子节点,需要递归
    • 6. 手写diff——首次上DOM树patch(container, myVnode1)
      • 6.0 DOM 预备知识
        • 6.0.1 Node.insertBefore()
        • 6.0.2 Node.appendChild()
        • 6.0.3 Element.tagName
        • 6.0.4 Node.removeChild
        • 6.0.5 document.createElement
      • 6.1 patch.js

2. snabbdom 简介 及 准备工作

2.1 简介

snabbdom(瑞典语,“速度”)是著名的虚拟DOM库,是diff算法的鼻祖
Vue源码借鉴了snabbdom
源码使用TypeScript写的https://github.com/snabbdom/snabbdom
从npm下载的是build出来的JavaScript版本
-D 是开发dev版本的依赖 -S是项目真正依赖

2.2 搭建初始环境

1. 安装snabbdom

npm init
npm install -D snabbdom

下载好的源码在node_modules
在这里插入图片描述
需要读取一些init等文件,使用webpack5,不能是webpack4(读取的地址不对)。接下来安装webpack5

2. 安装webpack5并配置

cnpm i -D webpack@5 webpack-cli@3 webpack-dev-server@3

配置webpack5
根目录下创建webpack.config.js

cnpm i -D webpack@5 webpack-cli@3 webpack-dev-server@3

“dev”: “webpack-dev-server” npm run dev实际跑的是npm run webpack-dev-server会读取webpack.config.js文件
在这里插入图片描述
webpack.config.js配置文件


module.exports = {
    // webpack5 不用配置mode
    // 入口
    entry: "./src/index.js",
    // 出口
    output: {
      // 虚拟打包路径,文件夹不会真正生成,而是在8080端口虚拟生成
      publicPath: "xuni",
      // 打包出来的文件名
      filename: "bundle.js",
    },
    // 配置webpack-dev-server
    devServer: {
      // 静态根目录
      contentBase: 'www',
      // 端口号
      port: 8080,
    },
  };
  

3. 复制官方demo Example

src/index.js

import {
  init,
  classModule,
  propsModule,
  styleModule,
  eventListenersModule,
  h,
} from "snabbdom";

const 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
]);

const container = document.getElementById("container");

const 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);

const 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(默认访问该文件)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <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>

npm run dev跑起来
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3. h函数的介绍与使用

3.1 介绍

diff算法发生在 虚拟DOM上,新旧虚拟DOM的比较
h 函数产生虚拟节点
在这里插入图片描述
在这里插入图片描述
虚拟节点vnode的属性

{
	children: undefined// 子元素 数组
	data: {} // 属性、样式、key
	elm: undefined // 对应的真正的dom节点(对象),undefined表示节点还没有上dom树
	key: // 唯一标识
	sel: "" // 选择器
	text: "" // 文本内容
}

3.2 使用h函数 创建虚拟节点

3.3 使用patch函数 将虚拟节点上DOM树

src/index.js

import {
    init,
    classModule,
    propsModule,
    styleModule,
    eventListenersModule,
    h,
  } from "snabbdom";
  

  
var myVnode1 = h('a', {
    props: {
        href: "http://www.baidu.com",
        target: '_blank',
      }
}, '百度')
// const myVnode2 = h('div', '我是盒子' )
const myVnode3 = h('div', {class:{'box': true}},  '我是盒子')

console.log(myVnode1)
//   将虚拟节点渲染成真实节点 需要使用patch函数
const 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
  ]);
  
const container = document.getElementById("container");
patch(container, myVnode1)

在这里插入图片描述
console.log(myVnode1)的输出
在这里插入图片描述

3.4 h函数嵌套使用,得到虚拟DOM树(重要)

在这里插入图片描述

const myVnode4 = h('ul', [
    h('li', '苹果1'),
    h('li', '苹果2'),
    h('li', '苹果3'),
    h('li', '苹果4'),
])
console.log(myVnode1)
//   将虚拟节点渲染成真实节点 需要使用patch函数
const 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
  ]);
  
const container = document.getElementById("container");
patch(container, myVnode4)

在这里插入图片描述

const myVnode4 = h('ul', [
    h('li', '苹果1'),
    h('li', [
        h('div', [
            h('p', "香蕉皮"),
            h('p', "苹果皮")
        ])
    ]),
    h('li', h('span', '西瓜')),
    h('li', '番茄'),
])

在这里插入图片描述

4. 手写h函数

参考ts版本的h函数,手写Js版本
在这里插入图片描述
看h函数源码,h最后调用vnode函数
在这里插入图片描述
h函数有很多形式,下面只是部分的形式。我们将写三个参数的h函数,进行基本的学习,参数1:标签 参数2 {} 参数三[]或者文字
在这里插入图片描述
在自己的src下创建如下文件,参考源码写h函数
在这里插入图片描述

源码中vnode函数是将接收的参数整合成一个对象,返回。我们自己手写vnode也是如此
在这里插入图片描述
src/mysnabbdom/vnode.js


export default function (sel, data, children, text, elm) {
    // sel, data, children, text, elm 参考function vnode
    return {sel, data, children, text, elm}
}

h函数源码中,对传入的参数进行判断,最后调用vnode函数
在这里插入图片描述
src\mysnabbdom\h.js

import vnode from "./vnode";

// 编写一个低配版的h函数,这个函数必须接受3个参数,缺一不可
/*

h('div', {}, '文字')
h('div', {}, [])
h('div', {}, h())
*/
export default function (sel, data, c) {
    // 检查参数个数
    if (arguments.length != 3) {
        throw new Error("低配版的h函数必须三个参数")
    }
    // 检查参数c的类型
    if (typeof c == 'string' || typeof c == 'number') {
        //调用这种版本 h('div', {}, '文字')
        return vnode(sel, data,undefined, c, undefined)
    } else if (Array.isArray(c)) {
        let children = []
        // h('div', {}, []) ,[]内部是h函数,h函数返回的是对象,需要对数组每一项判断
        for (let i = 0; i < c.length; ++i) {
            if (!(typeof c[i] == "object" && c[i].hasOwnProperty('sel'))) {
                throw new Error("传入的数组参数中有项不是h函数")
            }
            // 这里需不要执行c[i]因为c[i]已经执行过了,在[h()]数组内已经执行了,此时的c[i]是执行后的结果
            // 收集children
            children.push(c[i])
        }
        // 循环结束,children收集完毕
        return vnode(sel,  data, children, undefined, undefined)


    } else if (typeof c == 'object' && c.hasOwnProperty('sel')) {
        // h('div', {}, h()) 内部的h返回的是对象并且有sel属性
        let children = [c]
        return vnode(sel,data,children, undefined, undefined)

        
    } else {
        throw new Error("传入的第三个参数不正确")
    }
}

在这里插入图片描述
src\index.js调用自己得h函数,其他函数调用官方的,进行渲染

// 导入自己的h函数
import h from './mysnabbdom/h'
import {
    init,
    classModule,
    propsModule,
    styleModule,
    eventListenersModule,
  } from "snabbdom";
  

let b = h('li', { }, [
    h('li', {  }, "xx1"),
    h('div', { }, "xx2")

])
//   将虚拟节点渲染成真实节点 需要使用patch函数
const 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
]);
const container = document.getElementById("container");

patch(container, b)

在这里插入图片描述

5. 手写diff算法准备

5.1 diff算法原理

最小量更新,key很关键。key是这个节点的唯一标识,告诉diff算法,在更改前后它们是同一个DOM节点。
问题: 如何定义是同一个虚拟节点
答:选择器相同且key相同

只进行同层比较,不会进行跨层比较。即使是同一片 虚拟节点,但是跨层了,diff就是暴力删除旧的,然后插入新的
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// 导入自己的h函数
import h from './mysnabbdom/h'
import {
    init,
    classModule,
    propsModule,
    styleModule,
    eventListenersModule,
} from "snabbdom";


let vnode1 = h('ul', {}, [
    h('li', { key: 'A' }, 'A'),
    h('li', { key: 'B' }, 'B'),
    h('li', { key: 'C' }, 'C'),
    h('li', { key: 'D' }, 'D'),

])
let vnode2 = h('li', {}, [
    h('session', { key: 'SS' },
        [
            h('li', { key: 'A' }, 'A'),
            h('li', { key: 'B' }, 'B'),
            h('li', { key: 'C' }, 'C'),
            h('li', { key: 'D' }, 'D'),
            h('li', { key: 'E' }, 'E'),
        ])

])
//   将虚拟节点渲染成真实节点 需要使用patch函数
const 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
]);
const container = document.getElementById("container");
patch(container, vnode1)
const btn = document.getElementById("btn");
btn.addEventListener("click", function () {
    // 点击按钮,将vnode1变成vnode2
    // patch 意思是修补 ,对虚拟节点vnode1、vnode2对比执行最小量更新
    patch(vnode1, vnode2)
})

更改顺序时,我的是部分更新,老师的是全部都没有更新
在这里插入图片描述

5.2 手写diff预备

在这里插入图片描述

5.2.1 源码中如何定义“同一个节点”

最新的版本是比较:选择器sel相同, key相同, data相同, text相同
老版本的比较: 选择器sel相同, key相同
在这里插入图片描述

5.2.2 源码中创建子节点,需要递归

6. 手写diff——首次上DOM树patch(container, myVnode1)

6.0 DOM 预备知识

6.0.1 Node.insertBefore()

var insertNode = parentNode.insertBefore(newNode, referenceNode);

insertedNode :被插入节点(newNode)
parentNode :新插入节点的父节点
newNode :用于插入的节点
referenceNode :newNode 将要插在这个节点之前
在当前节点下增加一个子节点 Node,并使该子节点位于参考节点的前面。

6.0.2 Node.appendChild()

element.appendChild(aChild)

将一个节点附加到指定父节点的子节点列表的末尾处。
如果将被插入的节点已经存在于当前文档的文档树中,那么 appendChild() 只会将它从原先的位置移动到新的位置(不需要事先移除要移动的节点)。

6.0.3 Element.tagName

返回当前元素的标签名

elementName = element.tagName

elementName 是一个字符串,包含了element元素的标签名.
在HTML文档中, tagName会返回其大写形式

6.0.4 Node.removeChild

从DOM中删除一个子节点。返回删除的节点

let oldChild = node.removeChild(child);
//OR
element.removeChild(child);

child 是要移除的那个子节点.
node 是child的父节点.
oldChild保存对删除的子节点的引用. oldChild === child.

6.0.5 document.createElement

var element = document.createElement(tagName[, options]);

tagName:指定要创建元素类型的字符串, 创建元素时的 nodeName 使用 tagName 的值为初始化,该方法不允许使用限定名称(如:“html:a”),在 HTML 文档上调用 createElement() 方法创建元素之前会将tagName 转化成小写,在 Firefox、Opera 和 Chrome 内核中,createElement(null) 等同于 createElement(“null”)
返回 新建的元素(Element)

6.1 patch.js


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何把歌曲里的伴奏音乐提取出来,分享几个方法给大家!

对于一首歌&#xff0c;我们都知道&#xff0c;它有两部分组成&#xff1a;背景音乐人声。这两者合在一起&#xff0c;便是我们经常听的歌。部分用户想要直接获取歌曲伴奏&#xff0c;那么可以在UU伴奏网上下载。 操作方法比较简单&#xff0c;直接搜索想要的歌曲名称就可以了…

【分布式系统与一致性协议】

分布式系统与一致性协议 CAP原理APCPCA总结BASE理论 一致性拜占庭将军问题 分布式系统是一个硬件或软件组件分布在不同的网络计算机上&#xff0c;彼此之间仅仅通过消息传递进行通信和协调的系统。 分布式系统的设计目标一般包含如下&#xff1a; 可用性&#xff1a;可用性是分…

C++14中binary literals的使用

一个形如42的值被称作字面值常量(literal),这样的值一望而知。每个字面值常量都对应一种数据类型&#xff0c;字面值常量的形式和值决定了它的数据类型。 我们可以将整型字面值写作十进制(基数为10)、八进制(基数为8)或十六进制(基数为16)数的形式。以0开头的整数代表八进制数&…

Dijkstra算法求最短路

Dijkstra算法是单源最短路算法&#xff0c;是用来求一个点到其他所有点点最短距离&#xff0c;使用小根堆优化后时间复杂度大概为 O m l o g n Omlogn Omlogn 注意&#xff1a;不可以解决存在负权边的问题 【模板】单源最短路径&#xff08;标准版&#xff09; 链接&#xff1…

使用CRM系统通过四点快速收集客户信息

CRM是客户关系管理的缩写&#xff0c;它可以帮助企业管理销售、营销和客户服务&#xff0c;提升管理能力&#xff0c;获得更多收益。 那么&#xff0c;在CRM系统中客户是什么&#xff1f;如何快速收集客户信息呢&#xff1f;下面我们来说一说。 一、CRM中的客户是什么&#xf…

Java012——String引用数据类型的简单学习

回顾Java数据类型 本次要学习的是Java引用数据类型String 一、对String类简单说明 说明&#xff1a;String是Java中的一个类 二、String类的作用 作用&#xff1a;主要用来创建和操作字符串。 三、使用String类 3.1、创建字符串 注意&#xff1a; 1、字符串使用双引号&qu…

shell脚本:文本三剑客awk

awk-命令讲解&#xff1a; awk-命令讲解 一、awk&#xff1a;1.定义&#xff1a;2.格式&#xff1a;3.工作过程&#xff1a;4.工作原理&#xff1a;5.常用选项&#xff1a; 二、内置函数&#xff1a;1.getline的工作过程&#xff1a;2.打印&#xff1a;3.文件内容匹配过滤打印…

软考A计划-系统架构师-学习笔记-第四弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

信道编码的基本概念

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;https://github.com/timerring/information-theory 】或者公众号【AIShareLab】回复 信息论 获取。 文章目录 信道编码信道编码的…

[迁移学习]预训练和微调

一、概述 一般的有监督迁移学习分为以下三种&#xff1a; ①将训练好的模型作为特征抽取模块&#xff08;例如以resnet进行特征提取&#xff09; ②在一个相关的任务中训练后直接后直接使用(例如gpt) ③在训练好的模型基础上进行微调 此外还有无监督学习的方式 zero-shot&#…

jmeter004:察看结果树

元件添加路径&#xff1a;线程组>监听器>察看结果树 取样器结果&#xff1a; ps&#xff1a;取样器是可以显示变量所对应的值的 Thread Name&#xff1a;线程组名称 Sample Start&#xff1a;运行的开始时间 Load time&#xff1a;加载的时间、持续的时间 Connect Time&a…

从零玩转系列之微信支付开篇

一、前言 halo各位大佬很久没更新了最近在搞微信支付,因商户号审核了我半个月和小程序认证也找了资料并且将商户号和小程序进行关联,至此微信支付Native支付完成.此篇文章过长我将分几个阶段的文章发布(项目源码都有,小程序和PC端) 二、演示 微信支付Native案例 微信支付JSAPI案…

【MySQL】数据表的基本操作

目录 1. 创建表 2. 创建表案例 2.1 创建一个users表 2.2 查看表结构 2.3 修改表 3. 删除表 MySQL&#x1f337; 1. 创建表 语法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校验规则 engine 存储…

Blogger空闲短域名搜索代码

Blogger是一个由Google提供的&#xff08;收购的公司&#xff09;免费博客平台&#xff0c;旨在使个人博客创建变得容易。用户可以使用Blogger创建自己的博客并将其托管在Google的服务器上。它不需要任何费用&#xff0c;并提供简单易用的编辑器和主题来帮助用户轻松地创建博客…

网络安全学术顶会——SP 2023 议题清单、摘要与总结(下)

注&#xff1a;本文由ChatGPT与Claude联合生成 121、QueryX: Symbolic Query on Decompiled Code for Finding Bugs in COTS Binaries 可扩展的静态检查工具&#xff0c;如Sys和CodeQL&#xff0c;成功地发现了源代码中的错误。这些工具允许分析人员编写应用程序特定的规则&…

虚拟机角度下的线程

虚拟机角度下的线程 jvm与线程 一个 Java 应用程序通常只包含一个 JVM 进程&#xff08;Java Virtual Machine&#xff0c;Java虚拟机&#xff09;&#xff0c;但在某些情况下可能会有多个 JVM 进程。 一个 Java 应用通常是一个进程&#xff0c;这个进程就是jvm&#xff0c;编…

从Cookie到Session: Servlet API中的会话管理详解

文章目录 一. Cookie与Session1. Cookie与Session2. Servlet会话管理操作 二. 登录逻辑的实现 一. Cookie与Session 1. Cookie与Session 首先, 在学习过 HTTP 协议的基础上, 我们需要知道 Cookie 是 HTTP 请求报头中的一个关键字段, 本质上是浏览器在本地存储数据的一种机制,…

基本类型转换和引用类型转换

文章目录 前言基本类型转换自动数据类型转换强制数据类型转换 引用数据类型转换向上转型向下转型 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; Java 中的类型转换可以分为基本类型转换和引用类型转换两种。 基本类型转换指的是将一种基本数据类型转换…

Koa学习3:用户添加、错误处理

模型 在src目录下创建model目录&#xff0c;用来存放模型 创建用户模型 user.model.js 注意&#xff1a; UUID类型是无法自增的&#xff0c;将id设置为UUID类型时只需要为其指定默认值即可 // 数据类型 const { DataTypes } require(sequelize); // 导入已经连接了数据库…

集合导题、刷题、考试全套完整流程,专业强大的功能,提高刷题学习效率和企业的培训效率

土著刷题微信小程序v1.15&#xff0c;主要是迭代了考试模块的进阶功能&#xff0c;对考试模块进行了一次升级改造。 由于在v1.15开发期间&#xff0c;收到了违规内容整改的通告&#xff0c;为了遵守相关法律法规&#xff0c;让小程序能够平稳安全地运营下去&#xff0c;我们特此…