当前位置: 首页 > news >正文

【三十天精通Vue 3】第十六天 Vue 3 的虚拟 DOM 原理详解

请添加图片描述

引言

Vue 3 的虚拟 DOM 是一种用于优化 Vue 应用程序性能的技术。它通过将组件实例转换为虚拟 DOM,并在组件更新时递归地更新虚拟 DOM,以达到高效的渲染性能。在 Vue 3 中,虚拟 DOM 树由 VNode 组成,VNode 是虚拟 DOM 的基本单元。VNode 具有自己的类型和结构,并且可以通过补丁算法进行更新。

一、Vue 3 的虚拟 DOM 树结构

未命名.png

1.1 VNode 的基本结构

一个 VNode 对象的基本结构如下:

{// 节点类型,比如元素节点、文本节点等type: String | Function,// 节点的 props,比如 class、style、事件等props: Object,// 节点的子节点,可以是一个 VNode 数组或者是文本内容children: Array<VNode> | String | Number | null,// 节点的 key 值,用于优化 diff 算法key: String | Number | null,// 节点的 ref 值,用于访问该节点的引用ref: String | Function | null
}

其中,type 表示节点的类型,可以是元素节点的标签名,也可以是组件的构造函数;props 表示节点的属性,包括 class、style、事件等;children 表示节点的子节点,可以是一个 VNode 数组或者是文本内容;key 表示节点的 key 值,用于优化 diff 算法;ref 表示节点的 ref 值,用于访问该节点的引用。

1.2 VNode 的类型

在 Vue 3 中,VNode 的类型可以分为以下几种:

  • 元素节点:表示一个 HTML 元素,比如 <div><p> 等;
  • 组件节点:表示一个 Vue 组件,比如 <MyComponent>
  • 文本节点:表示一个纯文本节点,比如 Hello, world!
  • 注释节点:表示一个注释节点,比如 <!-- 注释内容 -->

不同类型的 VNode 会在渲染时使用不同的逻辑进行处理。

1.3 VNode 的 Patch 补丁算法

在 Vue 3 中,使用了 Patch 补丁算法来比较新旧 VNode 树的差异,并进行 DOM 更新。Patch 补丁算法包括以下几个步骤:

  1. 首先比较新旧 VNode 的类型和 key 值,如果不相同,则直接替换节点;

  2. 如果新旧 VNode 的类型和 key 值相同,则比较它们的属性和子节点:

    • 如果属性不同,则更新节点的属性;
    • 如果子节点不同,则递归比较子节点,并更新 DOM。
  3. 如果旧VNode没有子节点,但新VNode有子节点,则将旧VNode的文本内容清空,并添加新的子节点;

  4. 如果新VNode没有子节点,但旧VNode有子节点,则删除旧的子节点;

  5. 如果新旧 VNode 都是文本节点,则直接更新文本内容;

  6. 如果新旧 VNode 的子节点都是同类型的节点,就会使用 Diff 算法来进一步比较子节点的差异,并进行 DOM 更新。

Patch 补丁算法主要的优点是能够最小化 DOM 操作,减少页面重绘的次数,提高性能。而且它可以在非常高效的时间内对大型的 VNode 树进行更新操作。

下面是一个简单的示例,展示了如何使用 Patch 补丁算法来更新 DOM:

<div id="app"></div>
javascriptCopy code
// 创建旧的 VNode 树
const oldVNode = h('div', { class: 'container' }, [h('h1', { style: 'color: red' }, 'Hello World!'),h('p', 'This is a paragraph.')
])// 将旧的 VNode 渲染到页面上
render(oldVNode, document.getElementById('app'))// 创建新的 VNode 树
const newVNode = h('div', { class: 'container' }, [h('h1', { style: 'color: green' }, 'Hello Vue 3!'),h('p', 'This is a new paragraph.')
])// 使用 Patch 补丁算法比较新旧 VNode,更新 DOM
patch(oldVNode, newVNode)

在上面的示例中,我们首先创建了一个旧的 VNode 树,并将它渲染到页面上。然后创建了一个新的 VNode 树,并使用 Patch 补丁算法将新旧 VNode 进行比较和更新。在更新过程中,我们只需要更新了两个节点的属性和文本内容,而不需要重新创建整个 DOM 树,从而提高了性能。

二、Vue 3 的虚拟 DOM 更新过程

2.1 Diff 算法的过程

2.1.1 Diff 算法的核心思想

在更新 DOM 的过程中,Diff 算法的核心思想是找出新旧 VNode 树之间的差异,并只更新差异部分的 DOM,从而避免不必要的 DOM 操作,提高性能。

