Vue3 源码解析(六):响应式原理与 reactive
今天这篇文章是笔者会带着大家一起深入剖析 Vue3 的响应式原理实现,以及在响应式基础 API 中的 reactive 是如何实现的。对于 Vue 框架来说,其非侵入的响应式系统是最独特的特性之一了,所以不论任何一个版本的 Vue,在熟悉其基础用法后,响应式原理都是笔者最想优先了解的部分,也是阅读源码时必细细研究的部分。毕竟知己知彼百战不殆,当你使用 Vue 时,掌握了响应式原理一定会让你的 coding 过程更加游刃有余的。
Vue2 的响应式原理
在开始介绍 Vue3 的响应式原理前,我们先一起回顾一下 Vue2 的响应式原理。
当我们把一个普通选项传入 Vue 实例的 data 选项中,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。而 Vue2 在处理数组时,也会通过原型链劫持会改变数组内元素的方法,并在原型链观察新增的元素,以及派发更新通知。

这里放上一张 Vue2 文档中介绍响应式的图片。对于文档中有的描述笔者就不再赘述,而从 Vue2 的源码角度来对照图片说一说。在 Vue2 的源码中的 src/core 路径下有一个 observer 模块,它就是 Vue2 中处理响应式的地方了。在这个模块下 observer 负责将对象、数组转换成响应式的,即图中的紫色部分,处理 Data 的 getter 及 setter。当 data 中的选项被访问时,会触发 getter,此时 observer 目录下的 wather.js 模块就会开始工作,它的任务就是收集依赖,我们收集到的依赖是一个个 Dep 类的实例化对象。而 data 中的选项变更时,会触发 setter 的调用,而在 setter 的过程中,触发 dep 的 notify 函数,派发更新事件,由此实现数据的响应监听。
Vue3 的响应式变化
在简单回顾了 Vue2 的响应式原理后,我们会有一个疑惑,Vue3 的响应式原理与 Vue2 相比有什么不同呢?
在 Vue3 中响应式系统最大的区别就是,数据模型是被代理的 JavaScript 对象了。不论是我们在组件的 data 选项中返回一个普通的JavaScript 对象,还是使用 composition api 创建一个 reactive 对象,Vue3 都会将该对象包裹在一个带有 get 和 set 处理程序的 Proxy 中。
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值等)。
其基础语法类似于:
const p = new Proxy(target, handler)
Proxy 相比较于 Object.defineProperty 究竟有什么优势呢?这个问题让我们先从 Object.defineProperty 的弊端说起。
从 Object 的角度来说,由于 Object.defineProperty 是对指定的 key 生成 getter/setter 以进行变化追踪,那么如果这个 key 一开始不存在我们定义的对象上,响应式系统就无能为力了,所以在 Vue2 中无法检测对象的 property 的添加或移除。而对于这个缺陷,Vue2 提供了 vm.$set
和全局的 Vue.set
API 让我们能够向对象添加响应式的 property。
从数组的角度来说,当我们直接利用索引设置一个数组项时,或者当我们修改数组长度时,Vue2 的响应式系统都不能监听到变化,解决的方法也如上,使用上面提及的 2 个 api。
而这些问题在 ES6 的新特性 Proxy 面前通通都是不存在的,Proxy 对象能够利用 handler 陷阱在 get、set 时捕获到任何变动,也能监听对数组索引的改动以及 数组 length 的改动。
而依赖收集和派发更新的方式在 Vue3 中也变得不同,在这里我先快速的整体描述一下:在 Vue3 中,通过 track 的处理器函数来收集依赖,通过 trigger 的处理器函数来派发更新,每个依赖的使用都会被包裹到一个副作用(effect)函数中,而派发更新后就会执行副作用函数,这样依赖处的值就被更新了。
响应式基础 reactive 的实现
既然这是一个源码分析的文章,咱们还是从源码的角度来分析响应式究竟是如何实现的。所以笔者会先分析响应式基础的 API —— reactive ,相信通过讲解 reactive 的实现,大家会对 Proxy 有更深刻的认识。
reactive
二话不说,直接看源码。下面是 reactive API 的函数,函数的参数接受一个对象,通过 createReactiveObject
函数处理后,直接返回一个 proxy 对象。
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {// 如果试图去观察一个只读的代理对象,会直接返回只读版本if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {return target}// 创建一个代理对象并返回return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)
}
在第三行能看到通过判断 target 中是否有 ReactiveFlags 中的 IS_READONLY key 确定对象是否为只读对象。ReactiveFlags 枚举会在源码中不断的与我们见面,所以有必要提前介绍一下 ReactiveFlags:
export const enum ReactiveFlags {SKIP = '__v_skip', // 是否跳过响应式 返回原始对象IS_REACTIVE = '__v_isReactive', // 标记一个响应式对象IS_READONLY = '__v_isReadonly', // 标记一个只读对象RAW = '__v_raw' // 标记获取原始值
}
在 ReactiveFlags 枚举中有 4 个枚举值,这四个枚举值的含义都在注释里。对于 ReactiveFlags 的使用是代理对象对 handler 中的 trap 陷阱非常好的应用,对象中并不存在这些 key,而通过 get 访问这些 key 时,返回值都是通过 get 陷阱的函数内处理的。介绍完 ReactiveFlags 后我们继续往下看。
createReactiveObject
function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any>
)
先看 createReactiveObject 函数的签名,该函数接受 5 个参数:
- target:目标对象,想要生成响应式的原始对象。
- isReadonly:生成的代理对象是否只读。
- baseHandlers:生成代理对象的 handler 参数。当 target 类型是 Array 或 Object 时使用该 handler。
- collectionHandlers:当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler。
- proxyMap:存储生成代理对象后的 Map 对象。
这里需要注意的是 baseHandlers 和 collectionHandlers 的区别,这两个参数会根据 target 的类型进行判断,最终选择将哪个参数传入 Proxy 的构造函数,当做 handler 参数使用。
接着我们开始看 createReactiveObject 的逻辑部分:
function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any>
) {// 如果目标不是对象,直接返回原始值if (!isObject(target)) {return target}// 如果目标已经是一个代理,直接返回// 除非对一个响应式对象执行 readonlyif (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {return target}// 目标已经存在对应的代理对象const existingProxy = proxyMap.get(target)if (existingProxy) {return existingProxy}// 只有白名单里的类型才能被创建响应式对象const targetType = getTargetType(target)if (targetType === TargetType.INVALID) {return target}const proxy = new Proxy(target,targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers)proxyMap.set(target, proxy)return proxy
}
在该函数的逻辑部分,可以看到基础数据类型并不会被转换成代理对象,而是直接返回原始值。
并且会将已经生成的代理对象缓存进传入的 proxyMap,当这个代理对象已存在时不会重复生成,会直接返回已有对象。
也会通过 TargetType 来判断 target 目标对象的类型,Vue3 仅会对 Array、Object、Map、Set、WeakMap、WeakSet 生成代理,其他对象会被标记为 INVALID,并返回原始值。
当目标对象通过类型校验后,会通过 new Proxy() 生成一个代理对象 proxy,handler 参数的传入也是与 targetType 相关,并最终返回已生成的 proxy 对象。
所以回顾 reactive api,我们可能会得到一个代理对象,也可能只是获得传入的 target 目标对象的原始值。
Handlers 的组成
在 @vue/reactive 库中有 baseHandlers 和 collectionHandlers 两个模块,分别生成 Proxy 代理的 handlers 中的 trap 陷阱。
例如在上面生成 reactive 的 api 中 baseHandlers 的参数传入了一个 mutableHandlers 对象,这个对象是这样的:
export const mutableHandlers: ProxyHandler<object> = {get,set,deleteProperty,has,ownKeys
}
通过变量名我们能知道 mutableHandlers 中存在 5 个 trap 陷阱。而在 baseHandlers 中,get 和 set 都是通过工厂函数生成的,以便于适配除 reactive 外的其他 api,例如 readonly、shallowReactive、shallowReadonly 等。
baseHandlers 是处理 Array、Object 的数据类型的,这也是我们绝大部分时间使用 Vue3 时使用的类型,所以笔者接下来着重的讲一下baseHandlers 中的 get 和 set 陷阱。
get 陷阱
上一段提到 get 是由一个工厂函数生成的,先来看一下 get 陷阱的种类。
const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
get 陷阱有 4 个类型,分别对应不同的响应式 API,从名称中就可以知道对应的 API 名称,非常一目了然。而所有的 get 都是由 createGetter 函数生成的。所以接下来我们着重看一下 createGetter 的逻辑。
还是老规矩,先从函数签名看起。
function createGetter(isReadonly = false, shallow = false) {return function get(target: Target, key: string | symbol, receiver: object) {}
}
createGetter 有 isReadonly 和 shallow 两个参数,让使用 get 陷阱的 api 按需使用。而函数的内部返回了一个 get 函数,使用高阶函数的方式返回将会传入 handlers 中 get 参数的函数。
接着看 createGetter 的逻辑:
// 如果 get 访问的 key 是 '__v_isReactive',返回 createGetter 的 isReadonly 参数取反结果
if (key === ReactiveFlags.IS_REACTIVE) {return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {// 如果 get 访问的 key 是 '__v_isReadonly',返回 createGetter 的 isReadonly 参数return isReadonly
} else if (// 如果 get 访问的 key 是 '__v_raw',并且 receiver 与原始标识相等,则返回原始值key === ReactiveFlags.RAW &&receiver ===(isReadonly? shallow? shallowReadonlyMap: readonlyMap: shallow? shallowReactiveMap: reactiveMap).get(target)
) {return target
}
从这段 createGetter 逻辑中,笔者专门介绍过的 ReactiveFlags 枚举在这就取得了妙用。其实目标对象中并没有这些 key,但是在 get 中Vue3 就对这些 key 做了特殊处理,当我们在对象上访问这几个特殊的枚举值时,就会返回特定意义的结果。而可以关注一下 ReactiveFlags.IS_REACTIVE 这个 key 的判断方式,为什么是只读标识的取反呢?因为当一个对象的访问能触发这个 get 陷阱时,说明这个对象必然已经是一个 Proxy 对象了,所以只要不是只读的,那么就可以认为是响应式对象了。
接着看 get 的后续逻辑。
继续判断 target 是否是一个数组,如果代理对象不是只读的,并且 target 是一个数组,并且访问的 key 在数组需要特殊处理的方法里,就会直接调用特殊处理的数组函数执行结果,并返回。
arrayInstrumentations 是一个对象,对象内保存了若干个被特殊处理的数组方法,并以键值对的形式存储。
我们之前说过 Vue2 以原型链的方式劫持了数组,而在这里也有类似地作用,而数组的部分我们准备放在后续的文章中再介绍,下面是需要特殊处理的数组。
- 对索引敏感的数组方法
- includes、indexOf、lastIndexOf
- 会改变自身长度的数组方法,需要避免 length 被依赖收集,因为这样可能会造成循环引用
- push、pop、shift、unshift、splice
// 判断 taeget 是否是数组
const targetIsArray = isArray(target)
// 如果不是只读对象,并且目标对象是个数组,访问的 key 又在数组需要劫持的方法里,直接调用修改后的数组方法执行
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)
}// 获取 Reflect 执行的 get 默认结果
const res = Reflect.get(target, key, receiver)// 如果是 key 是 Symbol,并且 key 是 Symbol 对象中的 Symbol 类型的 key
// 或者 key 是不需要追踪的 key: __proto__,__v_isRef,__isVue
// 直接返回 get 结果
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res
}// 不是只读对象,执行 track 收集依赖
if (!isReadonly) {track(target, TrackOpTypes.GET, key)
}// 如果是 shallow 浅层响应式,直接返回 get 结果
if (shallow) {return res
}if (isRef(res)) {// 如果是 ref ,则返回解包后的值 - 当 target 是数组,key 是 int 类型时,不需要解包const shouldUnwrap = !targetIsArray || !isIntegerKey(key)return shouldUnwrap ? res.value : res
}if (isObject(res)) {// 将返回的值也转换成代理,我们在这里做 isObject 的检查以避免无效值警告。// 也需要在这里惰性访问只读和星影视对象,以避免循环依赖。return isReadonly ? readonly(res) : reactive(res)
}// 不是 object 类型则直接返回 get 结果
return res
在处理完数组后,我们对 target 执行 Reflect.get 方法,获得默认行为的 get 返回值。
之后判断 当前 key 是否是 Symbol,或者是否是不需要追踪的 key,如果是的话直接返回 get 的结果 res。
下面👇几个 key 是不需要被依赖收集或者返回响应式结果的。
__proto__
_v_isRef
__isVue
接着判断当前代理对象是否是只读对象,如果不是只读的话,则运行笔者上文提及的 tarck 处理器函数收集依赖。
如果是 shallow 的浅层响应式,则不需要将内部的属性转换成代理,直接返回 res。
如果 res 是一个 Ref 类型的对象,就会自动解包返回,这里就能解释官方文档中提及的 ref 在 reactive 中会自动解包的特性了。而需要注意的是,当 target 是一个数组类型,并且 key 是 int 类型时,即使用索引访问数组元素时,不会被自动解包。
如果 res 是一个对象,就会将该对象转成响应式的 Proxy 代理对象返回,再结合我们之前分析的缓存已生成的 proxy 对象,可以知道这里的逻辑并不会重复生成相同的 res,也可以理解文档中提及的当我们访问 reactive 对象中的 key 是一个对象时,它也会自动的转换成响应式对象,而且由于在此处生成 reactive 或者 readonly 对象是一个延迟行为,不需要在第一时间就遍历 reactive 传入的对象中的所有 key,也对性能的提升是一个帮助。
当 res 都不满足上述条件时,直接返回 res 结果。例如基础数据类型就会直接返回结果,而不做特殊处理。
至此,get 陷阱的逻辑全部结束了。
set 陷阱
与 createGetter 对应,set 也有一个 createSetter 的工厂函数,也是通过柯里化的方式返回一个 set 函数。
函数签名都大同小异,那么接下来笔者直接带大家盘逻辑。
set 的函数比较简短,所以这次一次性把写好注释的代码放上来,先看代码再讲逻辑。
function createSetter(shallow = false) {return function set(target: object,key: string | symbol,value: unknown,receiver: object): boolean {let oldValue = (target as any)[key]if (!shallow) {value = toRaw(value)oldValue = toRaw(oldValue)// 当不是 shallow 模式时,判断旧值是否是 Ref,如果是则直接更新旧值的 value// 因为 ref 有自己的 setterif (!isArray(target) && isRef(oldValue) && !isRef(value)) {oldValue.value = valuereturn true}} else {// shallow 模式不需要特殊处理,对象按原样 set}// 判断 target 中是否存在 keyconst hadKey =isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)// Reflect.set 获取默认行为的返回值const result = Reflect.set(target, key, value, receiver)// 如果目标是原始对象原型链上的属性,则不会触发 trigger 派发更新if (target === toRaw(receiver)) {// 使用 trigger 派发更新,根据 hadKey 区别调用事件if (!hadKey) {trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}
}
在 set 的过程中会首先获取新旧与旧值,当目前的代理对象不是浅层比较时,会判断旧值是否是一个 Ref,如果旧值不是数组且是一个 ref类型的对象,并且新值不是 ref 对象时,会直接修改旧值的 value。
看到这里可能会有疑问,为什么要更新旧值的 value?如果你使用过 ref 这个 api 就会知道,每个 ref 对象的值都是放在 value 里的,而 ref 与 reactive 的实现是有区别的,ref 其实是一个 class 实例,它的 value 有自己的 set ,所以就不会在这里继续进行 set 了。ref 的部分在后续的文章中会详细讲解。
在处理完 ref 类型的值后,会声明一个变量 hadKey,判断当前要 set 的 key 是否是对象中已有的属性。
接下来调用 Reflect.set 获取默认行为的 set 返回值 result。
然后会开始派发更新的过程,在派发更新前,需要保证 target 和原始的 receiver 相等,target 不能是一个原型链上的属性。
之后开始使用 trigger 处理器函数派发更新,如果 hadKey 不存在,则是一个新增属性,通过 TriggerOpTypes.ADD 枚举来标记。这里可以看到开篇分析 Proxy 强于 Object.defineProperty 的地方,会监测到任何一个新增的 key,让响应式系统更强大。
如果 key 是当前 target 上已经存在的属性,则比较一下新旧值,如果新旧值不一样,则代表属性被更新,通过 TriggerOpTypes.SET 来标记派发更新。
在更新派发完后,返回 set 的结果 result,至此 set 结束。
总结
在今天的文章中,笔者先带大家回顾了 Vue2 的响应式原理,又开始介绍 Vue3 的响应式原理,通过比较 Vue2 和 Vue3 的响应式系统的区别引出 Vue3 响应式系统的提升之处,尤其是其中最主要的调整将 Object.defineProperty 替换为 Proxy 代理对象。
为了让大家属性 Proxy 对响应式系统的影响,笔者着重介绍了响应式基础 API:reactive。分析了 reactive 的实现,以及 reactive api 返回的 proxy 代理对象使用的 handlers 陷阱。并且对陷阱中我们最常用的 get 和 set 的源码进行分析,相信大家在看完本篇文章以后,对 proxy 这个 ES2015 的新特性的使用又有了新的理解。
本文只是介绍 Vue3 响应式系统的第一篇文章,所以 track 收集依赖,trigger 派发更新的过程没有详细展开,在后续的文章中计划详细讲解副作用函数 effect,以及 track 和 trigger 的过程,如果希望能详细了解响应式系统的源码,麻烦大家点个关注免得迷路。
最后,如果这篇文章能够帮助到你了解 Vue3 中的响应式原理和 reactive 的实现,希望能给本文点一个喜欢❤️。如果想继续追踪后续文章,也可以关注我的账号或 follow 我的 github,再次谢谢各位可爱的看官老爷。

喜欢的朋友记得点赞、收藏、关注哦!!!
相关文章:
Vue3 源码解析(六):响应式原理与 reactive
今天这篇文章是笔者会带着大家一起深入剖析 Vue3 的响应式原理实现,以及在响应式基础 API 中的 reactive 是如何实现的。对于 Vue 框架来说,其非侵入的响应式系统是最独特的特性之一了,所以不论任何一个版本的 Vue,在熟悉其基础用…...
规则引擎 - Easy Rules
Easy Rules 依赖demodemo1demo2 总结 Easy Rules 是一个轻量级的 Java 规则引擎,使用简单,适合快速开发和简单的规则场景,适合对于一些判断,是否属于白名单,是否有特殊权限,是否属于当前区域,调…...
uniapp打ios包
uniapp在windows电脑下申请证书并打包上架 前言 该开发笔记记录了在window系统下,在苹果开发者网站生成不同证书,进行uniapp打包调试和上线发布,对window用户友好 注:苹果打包涉及到两种证书:开发证书 和 分发证书 …...
from tensorflow.keras.models import Model中Model报红;以及动态链接库(DLL)初始化例程失败
博主也是网上搜索资料又问的kimi 试了一节课 总结下来: 1、用这句话导入Model from tensorflow.keras.models import Model 2、更新pip pip install --upgrade pip 3、卸载重装tensorflow pip uninstall tensorflow 4、使用阿里云使tensorflow降低到2.8.0&a…...
【初阶数据结构】树——二叉树(上)
文章目录 目录 前言 一、树 1.树的概念与结构 2.树相关术语 3.树的表示 二、二叉树 1.概念与结构 2.特殊的二叉树 3.二叉树存储结构 总结 前言 本篇带大家学习一种非线性数据结构——树,简单认识树和二叉数以及了解二叉树的存储结构。 一、树 1.树的概念与结构 树…...
Debian GNU/Linux的新手入门介绍
Debian GNU/Linux:起源、基本介绍与发行版对比 一、起源与发展历程 Debian GNU/Linux 是现存最古老的 Linux 发行版之一,由 Ian Murdock 于 1993 年 8 月 16 日创立。其名称结合了他的女友(后成为妻子)Debra 和他自己的名字 Ian…...
13 数据存储单位与 C 语言整数类型:从位到艾字节、常见整数类型及其范围、字面量后缀、精确宽度类型详解
1 数据存储单位 在计算机科学领域,数据存储采用分层级的单位体系,各层级单位以 2 的幂次方为换算基础,而非传统数学中的 10 的幂次方。 以下是常见的数据存储单位介绍: 位(bit,b)是计算机中的最…...
【CPU】结合RISC-V CPU架构回答中断系统的7个问题(个人草稿)
结合RISC-V CPU架构对中断系统七个关键问题的详细解析,按照由浅入深的结构进行说明: 一、中断请求机制(问题①) 硬件基础: RISC-V通过CLINT(Core Local Interrupter)和PLIC(Platfor…...
基于unsloth微调一个越狱大模型
网上其实并没有找到现成的开源越狱数据集,所以数据集获取是个麻烦事。想了想easydataset,可能还是不行,easydataset是基于大模型回答的,大模型一般都做了对齐训练,那本地文档生成数据这条路也不可靠。 现成的越狱数据…...
城市客运安全员证适用岗位及要求
城市客运安全员证适用岗位及要求 城市客运安全员证是从事城市公共交通行业安全管理工作的重要资格证书,主要适用于以下岗位: 1. 公交车辆安全员 岗位职责:负责公交车辆运行过程中的安全监督,检查乘客携带物品,防止危…...
UDP协议详解
UDP协议详解 一、理解socket套接字 1.1理解IP 我们都知道在网络中IP用来标识主机的唯一性。那么?这句话该如何理解呢?大家来思考一个问题:计算机之间传输传输数据是目的吗?就好比,你爸叫你给你妈带句话ÿ…...
Unreal Engine中FRotator与FQuat在赛车游戏方向盘控制中的协同应用解析
摘要 深入剖析 Unreal Engine 中这两个关键组件在赛车游戏方向盘控制中的协同作用,涵盖全流程与实践技巧。 一、引言 在赛车游戏开发中,实现逼真的方向盘控制是提升玩家体验的关键要素之一。而在 Unreal Engine 里,FRotator 与 FQuat 这两…...
第十四届蓝桥杯 2023 C/C++组 飞机降落
目录 题目: 题目描述: 编辑题目链接: 思路: 核心思路: 思路详解: 代码: 代码详解: 题目: 题目描述: 题目链接: 洛谷 P9241 [蓝桥杯 20…...
完美解决Microsoft Edge浏览器无法同步/一直在同步中/更新失败等问题
在使用Microsoft Edge浏览器的过程中,我们可能会遇到一些常见的问题,例如无法同步数据或无法更新浏览器。这些问题通常可以归结为以下两个主要原因: 一、网络连接问题 当Edge浏览器无法同步或更新时,首要考虑的是网络连接问题。…...
CSS文本属性
CSS文本属性 在CSS中,可以使用以下属性来设置文本的样式和布局: 1.color : 设置文本颜色。可以使用颜色名称、十六进制值或RGB值来指定颜色: p{color:red; }font-family : 设置文本的字体系列。可以指定一个或多个字…...
思科路由器做DNS服务器
1.实验环境中,常常需要一台DNS服务器来做名称解析,一般会安装一台windows server,启用dns服务,或者安装一台Linux服务器,安装Bind来实现;虽然可以实现你想要的功能,但是费时费力且配置复杂&…...
Windows部署FunASR实时语音听写便捷部署教程
FunASR提供可便捷本地或者云端服务器部署的实时语音听写服务,内核为FunASR已开源的runtime-SDK。 FunASR集成了达摩院语音实验室在Modelscope社区开源的语音端点检测(VAD)、Paraformer-large非流式语音识别(ASR)、Paraformer-large流式语音识别(ASR)、标点预测(PUNC) 等相关能…...
C++之unordered封装
目录 一、哈希表的修改 1.1、哈希表节点结构 1.2、迭代器 1.3、哈希表结构 1.4、完整代码 二、unordered_map的实现 二、unordered_set的实现 一、哈希表的修改 注意:这里我们使用哈希桶来封装unordered_map和unordered_set。 1.1、哈希表节点结构 templa…...
Pycharm(九)函数的闭包、装饰器
目录 一、函数参数 二、闭包 三、装饰器 一、函数参数 def func01():print("func01 shows as follows") func01() # 函数名存放的是函数所在空间的地址 print(func01)#<function func01 at 0x0000023BA9FC04A0> func02func01 print(func02)#<function f…...
7. 栈与队列(随想录)
1.栈实现队列 2.用队列实现栈 3.有效的括号 4.删除字符串中的所有相邻重复项 5.逆波兰表达式 6.滑动窗口最大值 7.前k个高频元素...
GPU软硬件架构协同设计解析
GPU软硬件架构协同设计解析 GPU(图形处理器)的软硬件协同设计是其在通用计算和高性能计算(HPC)领域取得突破的核心原因。以下从硬件架构、软件架构、协同设计的关键技术及典型案例展开深度解析。 一、硬件架构的核心设计原则 流式多处理器(SM)的模块化设计 计算单元…...
【软考】论NoSQL数据库技术及其应用示例
论NoSQL数据库技术及其应用 随着互联网web2.0网站的兴起,传统关系数据库在应对web2.0 网站,特别是超大规模和高并发的web2.0纯动态SNS网站上已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到…...
特伦斯智慧钢琴评测:如何用科技重塑钢琴学习新体验
对于渴望学习钢琴的爱好者而言,传统钢琴的笨重体积、高昂成本与扰民问题往往成为绊脚石。而智能电钢琴的出现,正以轻量化设计、沉浸式体验与智能化功能打破这些壁垒。特伦斯智慧钢琴凭借其专业级硬件配置与创新教学系统,成为市场中兼具性能与…...
UML 状态图:解锁电子图书馆管理系统的高效设计
目录 一、UML 状态图的核心要素 状态:系统行为的 “栖息地” 转换:连接状态的 “桥梁” 动作:赋予功能的 “实践者” 二、电子图书馆管理系统状态图解析 系统空闲状态:一切的起点 读者登录与身份验证:安全的 “…...
UML 状态图:陪伴机器人系统示例
目录 一、状态图的基本概念 1.1 状态 1.2 转换 1.3 动作 二、陪伴机器人系统状态图解析 2.1 初始与待机状态 2.2 情绪检测中状态 2.3 陪伴模式下的细分 2.4 疏导模式的严谨流程 2.5 安抚模式的关键作用 三、状态图绘画 四、UML 状态图的强大 4.1 直观呈现系统行为…...
超详细实现单链表的基础增删改查——基于C语言实现
文章目录 1、链表的概念与分类1.1 链表的概念1.2 链表的分类 2、单链表的结构和定义2.1 单链表的结构2.2 单链表的定义 3、单链表的实现3.1 创建新节点3.2 头插和尾插的实现3.3 头删和尾删的实现3.4 链表的查找3.5 指定位置之前和之后插入数据3.6 删除指定位置的数据和删除指定…...
分布式光纤测温技术让森林火灾预警快人一步
2025年春季,多地接连发生森林火灾,累计过火面积超 3万公顷。春季历来是森林草原火灾易发、多发期,加之清明节已到来,生产生活用火活跃,民俗祭祀用火集中,森林火灾风险进一步加大。森林防火,人人…...
判断链表是否为环(Java版本自己用)
141. 环形链表 核心代码版本: public class Solution {public boolean hasCycle(ListNode head) {if (head null) {return false;}ListNode slow head;ListNode fast head.next;while (fast ! null && fast.next ! null) {if (slow fast) {return true…...
leetcode 516. Longest Palindromic Subsequence
题目描述: 代码: class Solution { public:int longestPalindromeSubseq(string s) {int n s.size();//i<j,dp[i][j]表示s[i,j]的最长回文子串的长度,按照这个定义dp[0][n-1]就是答案,i>j的dp[i][j]不定义vector<vector<int>> dp(n,…...
关于敏感文件或备份 安全配置错误 禁止通过 URL 访问 Vue 项目打包后的 .gz 压缩文件
要禁止通过 URL 访问 Vue 项目打包后的 .gz 压缩文件(如 sc.6abb69d9.css.gz)或其他敏感文件,可以通过 Nginx 配置和 Tomcat 配置双重防护来实现。以下是具体解决方案: 方法 1:通过 Nginx 配置禁止访问 .gz 文件 在 N…...
Linux系统启动全流程解析:从BIOS到用户登录
摘要 深度解析Linux系统启动五阶段:内核加载→init进程初始化→系统服务启动→终端创建→用户登录,涵盖SysV/Systemd差异及运行级别管理,提供故障排查指南。 一、启动流程全景概览 Linux系统启动过程严格遵循5个阶段顺序执行,每…...
unity动态骨骼架设+常用参数分享(包含部分穿模解决方案)
Unity骨骼物理模拟插件Dynamic Bone Dynamic Bone 可用于对角色的骨骼(bones)或者铰链系统(joints)施加物理效果。 物理效果可以使得游戏角色的头发、衣服、胸部或者是其他的任何部位,都可以以近似真实的状态运动。 …...
【云原生】k8s集群部署最新版ELFK日志采集平台
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Golang开…...
基于瑞芯微RK3576国产ARM八核2.2GHz A72 工业评估板——ROS2系统使用说明
前 言 本文主要介绍创龙科技TL3576-MiniEVM评估板演示基于Ubuntu的ROS系统(版本:ROS2 Foxy)使用说明,包括镜像编译、镜像替换,以及ROS系统测试的方法。适用开发环境如下。 Windows开发环境:Windows 10 64bit Linux虚拟机环境:VMware16.2.5、Ubuntu22.04.5 64bit U-B…...
android studio sdk unavailable和Android 安装时报错:SDK emulator directory is missing
md 网上说的都是更换proxy代理什么的,还有一些二其他乱七八糟的,根本没用,感觉很多就是解决不了问题,还贼多贼一致,同质化,感觉很坑人,让人觉得他们和我的一样的,大家都是按他们说的…...
qemu构建arm环境(AI生成)
要编译 qemu-system-arm,你需要安装一些依赖库,配置编译环境,并执行编译过程。以下是一般步骤,适用于大多数基于Linux的系统。 1. 安装依赖 首先,你需要安装一些必要的开发工具和库。你可以使用包管理器来安装这些依…...
10天学会嵌入式技术之51单片机-day-4
第十二章 中断系统 中断系统是单片机用于处理外部紧急事件的一种机制。中断系统工作的大致流程如下 图所示:当 CPU 正在处理某项任务时,外部发生了某个紧急事件,此时 CPU 会暂停当前 的工作,转而去处理这个紧急事件,处…...
spark—SQL3
连接方式 内嵌Hive: 使用时无需额外操作,但实际生产中很少使用。 外部Hive: 在虚拟机下载相关配置文件,在spark-shell中连接需将hive-site.xml拷贝到conf/目录并修改url、将MySQL驱动copy到jars/目录、把core-site.xml和hdfs-sit…...
CENTOS 7 安装VNC
一、VNC简介 VNC(Virtual Network Computing),为一种使用RFB协议的屏幕画面分享及远程操作软件。此软件借由网络,可发送键盘与鼠标的动作及即时的屏幕画面。 VNC与操作系统无关,因此可跨平台使用,例如可用…...
第42讲:走进智慧农业的“感知神经系统”——农田遥感 + 边缘计算的融合实践
目录 ✨一、为什么要融合遥感与边缘计算? 🧪二、典型应用场景案例 ✅ 案例 1:棉花田的智能水分监测系统 ✅ 案例 2:水稻纹枯病自动识别与预警系统 💻三、关键技术框架与实现思路 🚦 1. 系统架构流程图: 📦 2. 模型部署建议: 💡四、未来发展趋势展望 �…...
Dify忘记管理员密码,重置的问题
今天本地win10电脑,使用源码启动dify,忘记了管理员账号和密码,于是网上查找解决办法。 1.有的网上资料说是去数据库删除用户表,于是进入数据库: docker exec -it docker-db-1 psql -U postgres -d dify 找到postgre…...
C#—Lazy<T> 类型(延迟初始化/懒加载模式)
C# 的 Lazy<T> 类型 Lazy<T> 是 C# 中的一个类,用于实现延迟初始化(懒加载)模式。它提供了一种线程安全的方式来延迟创建大型或资源密集型对象,直到第一次实际需要时才进行初始化。 主要特点 延迟初始化:…...
unity打包安卓时的签名文件jks转换keystore
前言 unity打包安卓时需要的签名文件格式默认是keystore,而有时我们拿到的是jks格式的签名文件,就需要把jks格式文件转换成keystore格式文件。 其实在windows下也可以不转换,在选择签名文件的文件选择框时,把文件扩展名筛选项&a…...
Android audio_policy_configuration.xml加载流程
目录 一、audio_policy_configuration.xml文件被加载流程 1、AudioPolicyService 创建阶段 2、createAudioPolicyManager 实现 3、AudioPolicyManager 构造 4、配置文件解析 loadConfig 5、核心解析逻辑 PolicySerializer::deserialize 二、AudioPolicyConfig类解析 1、…...
AOSP Android14 Launcher3——远程窗口动画关键类SurfaceControl详解
在 Launcher3 执行涉及其他应用窗口(即“远程窗口”)的动画时,例如“点击桌面图标启动应用”或“从应用上滑回到桌面”的过渡动画,SurfaceControl 扮演着至关重要的角色。它是实现这些跨进程、高性能、精确定制动画的核心技术。 …...
iframe下系统访问跨域问题解决办法
问题描述:iframe下嵌入web页面,访问后端接口跨域,导致接口调不通。 产生原因:iframe下,web端访问后端接口时,会优先向后端发送请求方法为OPTIONS的预检测请求,该请求调用不通,导致真…...
Kafka 如何理解Kafka的高可用
一、Kafka高可用核心思想:备胎的自我修养 核心口诀:“别把鸡蛋放在一个篮子里,除非你他妈有100个篮子!” Kafka的高可用设计,本质上就是一场**“分布式备胎大战”**。它的核心逻辑是: “老子不信任任何单…...
11-DevOps-Jenkins Pipeline流水线作业
前面已经完成了,通过在Jenkins中创建自由风格的工程,在界面上的配置,完成了发布、构建的过程。 这种方式的缺点就是如果要在另一台机器上进行同样的配置,需要一项一项去填写,不方便迁移,操作比较麻烦。 解…...
C++学习之游戏服务器开发十一DOCKER的基本使用
目录 1.多实例部署方案 2.容器的概念 3.docker初识 4.docker仓库 5.docker镜像 6.docker容器 7.docker和虚拟机的区别 8.docker命令解释 9.dockerfile构建镜像 10.离线分发镜像 1.多实例部署方案 redis 命令( redis-cli XXXX ) set key value:…...
docker学习笔记2-最佳实践
一、在容器中启动mysql的最佳实践 (一)查找目录 1、mysql的配置文件路径 /etc/mysql/conf.d 2、mysql的数据目录 /var/lib/mysql 3、环境变量 4、端口 mysql的默认端口3306。 (二)启动命令 docker run -d -p 3306:3306 …...