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

Vue3 源码解析(十):watch 的实现原理

本篇文章笔者会讲解 Vue3 中侦听器相关的 api:watchEffect 和 watch 。在 Vue3 之前 watch 是 option 写法中一个很常用的选项,使用它可以非常方便的监听一个数据源的变化,而在 Vue3 中随着 Composition API 的写法推行也将 watch 独立成了一个 响应式 api,今天我们就一起来学习 watch 相关的侦听器是如何实现的。

👇 储备知识要求:

在阅读本文前,建议你已经学习过本系列的第 7 篇文章的 effect 副作用函数的相关知识,否则在讲解副作用的相关部分可能会出现不理解的情况。

watchEffect

由于 watch api 中的许多行为都与 watchEffect api 一致,所以笔者将 watchEffect 放在首位讲解,为了根据响应式状态自动应用和重新应用副作用,我们可以使用 watchEffect 方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在以来变更时重新运行该函数。

watchEffect 函数的实现非常简洁:

export function watchEffect(effect: WatchEffect,options?: WatchOptionsBase
): WatchStopHandle {return doWatch(effect, null, options)
}

首先来看参数类型:

export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => voidexport interface WatchOptionsBase {flush?: 'pre' | 'post' | 'sync'onTrack?: ReactiveEffectOptions['onTrack']onTrigger?: ReactiveEffectOptions['onTrigger']
}export type WatchStopHandle = () => void

第一个参数 effect,接收函数类型的变量,并且在这个函数中会传入 onInvalidate 参数,用以清除副作用。

第二个参数 options 是一个对象,在这个对象中有三个属性,你可以修改 flush 来改变副作用的刷新时机,默认为 pre,当修改为 post 时,就可以在组件更新后触发这个副作用侦听器,改同 sync 会强制同步触发。而 onTrack 和 onTrigger 选项可以用于调试侦听器的行为,并且两个参数只能在开发模式下工作。

参数传入后,函数会执行并返回 doWatch 函数的返回值。

由于 watch api 也会调用 doWatch 函数,所以 doWatch 函数的具体逻辑我们会放在后边讲。先看 watch api 的函数实现。

watch

这个独立出来的 watch api 与组件中的 watch option 是完全等同的,watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况下这个侦听是惰性的,即只有当被侦听的源发生变化时才执行回调。

与 watchEffect 相比,watch 有以下不同:

  • 懒性执行副作用
  • 更具体地说明说明状态应该处罚侦听器重新运行
  • 能够访问侦听状态变化前后的值

watch 函数的函数签名有许多种重载情况,且代码行数较多,所以笔者不准备分析每个重载情况,一起来看一下 watch api 的实现。

export function watch<T = any, Immediate extends Readonly<boolean> = false>(source: T | WatchSource<T>,cb: any,options?: WatchOptions<Immediate>
): WatchStopHandle {if (__DEV__ && !isFunction(cb)) {warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +`supports \`watch(source, cb, options?) signature.`)}return doWatch(source as any, cb, options)
}

watch 接收 3 个参数,source 侦听的数据源,cb 回调函数,options 侦听选项。

source 参数

source 的类型如下:

export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
type MultiWatchSources = (WatchSource<unknown> | object)[]

从两个类型定义看出,数据源支持传入单个的 Ref、Computed 响应式对象,或者传入一个返回相同泛型类型的函数,以及 source 支持传入数组,以便能同时监听多个数据源。

cb 参数

在这个最通用的声明中,cb 的类型是 any,但是其实 cb 这个回调函数也有他自己的类型:

export type WatchCallback<V = any, OV = any> = (value: V,oldValue: OV,onInvalidate: InvalidateCbRegistrator
) => any

在回调函数中,会提供最新的 value、旧 value,以及 onInvalidate 函数用以清除副作用。

options

export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {immediate?: Immediatedeep?: boolean
}

可以看到 options 的类型 WatchOptions 继承了 WatchOptionsBase,这也就是 watch 除了 immediate 和 deep 这两个特有的参数外,还可以传递 WatchOptionsBase 中的所有参数以控制副作用执行的行为。

分析完参数后,可以看到函数体内的逻辑与 watchEffect 几乎一致,但是多了在开发环境下检测回调函数是否是函数类型,如果回调函数不是函数,就会报警。

执行 doWatch 时的传参与 watchEffect 相比,多了第二个参数回调函数。