Diff 算法会对比新旧 VNode 树的节点,找出它们之间的差异。为了提高效率,Diff 算法会采用一些优化策略,如只比较同级节点、尽早终止比较、复用已有节点等。

2.1.2 Diff 算法的实现过程

  1. 比较两个数据结构之间的差异。

    • 差异可以表示为行差异或列差异。
    • 行差异:左数据结构中的行与右数据结构中的行不同。
    • 列差异:左数据结构中的列与右数据结构中的列不同。
  2. 将差异划分为行差异和列差异。

    • 将行差异转换为列差异。
    • 将列差异转换为行差异。
  3. 更新数据结构中的差异部分。

    • 将行差异或列差异更新到数据结构的对应位置。
    • 对于具有相同值的列,可以将其更新为相同的值。
    • 对于具有不同值的列,需要将其更新为新的值。

    下面是 Diff 算法的伪代码实现:

Diff(left, right)  if left == right  return []  if left.length == 0 or right.length == 0 return [left, right]  left_arr = Array(left.length)  right_arr = Array(right.length)  for i in 0..left.length-1  for j in 0..right.length-1  if left[i] == right[j]  left_arr[i] = right_arr[j]  else  left_arr[i] = Diff.diff_row(left[i], right[j])  right_arr[j] = Diff.diff_row(right[j], left[i])  return Diff.diff_col(left_arr, right_arr)  

2.2 Diff 算法的优化

2.2.1 Diff 算法的时间复杂度

Diff 算法的时间复杂度取决于 VNode 树的结构和节点数量,一般情况下是 O(n^3)。为了提高性能,Vue 3 中实现了一些优化策略,如只比较同级节点、尽早终止比较、复用已有节点等。

2.2.2 Key 值的作用

Key 值不仅能够帮助 Diff 算法建立节点之间的对应关系,还能够帮助优化 DOM 更新的过程。在更新 DOM 的时候,如果新旧 VNode 的 key 值相同,则可以认为它们是同一个节点,此时可以直接复用旧节点的 DOM 元素,而不需要进行 DOM 的删除和创建操作,从而提高 DOM 更新的效率。

此外,如果没有为节点指定 key 值,则 Diff 算法将默认使用节点在 VNode 树中的位置作为其 key 值。这种情况下,如果 VNode 树中的节点顺序发生变化,Diff 算法会误认为这是节点发生了变化,从而导致不必要的 DOM 更新。因此,在开发中,建议为每个节点指定唯一的 key 值,从而避免这种情况的发生。

2.2.3 双端比较算法

在 Diff 算法中,双端比较算法是一种常用的优化策略。该算法的核心思想是,从新旧 VNode 树的两端开始比较节点,如果发现新旧节点不同,则直接退出比较。这种方法可以有效地减少不必要的比较操作,从而提高 Diff 算法的效率。

2.2.4 Diff 算法的边界情况

在使用 Diff 算法时,需要注意一些边界情况,如以下几种情况:

  • 在进行 Diff 算法时,如果新 VNode 树为空,则直接删除旧 VNode 树中的所有节点;
  • 在进行 Diff 算法时,如果旧 VNode 树为空,则直接创建新 VNode 树中的所有节点;
  • 在进行 Diff 算法时,如果新旧 VNode 的类型不同,则直接替换节点;
  • 在进行 Diff 算法时,如果新旧 VNode 的 key 值不同,则认为它们是不同的节点,直接替换节点;
  • 在进行 Diff 算法时,如果新旧 VNode 的属性不同,则直接更新节点的属性;
  • 在进行 Diff 算法时,如果新旧 VNode 的子节点不同,则递归比较子节点,直到更新完所有子节点。

三、Vue 3 的虚拟 DOM 渲染流程

1.png

3.1 模板编译器的作用

3.1.1 模板编译器的过程

在 Vue 3 中,模板编译器的主要作用是将模板字符串转换为渲染函数。渲染函数是一个 JavaScript 函数,用于渲染组件的虚拟 DOM 树。

模板编译器的过程主要包括以下几个步骤:

  1. 解析模板字符串,生成抽象语法树(AST)。
  2. 遍历抽象语法树,生成渲染函数的代码。
  3. 将渲染函数的代码转换为 JavaScript 代码,并编译为可执行的函数。

在 Vue 3 中,模板编译器是可选的,也就是说,你可以使用手写的渲染函数代替模板编译器生成的渲染函数。

3.1.2 模板编译器的性能优化

为了提高模板编译器的性能,Vue 3 引入了以下几种优化方式:

  1. 缓存编译结果:将编译后的渲染函数缓存起来,下次渲染时直接使用缓存的渲染函数。
  2. 静态提升:将静态节点提升为常量,在渲染时只需要创建一次静态节点。
  3. 静态节点提取:将静态节点提取到单独的 VNode 中,避免每次重新渲染时都重新创建静态节点。
  4. 模板 inlining:将小型的模板内联到父级模板中,减少了模板编译器的工作量。

3.2 Vue 3 的渲染流程

3.2.1 Vue 3 的初始化流程

在初始化阶段,Vue 3 会做以下几件事情:

  1. 初始化组件实例:Vue 3 在创建组件实例时,会创建一个渲染上下文(render context)对象,并将其作为组件实例的属性 $vnode 存储起来。
  2. 创建虚拟 DOM 树:Vue 3 会通过调用 render 函数生成一个虚拟 DOM 树,并将其存储在渲染上下文对象中的 $vnode 属性中。
  3. 将虚拟 DOM 树转换成真实 DOM:Vue 3 会将 $vnode 属性中的虚拟 DOM 树转换成真实 DOM 树,并将其挂载到组件的根 DOM 元素上。

3.2.2 Vue 3 的更新流程

在更新阶段,Vue 3 会做以下几件事情:

  1. 判断是否需要更新:Vue 3 会通过比较新旧虚拟 DOM 树来判断组件是否需要更新。
  2. 执行更新:如果需要更新,Vue 3 会执行更新操作。更新操作包括计算出新的虚拟 DOM 树、比较新旧虚拟 DOM 树的差异、应用差异到真实 DOM 树上。
  3. 更新组件实例:更新组件的状态,包括 props 和 data 等属性的更新。

3.2.3 Vue 3 的卸载流程

在卸载阶段,Vue 3 会做以下几件事情:

  1. 执行 beforeUnmount 钩子函数:在组件实例被卸载之前,Vue 3 会执行组件的 beforeUnmount 钩子函数。
  2. 卸载子组件:Vue 3 会递归地卸载所有子组件。
  3. 卸载组件实例:Vue 3 会将组件实例从父组件中移除,并执行组件的 destroyed 钩子函数。同时,Vue 3 会将组件的根 DOM 元素从文档中移除,并销毁与之相关的事件监听器和定时器等资源。

四、Vue 3 的虚拟 DOM 与 React 的比较

2.png

4.1 Vue 3 的虚拟 DOM 与 React 的区别

  1. 模板语法 vs JSX: Vue 3 使用类似于 HTML 的模板语法,而 React 使用 JSX,一种类似于 JavaScript 的语法,需要使用特定的编译器转换为 JavaScript 代码。因此,Vue 3 更适合那些熟悉 HTML 的开发者,而 React 更适合那些更熟悉 JavaScript 的开发者。
  2. 响应式系统: Vue 3 内置了响应式系统,使得当状态发生改变时,组件能够自动地重新渲染。React 中需要使用 state 和 props 来管理组件的状态和属性,但并没有内置响应式系统。
  3. 性能优化: Vue 3 采用了静态分析技术,可以在编译时对模板进行优化,生成高效的渲染函数,从而提高渲染性能。React 使用了虚拟 DOM 技术,通过比较前后两个虚拟 DOM 树的差异,最小化 DOM 操作的次数,从而提高性能。
  4. API 设计: Vue 3 的 API 更加简单明了,通过一些简单的配置和选项,就能完成很多常见的操作,如组件化、路由、状态管理等。React 的 API 设计更加灵活,提供了更多的可定制化和可扩展性。

4.2 Vue 3 的虚拟 DOM 与 React 的共同点

  1. 虚拟 DOM: Vue 3 和 React 都使用虚拟 DOM 技术,通过在内存中构建虚拟 DOM 树来减少 DOM 操作,从而提高性能。
  2. 组件化: Vue 3 和 React 都支持组件化开发,将 UI 拆分为独立的组件,使得代码更加可维护、可重用。
  3. 单向数据流: Vue 3 和 React 都遵循单向数据流的原则,即数据只能从父组件向子组件传递,子组件不能直接修改父组件的数据。这种机制使得应用程序更加可靠,易于调试和维护。
  4. 生命周期函数: Vue 3 和 React 都提供了一些生命周期函数,允许开发者在组件生命周期的不同阶段执行一些操作,如组件挂载、更新、卸载等。这些生命周期函数使得开发者能够更好地管理组件的状态和行为。

五、Vue 3 的虚拟 DOM 的应用

4.png

5.1 Vue 3 的虚拟 DOM 在组件化开发中的应用