下面就让我们揭开这个终极 boss doWatch 的庐山真面目吧。

doWatch

不管是 watchEffect、watch 还是组件内的 watch 选项,在执行时最终调用的都是 doWatch 中的逻辑,这个强大的 doWatch 函数为了兼容各个 api 的逻辑源码也是挺长的大约有 200 行,所以老规矩,笔者会将长源码拆分开来讲。若想阅读完整源码请戳这里。

先从 doWatch 的函数签名看起:

function doWatch(source: WatchSource | WatchSource[] | WatchEffect | object,cb: WatchCallback | null,{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ,instance = currentInstance
): WatchStopHandle

这个函数签名与 watch 基本一致,多了一个 instance 的参数,默认值为 currentInstance,currentInstance 是当前调用组件暴露出来的一个变量,方便该侦听器找到自己对应的组件。

而 source 在这里的类型就比较清晰,支持单个的 source 或者数组,也只是一个普通对象。

接着会创建三个变量,getter 最终会当做副作用的函数参数传入,forceTrigger 标识是否需要强制更新,isMultiSource 标记传入的是单个数据源还是以数组形式传入的多个数据源。

let getter: () => any
let forceTrigger = false
let isMultiSource = false

然后会开始判断 source 的类型,根据不同的类型重置这三个参数的值。

  • ref 类型
    • 访问 getter 函数会获取到 source.value 值,直接解包。
    • forceTrigger 标记会根据是否是 shallowRef 来设置。
  • reactive 类型
    • 访问 getter 函数直接返回 source,因为 reactive 的值不需要解包获取。
    • 由于 reactive 中往往有多个属性,所以会将 deep 设置为 true,这里可以看出从外部给 reactive 设置 deep 是无效的。
  • 数组 array 类型
    • 将 isMultiSource 设置为 true。
    • forceTrigger 会根据数组中是否存在 reactive 响应式对象来判断。
    • getter 是一个数组形式,是 source 内各个元素的单个 getter 结果。
  • source 是函数 function 类型
    • 如果有回调函数
      • getter 就是 source 函数执行的结果,这种情况一般是 watch api 中的数据源以函数的形式传入。
    • 如果没有回调函数,那么此时就是 watchEffect api 的场景了。
      • 此时会为 watchEffect 设置 getter 函数,getter 函数逻辑如下:
        • 如果组件实例已经卸载,则不执行,直接返回
        • 否则执行 cleanup 清除依赖
        • 执行 source 函数
  • 如果 source 不是以上的情况,则将 getter 设置为空函数,并且报出 source 不合法的警告⚠️。

相关代码如下,由于逻辑已经完整的一丝不落的在上面分析了,所以就容笔者偷个懒,不加注释了。

if (isRef(source)) { // ref 类型的数据源,更新 getter 与 forceTriggergetter = () => (source as Ref).valueforceTrigger = !!(source as Ref)._shallow
} else if (isReactive(source)) { // reactive 类型的数据源,更新 getter 与 deepgetter = () => sourcedeep = true
} else if (isArray(source)) { // 多个数据源,更新 isMultiSource、forceTrigger、getterisMultiSource = trueforceTrigger = source.some(isReactive)// getter 会以数组形式返回数组中数据源的值getter = () =>source.map(s => {if (isRef(s)) {return s.value} else if (isReactive(s)) {return traverse(s)} else if (isFunction(s)) {return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)} else {__DEV__ && warnInvalidSource(s)}})
} else if (isFunction(source)) { // 数据源是函数的情况if (cb) {// 如果有回调,则更新 getter,让数据源作为 getter 函数getter = () =>callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)} else {// 没有回调即为 watchEffect 场景getter = () => {if (instance && instance.isUnmounted) {return}if (cleanup) {cleanup()}return callWithAsyncErrorHandling(source,instance,ErrorCodes.WATCH_CALLBACK,[onInvalidate])}}
} else {// 其余情况 getter 为空函数,并发出警告getter = NOOP__DEV__ && warnInvalidSource(source)
}

接着会处理 watch 中的场景,当有回调,并且 deep 选项为 true 时,将使用 traverse 来包裹 getter 函数,对数据源中的每个属性递归遍历进行监听。

if (cb && deep) {const baseGetter = gettergetter = () => traverse(baseGetter())
}

之后会声明 cleanup 和 onInvalidate 函数,并在 onInvalidate 函数的执行过程中给 cleanup 函数赋值,当副作用函数执行一些异步的副作用,这些响应需要在其失效时清除,所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作为入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

  • 副作用即将重新执行时。
  • 侦听器被停止(如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)。
let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {cleanup = runner.options.onStop = () => {callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)}
}

接着会初始化 oldValue 并赋值。

然后声明一个 job 函数,这个函数最终会作为调度器中的回调函数传入,由于是一个闭包形式依赖外部作用域中的许多变量,所以会放在后面讲,避免出现还未声明的变量造成理解困难。

根据是否有回调函数,设置 job 的 allowRecurse 属性,这个设置很重要,能够让 job 作为一个观察者的回调这样调度器就能知道它允许调用自身。

接着声明一个 scheduler 的调度器对象,根据 flush 的传参来确定调度器的执行时机。

  • 当 flush 为 sync 同步时,直接将 job 赋值给 scheduler,这样这个调度器函数就会直接执行。
  • 当 flush 为 post 需要延迟执行时,将 job 传入 queuePostRenderEffect 中,这样 job 会被添加进一个延迟执行的队列中,这个队列会在组件被挂载后、更新的生命周期中执行。
  • 最后是 flush 为默认的 pre 优先执行的情况,这是调度器会区分组件是否已经挂载,副作用第一次调用时必须是在组件挂载之前,而挂载后则会被推入一个优先执行时机的队列中。

这一部分逻辑的源码如下:

// 初始化 oldValue
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => { /*暂时忽略逻辑*/ } // 声明一个 job 调度器任务,暂时不关注内部逻辑// 重要:让调度器任务作为侦听器的回调以至于调度器能知道它可以被允许自己派发更新
job.allowRecurse = !!cblet scheduler: ReactiveEffectOptions['scheduler'] // 声明一个调度器
if (flush === 'sync') {scheduler = job as any // 这个调度器函数会立即被执行
} else if (flush === 'post') {// 调度器会将任务推入一个延迟执行的队列中scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {// 默认情况 'pre'scheduler = () => {if (!instance || instance.isMounted) {queuePreFlushCb(job)} else {// 在 pre 选型中,第一次调用必须发生在组件挂载之前// 所以这次调用是同步的job()}}
}

在处理完以上的调度器部分后,会开始创建副作用。

首先声明一个 runner 变量,它创建一个副作用并将之前处理好的 getter 函数作为副作用函数传入,并在副作用选项中设置了延迟调用,以及设置了对应的调度器。

并通过 recordInstanceBoundEffect 函数将该副作用函数加入组件实例的的 effects 属性中,好让组件在卸载时能够主动得停止这些副作用函数的执行。

接着会开始处理首次执行副作用函数。

  • 如果 watch 有回调函数
    • 如果 watch 设置了 immediate 选项,则立即执行 job 调度器任务。
    • 否则首次执行 runner 副作用,并将返回值赋值给 oldValue。
  • 如果 flush 的刷新时机是 post,则将 runner 放入延迟时机的队列中,等待组件挂载后执行。
  • 其余情况都直接首次执行 runner 副作用。

最后 doWatch 函数会返回一个函数,这个函数的作用是停止侦听,所以大家在使用时可以显式的为 watch、watchEffect 调用返回值以停止侦听。