在 Vue 3 中,组件是基本的构建块,因此使用虚拟 DOM 的优势在于组件的渲染和更新。每个组件都有自己的虚拟 DOM 树,这使得 Vue 3 在渲染组件时更加高效和快速。当组件的状态发生变化时,Vue 3 将仅更新该组件的虚拟 DOM 树,而不是重新渲染整个页面。

此外,Vue 3 还引入了 Teleport 组件,它可以使组件在 DOM 树中的位置移动而不会影响其状态。这对于需要在页面上移动或动态显示的组件非常有用,例如弹出框或下拉菜单。

5.2 Vue 3 的虚拟 DOM 在动态组件中的应用

在 Vue 3 中,动态组件是一种允许组件动态切换的技术。这使得开发者可以根据应用程序的需要,在不同的组件之间进行快速的切换,而不需要重新加载整个页面。这种技术在构建单页应用程序时非常有用。

Vue 3 的虚拟 DOM 可以非常有效地渲染和更新动态组件,使其在应用程序中具有更高的性能和可靠性。同时,使用 Vue 3 的虚拟 DOM 还可以更轻松地管理动态组件之间的状态,并确保在切换组件时不会丢失状态信息。

六、Vue 3 的虚拟 DOM 的优势和不足

33.png

6.1 Vue 3 的虚拟 DOM 的优势

以下是 Vue 3 的虚拟 DOM 的优势:

  1. 性能提升:Vue 3 的虚拟 DOM 采用了优化策略,使得在更新组件时只更新必要的部分,从而提高了性能。
  2. 更好的可维护性:通过将组件的结构抽象为虚拟 DOM,可以更好地进行组件的维护和管理,也方便进行单元测试。
  3. 更好的跨平台兼容性:通过使用虚拟 DOM,Vue 3 可以将组件的渲染方式抽象为函数调用,从而实现跨平台的渲染兼容性,例如在浏览器、服务器端渲染等环境中都可以使用同样的代码渲染组件。
  4. 更好的动画支持:Vue 3 的虚拟 DOM 支持通过 transition、animation 等方式进行动画渲染,从而提供更好的动画效果。
  5. 更好的开发体验:通过使用虚拟 DOM,开发者可以在开发过程中方便地进行组件的调试和修改,从而提高了开发效率。

6.2 Vue 3 的虚拟 DOM 的不足

以下是 Vue 3 的虚拟 DOM 的不足:

  1. 内存占用较高:由于虚拟 DOM 需要在内存中维护组件树的状态,因此在大型应用中可能会占用较多的内存资源。
  2. 学习成本高:Vue 3 的虚拟 DOM 需要掌握一定的概念和使用方法,因此学习成本可能较高。
  3. 不适用于所有场景:在一些简单的场景下,使用虚拟 DOM 可能会增加代码复杂度,不如直接操作 DOM。

七、Vue 3 的虚拟 DOM 的最佳实践

222.png

7.1 使用响应式数据更新 VNode 树

在 Vue 2 中,当我们更新数据时,需要手动触发更新 DOM 的操作。在 Vue 3 中,我们可以通过使用 data 选项来定义虚拟 DOM 的数据,并通过使用 updateVirtualDOM 方法来更新虚拟 DOM。当我们需要更新虚拟 DOM 时,我们可以使用 updateVirtualDOM 方法,该方法接受两个参数:要更新的虚拟 DOM 树和新的虚拟 DOM 树。

下面是一个使用 updateVirtualDOM 方法更新虚拟 DOM 树的示例:

export default {  setup() {  const socket = io();return {  socketMessage(data) {  this.$updateVirtualDOM(  data.message,  JSON.parse(JSON.stringify(data.message))  );  },  };  },  
};  

在上面的示例中,当接收到消息时,我们通过调用 $updateVirtualDOM 方法更新虚拟 DOM 树。这个方法接受两个参数:要更新的虚拟 DOM 树和新的虚拟 DOM 树。在更新虚拟 DOM 树时,我们将新的数据解析成 JSON 字符串,并将其作为第一个参数传递给 updateVirtualDOM 方法。第一个参数指定了要更新的虚拟 DOM 树,第二个参数指定了更新后的虚拟 DOM 树。

7.2 使用 Key 值进行优化

在 Vue 2 中,当我们更新数据时,我们需要手动触发更新 DOM 的操作。这可能会导致性能问题,因为每次数据更新时,Vue 都会重新渲染整个组件。在 Vue 3 中,我们可以通过使用 updateVirtualDOM 方法来更新虚拟 DOM,这可以大大提高性能。然而,仍然存在一些性能问题,特别是在大型组件中。为了进一步提高性能,我们可以使用 key 值对组件进行优化。