// 创建 runner 副作用
const runner = effect(getter, {lazy: true,onTrack,onTrigger,scheduler
})// 将 runner 添加进 instance.effects 数组中
recordInstanceBoundEffect(runner, instance)// 初始化调用副作用
if (cb) {if (immediate) {job() // 有回调函数且是 imeediate 选项的立即执行调度器任务} else {oldValue = runner() // 否则执行一次 runner,并将返回值赋值给 oldValue}
} else if (flush === 'post') {// 如果调用时机为 post,则推入延迟执行队列queuePostRenderEffect(runner, instance && instance.suspense)
} else {// 其余情况立即首次执行副作用runner()
}// 返回一个函数,用以显式的结束侦听
return () => {stop(runner)if (instance) {remove(instance.effects!, runner)}
}

doWatch 函数到这里就全部运行完毕了,现在所有的变量已经声明完毕,尤其是最后声明的 runner 副作用。我们可以回过头看看被调用了多次的 job 中究竟做了什么。

调度器任务中做的事情逻辑比较清晰,首先会判断 runner 副作用是否被停用,如果已经被停用则立即返回,不再执行后续逻辑。

之后区分场景,通过是否存在回调函数判断是 watch api 调用还是 watchEffect api 调用。

如果是 watch api 调用,则会执行 runner 副作用,将其返回值赋值给 newValue,作为最新的值。如果是 deep 需要深度侦听,或者是 forceTrigger 需要强制更新,或者新旧值发生了改变,这三种情况都需要触发 cb 回调,通知侦听器发生了变化。在调用侦听器之前会先通过 cleanup 清除副作用,接着触发 cb 回调,将 newValue、oldValue、onInvalidate 三个参数传入回调。在回调触发后再去更新 oldValue 的值。

而如果没有 cb 回调函数,即为 watchEffect 的场景,此时调度器任务仅仅需要执行 runner 副作用函数就好。

job 调度器任务中的具体代码逻辑如下:

const job: SchedulerJob = () => {if (!runner.active) { // 如果副作用以停用则直接返回return}if (cb) {// watch(source, cb) 场景// 调用 runner 副作用获取最新的值 newValueconst newValue = runner()// 如果是 deep 或 forceTrigger 或有值更新if (deep ||forceTrigger ||(isMultiSource? (newValue as any[]).some((v, i) =>hasChanged(v, (oldValue as any[])[i])): hasChanged(newValue, oldValue))) {// 当回调再次执行前先清除副作用if (cleanup) {cleanup()}// 触发 watch api 的回调,并将 newValue、oldValue、onInvalidate 传入callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [newValue,// 首次调用时,将 oldValue 的值设置为 undefinedoldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,onInvalidate])oldValue = newValue // 触发回调后,更新 oldValue}} else {// watchEffect 的场景,直接执行 runnerrunner()}
}

总结

在本文中,笔者给大家详细讲解了 Vue3 中提供的 watch、watchEffect 两个 api 的实现,并且在组件的 option 选项中的 watch,其实也是通过 doWatch 函数来完成侦听的。在讲解的过程中,我们发现 Vue3 中的侦听器也是通过副作用来实现的,所以理解侦听器之前需要先了解透彻副作用究竟做了什么。

我们看到 watch、watchEffect 的背后都是调用并返回 doWatch 函数,笔者拆解分析了 doWatch 函数,让读者能够清楚的知道 doWatch 每一行代码都做了什么,以便于当我们的侦听器不如自己预期的工作时,可以从细节之处分析原因,而不至于瞎猜瞎试。

最后,如果这篇文章能够帮助到你了解更了解 Vue3 中的 watch 的原理以及它的工作方式,希望能给本文点一个喜欢❤️。如果想继续追踪后续文章,也可以关注我的账号或 follow 我的 github,再次谢谢各位可爱的看官老爷。



喜欢的朋友记得点赞、收藏、关注哦!!!

相关文章:

Vue3 源码解析(十):watch 的实现原理

本篇文章笔者会讲解 Vue3 中侦听器相关的 api&#xff1a;watchEffect 和 watch 。在 Vue3 之前 watch 是 option 写法中一个很常用的选项&#xff0c;使用它可以非常方便的监听一个数据源的变化&#xff0c;而在 Vue3 中随着 Composition API 的写法推行也将 watch 独立成了一…...

2023年9月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题号 123456789101112131415 答案 CDBCDBACACBBDDA 1. 我们通常说的 “ 内存 ” 属于计算机中的&#xff08;&#xff09;。 A. 输出设备 B. 输 ⼊ 设备 C. 存储设备 D. 打印设备 【答案】 C 【考纲知识点】…...

vscode 远程连接ssh 密钥方式

目录 1. powershell 生成key&#xff1a; 2. 在服务器上安装公钥 linux测试成功&#xff1a; 3).为了确保连接成功&#xff0c;输入如下指令以保证以下文件权限正确&#xff1a; 3 开启 ssh 密钥登录 vscode 远程连接配置 python连接测试ok 查看日志&#xff1a; 1. po…...

字符函数和字符串函数

字符分类函数 C语言中有⼀系列的函数是专门做字符分类的&#xff0c;也就是⼀个字符是属于什么类型的字符的。 这些函数的使用都需要包含⼀个头文件&#xff1a;ctype.h 这些函数的用法非常类似。 int islower ( int c )islower是能够判断参数部分是否是小写字母的。 通过返…...

如何利用ChatGPT加速开发与学习:以BPMN编辑器为例

在现代开发中&#xff0c;开发者经常会遇到各种需要编写和学习新技术的任务。ChatGPT作为一种强大的自然语言处理工具&#xff0c;不仅可以辅助编写代码&#xff0c;还可以帮助学习新的编程概念和解决开发中的难题。本文将以开发一个BPMN&#xff08;业务流程建模与标注&#x…...

深度学习2

四、tensor常见操作 1、元素值 1.1、获取元素值 tensor.item() 返回tensor的元素&#xff1b;只能在一个元素值使用&#xff0c;多个报错&#xff0c;当存在多个元素值时需要使用索引进行获取到一个元素值时在使用 item。 1.2、元素值运算 tensor对元素值的运算&#xff1a;…...

工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程

一.背景 公司是非煤采矿业&#xff0c;核心业务是采选&#xff0c;大型设备多&#xff0c;安全风险因素多。当下政府重视安全&#xff0c;头部技术企业的安全解决方案先进但价格不低&#xff0c;作为民营企业对安全投入的成本很敏感。利用我本身所学&#xff0c;准备搭建公司的…...

单片机学习笔记 9. 8×8LED点阵屏

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…...

c++ 力扣题(1)JZ64

JZ64求123...n_牛客题霸_牛客网 因此不能使用等差求和&#xff08;禁掉位运算为了禁掉等差求和&#xff0c;右移一位就是等差除二&#xff09;、循环、递归来解决 我们运用到在 统计构造次数所学到的内容&#xff1a; 1.可以利用静态全局变量的思想来做 2.构造n次对象&…...

logback动态获取nacos配置

文章目录 前言一、整体思路二、使用bootstrap.yml三、增加环境变量四、pom文件五、logback-spring.xml更改总结 前言 主要是logback动态获取nacos的配置信息,结尾完整代码 项目springcloudnacosplumelog&#xff0c;使用的时候、特别是部署的时候&#xff0c;需要改环境&#…...

基于零相差前馈补偿的 PID 控制

零相差前馈补偿是一种结合前馈补偿与反馈控制的策略&#xff0c;旨在提高控制系统对参考信号的跟踪精度。通过设计合理的前馈补偿器&#xff0c;使得系统对参考输入实现零相位差的跟踪&#xff0c;同时利用 PID 控制器保证系统的稳定性和动态性能。 1. 原理概述 目标&#xff…...

任务通知的本质(任务通知车辆运行) 软件定时器的本质(增加游戏音效)

任务通知的本质 没有任务通知 所谓"任务通知"&#xff0c;你可以反过来读"通知任务"。 我们使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可 以明确指定&#xff1a;通知哪个任务。 使用队列、信号量、…...

如何在 MySQL 中进行数据导入和导出?

在 MySQL 中进行数据的导入和导出是一项常见的任务&#xff0c;用于数据备份、恢复、迁移以及数据分析等多种用途。MySQL 提供了多种方法来进行数据的导入和导出&#xff0c;每种方法都有其适用的场景和特点。以下是几种常用的 MySQL 数据导入和导出方法&#xff0c;包括命令行…...

python语言基础-5 进阶语法-5.3 流式编程

声明&#xff1a;本内容非盈利性质&#xff0c;也不支持任何组织或个人将其用作盈利用途。本内容来源于参考书或网站&#xff0c;会尽量附上原文链接&#xff0c;并鼓励大家看原文。侵删。 5.3 流式编程&#xff08;参考链接&#xff1a;https://www.zhihu.com/question/59062…...

centos 服务器 docker 使用代理

宿主机使用代理 在宿主机的全局配置文件中添加代理信息 vim /etc/profile export http_proxyhttp://127.0.0.1:7897 export https_proxyhttp://127.0.0.1:7897 export no_proxy"localhost,127.0.0.1,::1,172.171.0.0" docker 命令使用代理 例如我想在使用使用 do…...

[个人专属博客] - docker安装

&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389; 欢迎访问的个人博客&#xff1a;http://swzbk.site/&#xff0c;加好友&#xff0c;一起赚&#x1f9e7;&#x1f9e7;&#x1f9e7; &#x1f389;&#x1f389;&#x1f389;&#x1f389;&…...

推荐一个QDirStat基于 Qt 的目录统计工具

QDirStat 是一款基于 Qt 的目录统计工具&#xff0c;旨在帮助用户分析磁盘空间使用情况并进行清理。QDirStat的一些主要特点和功能&#xff1a; 跨平台兼容性&#xff1a;QDirStat 支持 Linux、BSD、Unix-like 系统以及 macOS&#xff0c;确保了广泛的用户基础。 直观的数据展…...

yolo自动化项目实例解析(九) 导航

比如我们经常使用的导航&#xff0c;说白了就是寻找两点之间最近的路径&#xff0c;也就是所谓的寻路&#xff0c;我们需要想办法让程序知道他要去哪里&#xff0c;路径包含&#xff08;起点、轨迹、终点&#xff09; 一、录制轨迹 从平面角度来看&#xff0c;我们可以把区域视…...

MySQL 报错:1137 - Can‘t reopen table

MySQL 报错&#xff1a;1137 - Can’t reopen table 1. 问题 对临时表查询&#xff1a; select a.ts_code,a.tsnum,b.tsnum from (select t.ts_code ,count(*) tsnum from tmp_table t group by t.ts_code having count(*) > 20 and count(*)< 50 ) a ,(select t.ts_…...

Bokeh实现大规模数据可视化的最佳实践

目录 引言 一、Bokeh简介 二、安装Bokeh 三、数据准备 四、性能优化 五、创建图表 六、添加交互功能 七、应用案例 八、高级技巧 九、总结 引言 在数据科学领域,数据可视化是一个至关重要的环节。通过可视化,我们可以直观地理解数据的特征和趋势,为数据分析和决策…...

HTMLCSS:比赛记分卡

效果演示 这段 HTML 和 CSS 代码创建了一个卡片式的体育比赛信息展示组件&#xff0c;用于显示篮球比赛的两个队伍名称、比赛时间、比分以及一些装饰性的视觉元素。 HTML <div class"card"><div data-status"inprogress" class"teams"…...

什么是 Faiss?

好的&#xff0c;我来详细解释 Faiss&#xff0c;它的用途、使用场景&#xff0c;以及如何安装和使用。 什么是 Faiss&#xff1f; Faiss 是由 Facebook AI Research 开发的一个开源库&#xff0c;专门用于高效的相似性搜索和聚类。它非常擅长在高维向量空间中进行快速搜索&a…...

【prism】遇到一个坑,分享!

背景 我通用prism的方式写了一个弹窗,弹窗绑定一个 Loaded 事件,但是Loaded事件一直不触发!!! 具体过程 我的loaded事件也是通过命令的方式绑定的: <i:Interaction.Triggers><i:EventTrigger EventName="Loaded...

vue制作代码比较工具

前两天朋友问我 有没有vue可以做一个json代码在线比较工具 我也是在网上搜了一下找到的 废话不说 直接上代码 采用 v3 pnpm i v-code-diff <div><CodeDiff:old-string"oldStr":new-string"newStr"output-format"side-by-side"/>…...

GPT系列文章

GPT系列文章 GPT1 GPT1是由OpenAI公司发表在2018年要早于我们之前介绍的所熟知的BERT系列文章。总结&#xff1a;GPT 是一种半监督学习&#xff0c;采用两阶段任务模型&#xff0c;通过使用无监督的 Pre-training 和有监督的 Fine-tuning 来实现强大的自然语言理解。在 Pre-t…...

Qt实现可拖拽的矩形

之前项目上需要用Qt来绘制可拖拽改变形状的矩形。看了Qt Graphics相关的内容&#xff0c;虽然对Qt怎么添加图元的有了些了解&#xff0c;但是具体如何实现拖拽效果&#xff0c;一时也没有什么好的想法。还好网上有人分享的例子&#xff0c;很受启发。后来又回顾了一下这部分的代…...

python爬虫初体验(五)—— 边学边玩小游戏

1. 打开浏览器 利用webbrowser 模块的 open()函数可以启动一个新浏览器&#xff0c;打开指定的 URL。 import webbrowser webbrowser.open(http://inventwithpython.com/) 2. 猜数字游戏 # -*- coding: utf-8 -*- # This is a guess the number game. import randomsecretN…...

学习日志015--python单链表

创建 class Node:def __init__(self,data):# 数据域self.data data# 链接域self.next Noneclass LinkList:def __init__(self,):# 初始化头节点self.head None# 记录链表的长度self.size 0 增加 #头插def insert_head(self,value):# 创建新节点node Node(value)q self…...

51WORLD与南京水利研究院联合研发,国产数字孪生超融合一体机

近日&#xff0c;太湖流域水治理国际会议在江苏省无锡市举行。大会由水利部国际合作与科技司、河湖管理司、中国水利学会、水利部太湖流域管理局、无锡市人民政府、中国交通建设集团有限公司指导&#xff0c;南京水利科学研究院主办&#xff0c;以“践行新发展理念、推进流域水…...

自动泊车变自动撞车?小米SU7遭遇批量事故

科技新知 原创作者丨依蔓 编辑丨蕨影 小米系统bug&#xff0c;70多辆小米SU7同一天自动泊车撞墙、撞柱&#xff01; 近日&#xff0c;多名车主反映小米汽车SU7标准版“自动泊车”功能出现故障&#xff0c;造成不同程度的撞击、剐蹭损伤。 小米客服此前回应涉事车主&#xff0…...

异常和中断

在计算机系统中&#xff0c;异常和中断是两种常见的用于处理异步事件的机制。以下是常见的异常和中断及其特点的详细解释&#xff1a; 异常&#xff08;内中断&#xff09; 异常&#xff0c;也称为内中断&#xff0c;是由CPU内部事件引起的中断。异常通常与程序执行的当前指令…...

代理IP在后端开发中的应用与后端工程师的角色

目录 引言 代理IP的基本概念和工作原理 代理IP在后端开发中的应用 网络爬虫与数据采集 负载均衡与性能优化 安全防护与隐私保护 后端工程师在使用代理IP时面临的挑战 结论 引言 在数字化时代&#xff0c;网络技术的飞速发展极大地推动了各行各业的发展。其中&#xff…...

设计模式之 观察者模式

观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听一个主题对象&#xff08;Subject&#xff09;。当主题对象的状态发生变化时&#xff0c;所有依赖于它的观察者都会得到…...

SQLSever显示物理和逻辑 IO活动量的相关信息及显示分析、编译和执行各语句所需的毫秒数。

SQLSever SET STATISTICS IO显示物理和逻辑 IO活动量的相关信息及SET STATISTICS TIME显示分析、编译和执行各语句所需的毫秒数。 1、 SET STATISTICS IO (Transact-SQL) 物理 IO 与访问磁盘上的数据页相关&#xff0c;逻辑 IO 与访问内存中的数据页&#xff08;数据缓存&…...

CSS3 动画:前端开发的动态美

CSS3 动画:前端开发的动态美 CSS3 动画是现代网页设计中不可或缺的一部分,它为静态的网页元素添加了动态效果,提升了用户体验。本文将深入探讨CSS3动画的基础知识、高级技巧,并展示如何在实际项目中应用这些动画。 CSS3 动画基础 CSS3动画主要通过@keyframes和动画属性(…...

JavaWeb之综合案例

前言 这一节讲一个案例 1. 环境搭建 然后就是把这些数据全部用到sql语句中执行 2.查询所有-后台&前台 我们先写后台代码 2.1 后台 2.2 Dao BrandMapper&#xff1a; 注意因为数据库里面的名称是下划线分割的&#xff0c;我们类里面是驼峰的&#xff0c;所以要映射 …...

基于Spring Boot+Unipp的博物馆预约小程序(协同过滤算法、二维码识别)【原创】

&#x1f388;系统亮点&#xff1a;协同过滤算法、二维码识别&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17 前端&#xff1a; 技术&#xff1a;框…...

使用 Maven 构建一个简单的 Java 项目

Apache Maven 是一个强大的构建自动化工具&#xff0c;主要用于 Java 项目。它简化了构建和管理任何基于 Java 的项目的流程。 本指南将涵盖 Maven 的安装、设置一个简单的 Java 项目以及使用 Maven 运行该项目。 1. 安装 安装 Java 在安装 Maven 之前&#xff0c;需要确保…...

【51单片机】LCD1602液晶显示屏

学习使用的开发板&#xff1a;STC89C52RC/LE52RC 编程软件&#xff1a;Keil5 烧录软件&#xff1a;stc-isp 开发板实图&#xff1a; 文章目录 LCD1602存储结构时序结构 编码 —— 显示字符、数字 LCD1602 LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶显示屏是…...

UDP协议

UDP&#xff08;UserDatagramProtocol&#xff09;是一个简单的传输层协议&#xff0c;特点&#xff1a;无连接、不可靠、面向数据包、全双工。 报文结构 1.源端口&#xff1a; 源端口号&#xff0c;需要对方回信时选用&#xff0c;不需要时全部置0. 2.目的端口&#xff1a;目…...

阅读 ADiffusion-Based Framework for Multi-Class Anomaly Detection

A Diffusion-Based Framework for Multi-Class Anomaly Detection 我觉得引言部分写的不错&#xff0c;将问题清楚的讲出来了&#xff0c;值得借鉴&#xff01;&#xff01; 摘要 基于重建的方法在异常检测方面取得了显著成果。最近流行的扩散模型的卓越图像重建能力引发了研…...

网络云计算】2024第47周-每日【2024/11/21】周考-实操题-RAID6实操解析1

文章目录 1、RAID6配置指南&#xff08;大致步骤&#xff09;2、注意事项3、截图和视频 网络云计算】2024第47周-每日【2024/11/21】周考-实操题-RAID6实操 RAID6是一种在存储系统中实现数据冗余和容错的技术&#xff0c;其最多可以容忍两块磁盘同时损坏而不造成数据丢失。RAID…...

ts- declare关键词及vue3报错“Window typeof globalThis”上不存在属性“nextLoading”、`

报错“Window & typeof globalThis”上不存在属性“nextLoading”、 代码环境&#xff1a;vue3、ts 阮一峰讲解 declarets 用法告诉编译器某个类型是存在的 下面的例子是脚本使用浏览器全局对象document。 declare var document; document.title "Hello";上面…...

【STM32】在 STM32 USB 设备库添加新的设备类

说实话&#xff0c;我非常想吐槽 STM32 的 USB device library&#xff0c;总感觉很混乱。 USB Device library architecture 根据架构图&#xff1a; Adding a custom class 如果你想添加新的设备类&#xff0c;必须修改的文件有 usbd_desc.cusbd_conf.cusb_device.c 需要…...

【单点知识】基于PyTorch讲解自动编码器(Autoencoder)

文章目录 0. 前言1. 自动编码器的基本概念1.1 定义1.2 目标1.3 结构 2. PyTorch实现自动编码器2.1 导入必要的库2.2 定义自动编码器模型2.3 加载数据2.4 训练自动编码器 3. 自动编码器的意义4. 自动编码器的应用4.1 图像处理4.2自然语言处理&#xff1a;4.3推荐系统&#xff1a…...

html+js实现图片的放大缩小等比缩放翻转,自动播放切换,顺逆时针旋转

效果图&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>图片预览</title><sty…...

【蓝桥杯C/C++】翻转游戏:多种实现与解法解析

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: 蓝桥杯C/C 文章目录 &#x1f4af;题目&#x1f4af;问题分析解法一&#xff1a;减法法解法二&#xff1a;位运算解法解法三&#xff1a;逻辑非解法解法四&#xff1a;条件运算符解法解法五&#xff1a;数组映射法不同解法的比较…...

介绍一下toupper(ch);函数(c基础)

hi , I am 36 适合对象c语言初学者 toupper(ch1); tolower(ch2); 是返回ch的大写或小写的字符但并不改变ch 若传递数字仍返回该数字 格式 #include<ctype.h> char res toupper(ch); 链接扫雷游戏代码分享(c基础)-CSDN博客 hi , I am 36. thanks for your look…...

如何使用Python代码实现给GPU预加热

如何使用Python代码实现给GPU预加热 一、引言二、使用深度学习框架进行预加热2.1 TensorFlow预加热2.2 PyTorch预加热三、使用CUDA进行预加热四、预加热的效果评估与优化五、结论与展望在高性能计算和深度学习领域,GPU(图形处理器)已经成为不可或缺的加速工具。然而,在实际…...

基于 SpringBoot 的作业管理系统【附源码】

基于 SpringBoot 的作业管理系统 效果如下&#xff1a; 系统注册页面 学生管理页面 作业管理页面 作业提交页面 系统管理员主页面 研究背景 随着社会的快速发展&#xff0c;信息技术的广泛应用已经渗透到各个行业。在教育领域&#xff0c;课程作业管理是学校教学活动中的重要…...