在 Vue 3 中,key 值的作用是为组件生成唯一的标识符。当组件被重新渲染时,key 值会发生变化,这使得 Vue 无法直接渲染整个组件,而是只重新渲染需要更新的部分。下面是一个简单的示例:

export default {  data() {  return {  message: 'Hello Vue 3!',  };  },  methods: {  updateMessage() {  this.message = 'Hello Vue 3!';  },  },  setup() {  const socket = io();return {  socketMessage(data) {  this.$updateVirtualDOM(  { message: data.message },  JSON.parse(JSON.stringify({ message: data.message })))  },  };  },  
};  

在上面的示例中,当接收到消息时,我们通过调用 $updateVirtualDOM 方法更新虚拟 DOM 树。在这个示例中,我们将新的 message 值作为第一个参数传递给 updateVirtualDOM 方法,并将其作为第二个参数传递给方法 socketMessage。这样,Vue 3 将只重新渲染需要更新的部分,从而提高性能。

在 Vue 3 中,我们可以通过使用 Keep-Alive 组件来缓存组件,从而减少不必要的虚拟 DOM 渲染。Keep-Alive 组件是一个内置组件,它可以将挂载在其上的组件缓存起来,只有在组件主动被卸载或重新挂载时才会真正重新渲染。

下面是一个简单的 Keep-Alive 组件示例:

import { keepAlive } from 'vue';export default {  name: 'KeepAliveExample',  components: {  KeepAlive: keepAlive({  cache: true,  updateOn: 'load',  bind: true,  }),  },  
};  

在上面的示例中,我们使用 keepAlive 组件来缓存一个组件。注意,缓存组件的 key 应该使用一个唯一的标识符,例如组件名称加上版本号。在组件被重新挂载时,Vue 会检查该组件的缓存是否存在,如果存在,则直接使用缓存,否则重新渲染组件。

使用 Keep-Alive 组件可以有效地减少组件重新渲染的次数,提高页面渲染效率。

7.3 减少不必要的 DOM 操作

在 Vue 3 中,我们可以通过优化组件的生命周期方法来减少不必要的 DOM 操作。在 Vue 3 中,组件的生命周期方法包括:beforeCreate、created、beforeMount、mounted、beforeUnmount 和 destroyed。我们可以在这些生命周期方法中执行一些操作,例如更新数据或更新 DOM,但这些操作并不一定需要在每次渲染时执行。

下面是一个简单的示例:

import { createMount } from 'vue';export default {  name: 'MyComponent',  setup() {  const cache = createMount(this, {  data() {  return {  value: 'initial value',  };  },  props: {  value: {  type: String,  default: '',  },  },  ref: 'my-component',  });return {  cache,  };  },  
};  

在上面的示例中,我们创建了一个缓存组件,并在其 setup 方法中使用 createMount 函数来创建缓存组件。注意,在 setup 方法中,我们可以使用缓存组件的 ref 属性来访问缓存组件。这可以让我们在每次渲染时都使用相同的 DOM 元素,而不必每次都创建一个新的 DOM 元素。

通过使用缓存组件和优化组件的生命周期方法,我们可以有效减少不必要的 DOM 操作,从而提高页面渲染效率。

7.4 避免频繁的组件卸载和重新挂载

在 Vue 3 中,我们可以通过避免频繁的组件卸载和重新挂载来提高页面渲染效率。在 Vue 3 中,组件的卸载和重新挂载过程是非常耗时的,因为它们需要重新渲染整个组件。因此,我们应该尽可能避免频繁地使用组件卸载和重新挂载。

下面是一个简单的示例:

import { createMount } from 'vue';export default {  name: 'MyComponent',  setup() {  const cache = createMount(this, {  data() {  return {  value: 'initial value',  };  },  props: {  value: {  type: String,  default: '',  },  },  ref: 'my-component',  });// 添加一些定时器,用于在每次渲染后等待一段时间  cache.$nextTick(() => {  setTimeout(() => {  // 执行一些操作,例如更新 DOM 或更新数据  cache.$forceUpdate();  }, 500);  });return {  cache,  };  },  
};  

在上面的示例中,我们创建了一个缓存组件,并在其 setup 方法中使用 createMount 函数来创建缓存组件。我们还在缓存组件中添加了一些定时器,用于在每次渲染后等待一段时间。这可以让我们在每次渲染时都可以有效地避免频繁的组件卸载和重新挂载。

八、Vue 3 的虚拟 DOM 的常见问题及解决方案

8.1 如何提高 VNode 的性能

以下是一些提高 VNode 性能的方法:

  1. 避免不必要的渲染:Vue 3 会根据响应式数据自动重新渲染页面,但是有时候我们并不需要重新渲染整个页面,可以使用 Vue 3 提供的 shouldUpdate 方法来判断是否需要重新渲染组件。
  2. 合理使用计算属性:计算属性可以缓存一些计算结果,避免重复计算,提高性能。
  3. 减少 VNode 的层级:VNode 的层级越深,渲染所需的时间就越长。因此,尽量将组件的嵌套层级降到最低。
  4. 使用函数式组件:函数式组件没有响应式数据,也没有实例,因此渲染速度更快。
  5. 合理使用异步组件:异步组件可以将一些不必要的组件延迟加载,提高页面的加载速度。

2211.png

8.2 如何使用 Key 值进行优化

在渲染列表时,使用 key 值可以帮助 Vue 3 更好地跟踪每个 VNode 的状态,从而提高渲染性能。以下是一些使用 key 值进行优化的方法:

  1. 确保 key 值具有唯一性:每个 key 值都应该是唯一的,这样 Vue 3 才能正确地追踪每个 VNode 的状态。
  2. 不要使用索引作为 key 值:使用索引作为 key 值可能会导致渲染错误,因为当列表的顺序发生变化时,索引也会发生变化,从而导致 key 值不唯一。
  3. 使用动态 key 值:在一些情况下,动态生成 key 值可以更好地满足需求,比如在渲染动态组件时。

8.3 如何使用 Keep-Alive 缓存组件

使用 Keep-Alive 缓存组件可以避免频繁的组件销毁和创建,从而提高页面的性能。以下是一些使用 Keep-Alive 的方法:

  1. 在组件外层包裹一个 Keep-Alive 组件:这样包裹的组件会被缓存起来,当下次需要渲染时,就会直接使用缓存中的组件,而不是重新创建。
  2. 在需要缓存的组件上添加一个 name 属性:这样 Vue 3 才能正确地缓存该组件。
  3. 在需要销毁缓存的组件时,使用 $destroy 方法:这样可以手动销毁缓存的组件,从而释放内存。

在这里插入图片描述

相关文章:

【三十天精通Vue 3】第十六天 Vue 3 的虚拟 DOM 原理详解

引言 Vue 3 的虚拟 DOM 是一种用于优化 Vue 应用程序性能的技术。它通过将组件实例转换为虚拟 DOM&#xff0c;并在组件更新时递归地更新虚拟 DOM&#xff0c;以达到高效的渲染性能。在 Vue 3 中&#xff0c;虚拟 DOM 树由 VNode 组成&#xff0c;VNode 是虚拟 DOM 的基本单元…...

Arduino ESP8266通过udp获取时间以及同步本地时间方法

Arduino ESP8266通过udp获取时间以及同步本地时间 ✨通过udp获取NTP服务器上的时间戳,然后经过转换,得到当前具体的时间。转换相对复杂,对于获取时间还是相对比较准确。📝通过udp获取时间实现代码 #include <ESP8266WiFi.h> #include <WiFiUdp.h>//填写 WiFi…...

c/c++:char*定义常量字符串,strcmp()函数,strcpy()函数,寻找指定字符,字符串去空格

c/c&#xff1a;char*定义常量字符串&#xff0c;strcmp()函数&#xff0c;strcpy()函数&#xff0c;寻找指定字符&#xff0c;字符串去空格 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学会c的话&#xff0c; 我所…...

2023年6月DAMA-CDGA/CDGP数据治理认证考试可报名地区公布

2023年4月23日&#xff0c;据DAMA中国官方信息&#xff0c;目前6月DAMA-CDGA/CDGP数据治理认证考试开放报名地区有&#xff1a;北京、上海、广州、深圳、长沙、呼和浩特。目前南京、济南、西安、杭州等地区还在接近开考人数中&#xff0c;打算6月考试的朋友们可以抓紧时间报名啦…...

UDS的0x19服务介绍

什么是 UDS&#xff1f; UEI (Unified Diagnostic Services&#xff0c;统一诊断服务) 是一种在车辆电子控制单元 (ECU) 之间交换诊断信息的标准通信协议&#xff0c;它是OBD-II的某些扩展。利用 UDS 协议&#xff0c;诊断工程师可以访问车辆的各种功能&#xff0c;如读取故障…...

QinQ技术与Portal技术

QinQ 802.1Q-in-802.1Q&#xff0c;是一种扩展VLAN标签技术。在城域网中&#xff0c;需要大量的VLAN来隔离区分不同的用户&#xff0c;但是原有的802.1Q只有12个比特&#xff0c;仅能标识4096个VLANQinQ即在802.1Q的基础上&#xff0c;再增加一层外层标签。使得可以标识4096*40…...

Vue-自定义表单验证(rule,value,callback)详细使用

前言 最近在实际开发中遇到需要验证合同编号是否在数据库已经存在&#xff0c;自定义表单验证。 的表单验证大家都知道form绑定rules&#xff0c;prop绑定值与form.值一样&#xff0c;必填&#xff0c;失去焦点触发 提示信息。 今天我们讲一讲自定义验证规则具体使用场景和它…...

港联证券|TMT板块全线退潮,这些个股获主力逆市抢筹

计算机、电子、传媒、通讯职业流出规模居前。 今天沪深两市主力资金净流出709.92亿元&#xff0c;其中创业板净流出218.36亿元&#xff0c;沪深300成份股净流出187.92亿元。 资金流向上&#xff0c;今天申万一级职业普跌&#xff0c;除了国防军工职业小幅上涨&#xff0c;获主…...

WPF学习

一、了解WPF的框架结构 &#xff08;第一小节随便看下就可以&#xff0c;简单练习就行&#xff09; 1、新建WPF项目 xmlns&#xff1a;XML的命名空间 Margin外边距&#xff1a;左上右下 HorizontalAlignment&#xff1a;水平位置 VerticalAlignment&#xff1a;垂直位置 2…...

C#使用WebDriver模拟浏览器操作WEB页面

有时候需要模拟访问页面触发某个功能&#xff0c;可以使用WebDriver来实现这一功能&#xff0c;驱动打开浏览器&#xff0c;并对页面重定向以及对页面写入脚本等操作。 安装Selenium.Chrome&#xff0c;Selenium.Support.UI&#xff0c;Selenium 引入 using OpenQA.Selenium.…...

正则表达式 - 简单模式匹配

目录 一、测试数据 二、简单模式匹配 1. 匹配字面值 2. 匹配数字和非数字字符 3. 匹配单词与非单词字符 4. 匹配空白字符 5. 匹配任意字符 6. 匹配单词边界 7. 匹配零个或多个字符 8. 单行模式与多行模式 一、测试数据 这里所用文本是《学习正则表达式》这本书带的&a…...

银行数字化转型导师坚鹏:银行数字化转型培训方案

目录 一、银行数字化转型培训背景 二、银行数字化转型模型 三、银行数字化转型课程设计思路 四、 银行数字化转型课程基本介绍 五、 银行数字化转型课程设置 六、银行数字化转型课程大纲 七、培训方案实施流程 一、银行数字化转型培训背景 2020年1月3日&#xff…...

多维时序 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多变量时间序列预测

多维时序 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多变量时间序列预测 目录 多维时序 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多变量时间序列预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-CNN-…...

Shell知识点(一)

1.echo 命令 echo命令的作用是在屏幕输入一行文本&#xff0c;可以降该命令的参数原样输出。 $ echo hello world hello world 如果想要输出的是多行文本&#xff0c;包含换行符&#xff0c;这时就需要把多行文本放在引号里面 $ echo "<HTML><HEAD><TITLE…...

mysql 索引失效、联合索引失效场景和举例

索引失效 假设有一张user 表&#xff0c;表中包含索引 (id); (name); (birthday); (name,age); 对索引字段进行函数操作 select name from user where year(birthday) 2000;使用模糊查询&#xff0c;查询中使用通配符 select name from user where name like %益达%;使用i…...

快速将PDF转换为图片:使用在线转换器的步骤

PDF文件是一种常见的文档格式&#xff0c;但在某些情况下需要将其转换为图片格式&#xff0c;例如将PDF文件插入PPT演示文稿中。此时&#xff0c;使用在线PDF转换器是一种快速且简便的方法。 本文将介绍如何使用在线转换器将PDF文件转换为图片格式。 步骤1&#xff1a;选择合…...

什么是gpt一4-如何用上gpt-4

怎么使用gpt-4 目前GPT-4还未正式发布或公开&#xff0c;因此也没有详细的对接说明。但是我们可以根据GPT-4的前身GPT-3的应用经验&#xff0c;以及GPT-4的预期功能推测一些可能的使用步骤&#xff1a; 选择适合的GPT-4实现技术&#xff1a;GPT-4可能有不同的实现技术&#xff…...

Docker 相关概念

1、Docker是什么&#xff1f; 如何确保应用能够在这些环境中运行和通过质量检测&#xff1f;并且在部署过程中不出现令人头疼的版本、配置问题&#xff0c;也无需重新编写代码和进行故障修复&#xff1f; 答案就是使用容器。Docker之所以发展如此迅速&#xff0c;也是因为它对…...

STM32平衡小车 TB6612电机驱动学习

TB6612FNG简介 单片机引脚的电流一般只有几十个毫安&#xff0c;无法驱动电机&#xff0c;因此一般是通过单片机控制电机驱动芯片进而控制电机。TB6612是比较常用的电机驱动芯片之一。 TB6612FNG可以同时控制两个电机&#xff0c;工作电流1.2A&#xff0c;最大电流3.2A。 VM电…...

动态加载 JS 文件

动态加载JS文件是指在网页运行过程中通过JavaScript代码向页面中动态添加外部JS文件&#xff0c;这种方式能够提高页面加载速度和用户体验&#xff0c;并且可以帮助网站实现更多的功能和特效。 本文将详细介绍动态加载JS文件的基本原理、优势、注意事项以及具体实现方法&#…...

14、lldb调试指令

LLDB LLDB(Low Lever Debug): 默认内置于Xcode中的动态调试工具.标准的lldb提供了一组广泛的命令,旨在与老版本的GDB命令兼容.除了使用标准配置外,还可以很容易地自定义lldb以满足实际需要. 1.1 lldb语法: <command> [<subcommand> [<subcommand>...]] &l…...

浏览器缓存策略:强缓存和协商缓存

浏览器缓存&#xff1a;其实就是在本地使用的计算机中开辟一个内存区&#xff0c;同时也开辟一个硬盘区&#xff0c;作为数据传输的缓冲区&#xff0c;然后利用这个缓冲区来暂时保护用户以前访问的信息通常浏览器的缓存策略分为两种&#xff1a;强缓存和协商缓存&#xff0c;强…...

2023年Chat GPT 应用前景分析

从2022年12月初刚上线至今&#xff0c;不到半年时间ChatGPT月活就超过了1亿用户&#xff01;可谓火的一塌糊涂&#xff0c;比尔盖茨都称&#xff1a;ChatGPT的历史意义重大&#xff0c;不亚于PC或互联网诞生。以至于ChatGPT官网长期都处于满负荷运转的状态&#xff01; 由于Ch…...

并发计算公式

常用并发数计算公式&#xff1a;N[(n0.8SP)/(T0.2)]*R 其中&#xff1a; n为系统用户数&#xff1b; S为每个用户发生的业务笔数&#xff08;QPS&#xff09;&#xff1b; P为每笔业务所需要访问服务器的时间&#xff0c;单位为秒&#xff1b; T为使用业务的时间&#xff0c;单…...

“华为杯”研究生数学建模竞赛2020年-【华为杯】E题:能见度估计与预测(附获奖论文及python代码实现)

​​​​​​​ 目录 摘 要: 一、问题背景与问题重述 1.1 问题背景 1.2 问题重述...

Arduino学习笔记3

一.RGB三色小灯实验 1.源代码 int rgb_R11;//接到板子上面的PWM口11 R int rgb_G9;//接到板子上面的PWM口9 G int rgb_B10;//接到板子上面的PWM口10 B void setup() {pinMode(rgb_R,OUTPUT);//设置rgb_R的控制口为输出模式pinMode(rgb_G,OUTPUT);//设置rgb_G的控制口为输出模…...

BPMN2.0 任务-用户任务

“用户任务(user task)”用于对需要人工执行的任务进行建模。当流程执行到达用户任务时,会为指派至该任务的用户或组的任务列表创建一个新任务。 用户任务用左上角有一个小用户图标的标准任务(圆角矩形)表示。 用户任务在XML中如下定义。其中id是必须属性,name是可选属性…...

David Silver Reinforcement Learning -- Markov process

1 Introduction 这个章节介绍关键的理论概念。 马尔科夫过程的作用&#xff1a; 1&#xff09;马尔科夫过程描述强化学习环境的方法&#xff0c;环境是完全能观测的&#xff1b; 2&#xff09;几乎所有的RL问题可以转换成MDP的形式&#xff1b; 2 Markov Processes 2.1 Mark…...

项目结束倒数2

今天,解决了,多个点的最短路问题 用的dfs,配上了floyed计算出的广源距离 难点是要记录路线,dfs记录路线就很烦 但是好在结束了,经过无数的测试,确保没啥问题(应该把) 来看看我的代码 void dfs(int b[], int x, int* sum, int last, int sums, int a[], BFS& s, Floyd_A…...

VBA智慧办公9——图例控件教程

如图&#xff0c;利用VBA进行可视化交互界面的设计&#xff0c;在界面中我们用到了label&#xff0c;button&#xff0c;text&#xff0c;title等多个工具&#xff0c;在进行框图效果的逐一实现后可进行相应的操作和效果实现。 VBA&#xff08;Visual Basic for Applications&a…...