Vue Vapor 事件机制深潜:从设计动机到源码解析

基于 vue@3.6alpha 阶段)及 Vapor 的最新进展撰写;Vapor 仍在演进中,部分实现可能继续优化。

TL;DR(速览)

  • 传统(≤3.5):事件以元素为中心绑定;每个元素用 el._vei 保存 invoker,运行时通过 addEventListener 直绑;调用走 callWithErrorHandling,有错误上报链路。
  • Vapor(3.6 引入)全局事件委托;首次遇到某个事件类型,只在 document 绑定一次统一处理器;元素仅存 $evt{type} 句柄(可能是数组);非冒泡或带 once/capture/passive 的事件改为直绑。
  • 注意.stopVapor 里只能阻断 Vapor 自己的委托分发,阻断不了你手动 addEventListener 的原生监听;且 Vapor 的统一处理器默认不包 try/catch,异常可能中断委托链。

一、为什么 Vue 要引入 Vapor 模式?

1. 虚拟 DOM 的局限

虚拟 DOMVDOM)带来抽象、跨平台与统一渲染接口的好处,但不是“零成本”:

  • 每次更新往往重建整棵 VNodeJS 对象创建与 GC 压力显著)。
  • 需要递归 diff 比较,天然多了一层计算。
  • 大规模、频繁更新UI(如复杂表格、拖拽、实时刷新的仪表盘)中,这层开销会积累成瓶颈。
2. Vue 已有的优化手段
  • 静态提升(Static Hoisting:将不变节点提取出渲染循环。
  • Patch Flags:编译时给动态片段打标,运行时只检查标记处。
  • 事件/插槽缓存:减少重复创建。

这些措施让 VDOM 更高效,但结构性开销仍在。

3. Vapor 的设计动机

Vapor 是一种更激进的编译驱动策略:

  • 绕过运行时 VDOM,模板编译为“直接 DOM 操作”的最小更新程序。
  • 依赖图直达 DOM:每个响应式依赖对应精准更新逻辑。
  • 减少遍历、对象创建与比较,贴近原生性能与体积。
4. 对事件机制的影响
  • 传统模式:是否增删事件监听通常在 VNode diff 过程中决策。
  • Vapor 模式:编译期即可分析并决定“委托或直绑”,因此引入了全局事件委托方案来降低监听器数量与运行时成本。

二、传统事件机制回顾(≤3.5)

1. invoker_vei
  • 每个 DOM 元素挂载一个 el._vei = {},保存不同事件名的 invoker
  • 绑定:若已有同名 invoker仅改 .value;否则 addEventListener 新增。
  • 卸载:找到 invoker 后移除监听并清理。

源码节选(文件:packages/runtime-dom/src/modules/events.ts)——入口 patchEvent_vei 缓存

const veiKey = Symbol('_vei')export function patchEvent(el: Element & { [veiKey]?: Record<string, Invoker | undefined> },rawName: string,prevValue: EventValue | null,nextValue: EventValue | null,instance: ComponentInternalInstance | null = null
) {const invokers = el[veiKey] || (el[veiKey] = {})const existingInvoker = invokers[rawName]if (nextValue && existingInvoker) {existingInvoker.value = nextValue // 直接改 invoker.value} else {const [name, options] = parseName(rawName)if (nextValue) {const invoker = (invokers[rawName] = createInvoker(nextValue, instance))addEventListener(el, name, invoker, options)} else if (existingInvoker) {removeEventListener(el, name, existingInvoker, options)invokers[rawName] = undefined}}
}

源码节选(同文件)——.once/.passive/.capture 的解析

const optionsModifierRE = /(?:Once|Passive|Capture)$/
function parseName(name: string): [string, EventListenerOptions | undefined] {let options: EventListenerOptions | undefinedif (optionsModifierRE.test(name)) {options = {}let mwhile ((m = name.match(optionsModifierRE))) {name = name.slice(0, name.length - m[0].length);(options as any)[m[0].toLowerCase()] = true}}const event = name[2] === ':' ? name.slice(3) : hyphenate(name.slice(2))return [event, options]
}

源码节选(同文件)——.once/.passive/.capture 的解析

const optionsModifierRE = /(?:Once|Passive|Capture)$/
function parseName(name: string): [string, EventListenerOptions | undefined] {let options: EventListenerOptions | undefinedif (optionsModifierRE.test(name)) {options = {}let mwhile ((m = name.match(optionsModifierRE))) {name = name.slice(0, name.length - m[0].length);(options as any)[m[0].toLowerCase()] = true}}const event = name[2] === ':' ? name.slice(3) : hyphenate(name.slice(2))return [event, options]
}

源码节选(同文件)——createInvoker(纳入 error handling 链路)

function createInvoker(initialValue: EventValue, instance: ComponentInternalInstance | null) {const invoker: Invoker = (e: Event & { _vts?: number }) => {if (!e._vts) e._vts = Date.now()else if (e._vts <= invoker.attached) returncallWithAsyncErrorHandling(patchStopImmediatePropagation(e, invoker.value),instance,ErrorCodes.NATIVE_EVENT_HANDLER,[e])}invoker.value = initialValueinvoker.attached = Date.now()return invoker
}

源码节选(同文件)——成组处理函数与 stopImmediatePropagation 的打补丁

function patchStopImmediatePropagation(e: Event, value: EventValue): EventValue {if (isArray(value)) {const originalStop = e.stopImmediatePropagatione.stopImmediatePropagation = () => {originalStop.call(e);(e as any)._stopped = true}return value.map(fn => (e: Event) => !(e as any)._stopped && fn && fn(e))} else {return value}
}

这几段充分证明“传统模式=元素直绑+统一 invoker 缓存+错误上报”的链路。

2. 错误处理链

事件处理调用通过 callWithErrorHandling / callWithAsyncErrorHandling,触发 app.config.errorHandler / errorCaptured

源码节选(文件:packages/runtime-core/src/errorHandling.ts)——同步/异步错误封装

// 同步封装:统一 try/catch 并转交给 handleError
export function callWithErrorHandling(fn: Function,instance: ComponentInternalInstance | null | undefined,type: ErrorTypes,args?: unknown[],
): any {try {return args ? fn(...args) : fn()} catch (err) {handleError(err, instance, type)}
}// 异步封装:在同步封装之上,对 Promise 结果做 catch 并转交 handleError
export function callWithAsyncErrorHandling(fn: Function | Function[],instance: ComponentInternalInstance | null,type: ErrorTypes,args?: unknown[],
): any {if (isFunction(fn)) {const res = callWithErrorHandling(fn, instance, type, args)if (res && isPromise(res)) {res.catch(err => { handleError(err, instance, type) })}return res}if (isArray(fn)) {return fn.map(f => callWithAsyncErrorHandling(f, instance, type, args))}
}

作用:无论是同步回调还是返回 Promise 的异步回调,最终都会被包进统一的错误处理通道。

源码节选(文件:packages/runtime-core/src/errorHandling.ts)——错误分发流程核心

export function handleError(err: unknown,instance: ComponentInternalInstance | null | undefined,type: ErrorTypes,throwInDev = true,
): void {const contextVNode = instance ? instance.vnode : nullconst { errorHandler, throwUnhandledErrorInProduction } =(instance && instance.appContext.config) || EMPTY_OBJif (instance) {// 1) 自底向上调用父组件的 errorCaptured 钩子let cur = instance.parentconst exposedInstance = instance.proxyconst errorInfo = __DEV__? ErrorTypeStrings[type]: `https://vuejs.org/error-reference/#runtime-${type}`while (cur) {const hooks = cur.ecif (hooks) {for (let i = 0; i < hooks.length; i++) {if (hooks[i](err, exposedInstance, errorInfo) === false) return}}cur = cur.parent}// 2) 应用级 errorHandler(app.config.errorHandler)if (errorHandler) {pauseTracking()callWithErrorHandling(errorHandler, null, ErrorCodes.APP_ERROR_HANDLER, [err, exposedInstance, errorInfo])resetTracking()return}}// 3) 最终兜底(开发环境抛出、生产环境 console.error 或按配置抛出)logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction)
}

作用:组件级 errorCaptured → 应用级 errorHandler → 最终兜底 的三段式链路,正是你文中“错误处理链”的核心。

源码节选(文件:packages/runtime-core/src/errorHandling.ts)——错误类型标识(节选)

export enum ErrorCodes {SETUP_FUNCTION,RENDER_FUNCTION,NATIVE_EVENT_HANDLER = 5, // 重点:原生事件处理出错会标记为此COMPONENT_EVENT_HANDLER,/* ... */APP_ERROR_HANDLER,/* ... */
}export const ErrorTypeStrings: Record<ErrorTypes, string> = {/* ... */[ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',[ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',/* ... */
}

作用:当原生事件处理抛错时,会以 NATIVE_EVENT_HANDLER 类型进入上面的 handleError 流程,从而被 errorCaptured / app.config.errorHandler 捕获。这也能和你文中“传统模式下 invoker 会通过 callWithAsyncErrorHandling 进入错误链路”的描述首尾呼应。


三、Vapor 事件机制:只在 document 绑一次

1. 设计要点
  • 首次遇见某个事件类型(如 click),在 documentaddEventListener 一次统一处理器,之后不解绑。
  • 元素不再直绑处理函数,而是在节点对象上存一个私有字段,如 $evtclick$evtmousedown 等。
  • 统一处理器根据事件的真实冒泡路径,自下而上查找每个节点的 $evt{type} 并触发。
  • 若同一节点同一事件多次绑定(如不同修饰符),会把 $evt{type} 从单值升级为数组,依序执行。
2. Vapor 委托流程图
用户触发事件
document 是否已绑定该事件
调用 delegateEvents
在 document 绑定全局监听
统一处理函数 delegatedEventHandler
确定起始节点 为 composedPath 首元素 或 target
当前节点是否存在
结束
当前节点是否有事件句柄
移动到父节点 或 host
处理函数是否为数组
依次调用 若 cancelBubble 为真 则返回
调用处理函数 若 cancelBubble 为真 则返回

源码节选(运行时等价实现示意)——统一注册与统一分发

const delegatedEvents = Object.create(null)
const delegateEvents = (...names: string[]) => {for (const name of names) {if (!delegatedEvents[name]) {delegatedEvents[name] = truedocument.addEventListener(name, delegatedEventHandler)}}
}const delegatedEventHandler = (e: Event) => {let node = (e as any).composedPath?.()[0] || (e.target as Node)if (e.target !== node) Object.defineProperty(e, 'target', { configurable: true, value: node })Object.defineProperty(e, 'currentTarget', {configurable: true,get() { return node || document }})while (node) {const handlers = (node as any)[`$evt${e.type}`]if (handlers) {if (Array.isArray(handlers)) {for (const h of handlers) { if (!(node as any).disabled) { h(e); if ((e as any).cancelBubble) return } }} else {handlers(e); if ((e as any).cancelBubble) return}}node = (node as any).host instanceof Node && (node as any).host !== node? (node as any).host: (node as any).parentNode}
}
3. 同一事件多处理函数如何合并为数组

当同一元素的同一事件多次注册(例如不同修饰符)时,编译器多次调用 delegate(el, 'click', handler),把已有单值升级为数组:

function delegate(el: any, event: string, handler: Function) {const key = `$evt${event}`const existing = el[key]if (existing) {el[key] = Array.isArray(existing) ? (existing.push(handler), existing) : [existing, handler]} else {el[key] = handler}
}

四、编译期“委托 or 直绑”的判定条件

Vapor 不会对所有事件都用委托;编译器(compiler-vaporvOn transform)规则大致是:

  1. 静态事件名(不是 @[name] 动态);
  2. 没有事件选项修饰符once / capture / passive
  3. 事件在可委托清单内(常见如 clickinputkeydownpointer*touch*focusin/outbeforeinput 等)。
决策流程图
监听表达式
事件名是否为静态
直接绑定 addEventListener
是否包含 once capture passive
是否在可委托清单
使用 Vapor 委托 元素记录句柄 document 统一分发

源码节选(文件:packages/compiler-vapor/src/transforms/vOn.ts)——判定条件与清单(示意)

const delegatedEvents = /*#__PURE__*/ makeMap('beforeinput,click,dblclick,contextmenu,focusin,focusout,input,keydown,' +'keyup,mousedown,mousemove,mouseout,mouseover,mouseup,pointerdown,' +'pointermove,pointerout,pointerover,pointerup,touchend,touchmove,touchstart'
)const delegate =arg.isStatic && !eventOptionModifiers.length && delegatedEvents(arg.content)

五、事件修饰符的实现与 .stop 的“混用陷阱”

1. 修饰符由 withModifiers 包装

编译器将监听函数包到 withModifiers(fn, ['stop','alt',...]) 中。运行时先执行“守卫”,不满足条件则直接 return,否则再调用原处理函数。

源码节选(位置:runtime-dom 暴露 helpers;示意实现)

const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] as const
const modifierGuards = {stop: (e: Event) => e.stopPropagation(),prevent: (e: Event) => e.preventDefault(),self: (e: Event) => e.target !== (e as any).currentTarget,ctrl:  (e: KeyboardEvent) => !e.ctrlKey,shift: (e: KeyboardEvent) => !e.shiftKey,alt:   (e: KeyboardEvent) => !e.altKey,meta:  (e: KeyboardEvent) => !e.metaKey,left:   (e: MouseEvent) => 'button' in e && e.button !== 0,middle: (e: MouseEvent) => 'button' in e && e.button !== 1,right:  (e: MouseEvent) => 'button' in e && e.button !== 2,exact: (e: any, mods: string[]) => systemModifiers.some(m => e[`${m}Key`] && !mods.includes(m))
}export const withModifiers = (fn: Function, modifiers: string[]) => {const cache = (fn as any)._withMods || ((fn as any)._withMods = {})const key = modifiers.join('.')return cache[key] || (cache[key] = (event: Event, ...args: any[]) => {for (const m of modifiers) {const guard = (modifierGuards as any)[m]if (guard && guard(event, modifiers)) return}return fn(event, ...args)})
}
2. .stop 的边界
  • .stope.stopPropagation()统一处理器阶段发生;它能阻断 Vapor 的委托分发
  • 阻断不了你在元素或祖先上手写的原生 addEventListener(那些在真实冒泡阶段就触发了);
  • 混用传统与 Vapor 时,容易出现:“子节点 @click.stop 了,但父节点原生监听仍被触发”的现象。

示例:

<script setup lang="ts" vapor>
import { onMounted, useTemplateRef } from 'vue'
const elRef = useTemplateRef('elRef')
const add1 = () => console.log('add1 clicked')
onMounted(() => {elRef.value?.addEventListener('click', () => {console.log('native parent listener')})
})
</script><template><div @click="add1" ref="elRef"><div class="div1" @click.stop="add1">add1 按钮</div></div>
</template>

建议

  • 需要 .stop 的路径上,尽量不要并行存在手写原生监听。
  • 或将外层也改为 Vapor 统一委托体系,维持一致的冒泡控制。

六、非冒泡事件:直接绑定

blurmouseenter不冒泡事件,在 Vapor不走委托,直接绑到目标元素上(运行时 _on / addEventListener2)。

源码节选(等价实现)

源码节选(等价实现)

function addEventListener2(el: Element, event: string, handler: any, options?: any) {el.addEventListener(event, handler, options)return () => el.removeEventListener(event, handler, options)
}
function on(el: Element, event: string, handler: any, options: any = {}) {addEventListener2(el, event, handler, options)if (options.effect) {onEffectCleanup(() => {el.removeEventListener(event, handler, options)})}
}

七、组件事件:仍按“传统”处理

  • 组件事件视作 props 回调(如 onClick)传入子组件;不走文档委托;
  • 若父层写了 @click 但子组件未声明此事件,单根组件会透传到其根 DOM;多根且未 v-bind="$attrs" 时会被丢弃
  • 组件自定义事件不冒泡(区别于 DOM 事件)。

源码节选(编译输出形态示意)

const root = t0()
const vnode = _createComponent(_ctx.DemoVue, {onClick: () => _ctx.handleClick,'onCustom-click': () => _ctx.handleCustomClick
})

八、自定义原生事件:跨层传递的一把好钥匙

用浏览器的 CustomEvent 可创建会冒泡的原生事件,便于跨层传递(避免层层 props/emit):

Demo.vue

<script setup lang="ts" vapor>
import { useTemplateRef } from 'vue'
const catFound = new CustomEvent('animalfound', {detail: { name: '猫' },bubbles: true
})
const elRef = useTemplateRef('elRef')
setTimeout(() => elRef.value?.dispatchEvent(catFound), 3000)
</script><template><div ref="elRef">I am demo.vue</div>
</template>

App.vue

<script setup lang="ts" vapor>
import DemoVue from './Demo.vue'
const demoAnimalFound = (ev: CustomEvent<{ name: string }>) => console.log(ev)
const divAnimalFound = (ev: CustomEvent<{ name: string }>) => console.log(ev)
</script><template><div @animalfound="divAnimalFound"><demo-vue @animalfound="demoAnimalFound" /></div>
</template>

目前这类事件通常仍按普通原生事件直绑处理。若未来支持 .delegate 修饰符,开发者可“强制”走委托路径,让自定义冒泡事件同样享受监听器数量优化。


九、Vapor vs 传统:关键差异

  • 绑定位置:传统直绑在元素;Vapordocument 绑一次、元素只存句柄。
  • 寻找处理函数:传统靠 VNode diff 决策增删;Vapor 由统一处理器沿真实 DOM 冒泡路径查找 $evt{type}
  • 修饰符:两者都有;Vapor 修饰符实质是“守卫包装”。
  • .stop 行为:传统能阻断原生冒泡;Vapor 仅阻断 Vapor 的委托分发,对并行原生监听无能为力。
  • 错误处理:传统有 callWithErrorHandling 链路;Vapor 统一处理器默认不包 try/catch,需要业务自兜底。
  • 非冒泡事件:两者都直绑。
  • 组件事件:两者都按 props 回调处理;未声明的事件透传行为一致(单根透传、多根未 $attrs 丢弃)。

十、实践建议

  1. 优先场景:大列表/表格/密集交互页面,冒泡事件多、节点多,Vapor 委托能显著减少监听器数量。
  2. 避免混用陷阱:同一路径尽量不要混用 Vapor 委托与手写原生监听;确需混用时,明确 .stop 的边界。
  3. 修饰符与选项:需要 once/capture/passive 的监听会强制直绑;仅在必要时使用这些选项。
  4. 非冒泡事件:按直绑心智处理即可。
  5. 异常兜底:关键处理函数加 try/catch 并上报,避免异常中断委托链且无人感知。
  6. 组件事件:遵循传统心智;多根组件注意 $attrs 透传。

十一、最小示例

1. Vaporclick 编译要点(示意)
<!-- Vapor SFC:<script setup vapor> -->
<div class="row" @click="handleRow"></div><!-- 编译核心结果(示意) -->
_delegateEvents('click')            // document 只绑一次
n0.$evtclick = _ctx.handleRow       // 元素只存句柄
2. .stop 与原生监听混用的表现
<script setup lang="ts" vapor>
import { onMounted, useTemplateRef } from 'vue'
const refEl = useTemplateRef('refEl')
const inner = () => console.log('inner clicked')
onMounted(() => {// 原生监听:Vapor 的 .stop 无法阻断它refEl.value?.addEventListener('click', () => console.log('native parent'))
})
</script><template><div ref="refEl"><button @click.stop="inner">Click me</button></div>
</template>

结语

Vapor全局事件委托将“把监听器绑在每个元素上”的老路,升级为“在 document一次注册、统一分发”的新路,显著降低监听器数量和运行时开销;同时也带来与传统模式不同的错误传播路径与**.stop 边界**。在 Vapor 与传统并存的阶段,建议你统一事件策略避免混用陷阱,并在关键路径做好异常兜底。当你的页面以大量冒泡事件为主、且节点规模庞大时,Vapor 能带来切实的性能与体积收益。

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

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

相关文章

Day 01(01): Hadoop与大数据基石

目标&#xff1a;建立对大数据生态的整体认知&#xff0c;理解HDFS和MapReduce的核心思想。 8:00-9:30&#xff1a;【视频学习】在B站搜索“Hadoop入门”或“三小时入门大数据”&#xff0c;观看1-2个高播放量的简介视频&#xff0c;了解大数据面临的问题和Hadoop的解决方案。 …

开源 + 免费!谷歌推出 Gemini CLI,Claude Code 的强劲对手

在如今飞速发展的 AI 工具生态中&#xff0c;命令行界面&#xff08;CLI&#xff09;这一开发者与计算机交互的传统方式&#xff0c;正悄然发生着一场颠覆性的变革。2025 年 6 月 25 日&#xff0c;谷歌正式发布开源的 Gemini CLI&#xff0c;这一举措标志着谷歌 Gemini AI 能力…

MacOS - 记录MacOS发烫的好几天 - 幕后黑手竟然是

MacOS - 记录MacOS发烫的好几天 - 幕后黑手竟然是 Mac是不可能出bug的&#xff0c;一定是世界出bug了。 前言 几天前Mac突然开始烫烫的&#xff0c;就这么一烫烫了好几天。这可不行&#xff0c;所以看了下“活动监视器”&#xff0c;发现了一个Code Helper(Plugin)占据200%上下…

Vue基础知识-Vue中:class与:style动态绑定样式

完整源码<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><script src&quo…

终于赶在考试券过期前把Oracle OCP证书考下来了!

&#x1f6a9; 今天终于能松口气了——Oracle OCP证书到手&#xff01; 差点白白浪费一次考试机会&#xff08;1700&#xff09;&#xff01;3月底报名了Oracle OCP&#xff0c;摆烂了大半年&#xff0c;终于是逼着自己在考试券过期前考完了082和083科目&#xff0c;目前已经顺…

Power BI学习笔记-周报销售数据分析

Power BI学习笔记-周报销售数据分析 简介 来自B站的Power BI学习视频的学习笔记。 记录来自B站的Power BI教学视频&#xff0c;由“高级财务BP-Ni”发布&#xff0c;视频发布者主要发布财务类相关的PBI视频&#xff0c;视频长度30分钟左右。 视频链接&#xff1a; 【powerbi周报…

Oracle 数据库与操作系统兼容性指南

前言 作为一个在 Oracle 坑里摸爬滚打多年的老 DBA&#xff0c;最怕听到的就是"这个版本能不能装在这个系统上&#xff1f;"这种问题。昨天又有朋友来问我 Oracle 数据库和操作系统的兼容性&#xff0c;索性把这些年积累的官方兼容性列表整理出来&#xff0c;省得大家…

pytorch初级

本文章是本人通过读《Pytorch实用教程》第二版做的学习笔记&#xff0c;深度学习的核心部分&#xff1a;数据准备 ➡️ 模型构建 ➡️ 模型训练 ➡️ 模型评估与应用。根据上面的思路&#xff0c;我们分为几个部分&#xff1a; 第一部分&#xff1a;PyTorch 基础 - 涵盖了从基本…

UniApp 混合开发:Plus API 从基础到7大核心场景实战的完整指南

在 UniApp 混合开发中&#xff0c;plus API 是连接前端代码与原生设备能力的核心桥梁。基于 5 Runtime&#xff0c;它封装了设备硬件、系统交互、网络通信等近百种原生能力&#xff0c;解决了 UniApp 跨端 API 覆盖不足的问题。但直接使用 plus API 常面临兼容性复杂、回调嵌套…

本周难点问题详细总结

&#x1f4cb; 本周技术问题总结 &#x1f534; 1. 表单校验与用户体验 1.1 表单错误提示不规范 问题&#xff1a;校验失败时缺少页面标识位置&#xff1a;SupplierForm.vue:375代码示例&#xff1a;message.error([基本信息] 表单校验失败&#xff0c;请检查必填字段)影响&…

下一代自动驾驶汽车系统XIL验证方法

摘要自动驾驶汽车测试仍是一个新兴且尚未成熟的过程&#xff0c;全球统一的测试流程尚需时日。实车测试对资源要求极高&#xff0c;因此开发并提升基于虚拟环境的测试方法的效率至关重要。有鉴于此&#xff0c;本文提出一种新颖的 X-in-the-Loop&#xff08;XIL&#xff0c;X 代…

视频数据如何联网共享?

视频数据如何联网共享&#xff1f; 视频联网共享系统&#xff0c;实现前端设备的接入管理以及接入数据的获取。前端设备包括视频设备、卡口设备、Wifi数据采集设备、移动采集设备以及GPS/北斗数据采集设备等。系统实现海量视频数据的快速检索&#xff0c;并为上层数据应用提供视…

Django项目开发全链路:数据库操作、多环境配置、windows/linux项目部署一站式指南

Django项目开发全链路:数据库操作、多环境配置、windows/linux项目部署一站式指南 一、项目初始化 二、创建第一个应用 三、数据库与数据模型的应用 四、创建管理后台用户 五、数据模型与数据库交互之添加 六、数据模型与数据库交互之修改 七、数据模型与数据库交互之查询 八、…

GLib多线程编程实践:从数据结构到线程池的完整指南

引言 GLib是一个功能丰富、跨平台的C程序库,提供了大量高效且经过充分测试的数据结构与算法接口。本文将通过一个完整的实践案例,介绍如何使用GLib实现动态数组、链表、平衡二叉树和线程池,并分享在实际开发中遇到的常见问题及解决方案。 一、GLib核心数据结构实践 1.1 动…

LiteFlow:国产流程编排引擎体验

文章目录一、写在前面二、使用1、Springboot集成2、组件3、表达式4、上下文5、执行器6、脚本组件7、规则配置源8、元数据管理9、异步中的线程池10、动态构造11、决策路由12、生命周期13、其他三、总结一、写在前面 就不做过多介绍了。 官网&#xff1a;https://liteflow.cc/ …

Linux学习:生产者消费者模型

目录1. 生产者消费者模型的相关概念1.1 什么是生产者消费者模型1.2 生产者消费者模型的优势作用2. 多线程简单实现生产者消费者模型2.1 设计方案2.2 代码实现2.2.1 线程类2.2.2 BlockQueue类2.2.3 任务类2.2.4 主干代码1. 生产者消费者模型的相关概念 1.1 什么是生产者消费者模…

《深度学习》卷积神经网络:数据增强与保存最优模型解析及实现

目录 一、数据增强 1. 核心概念 2. 核心目的 3. 常用方法 4. 实现示例&#xff08;基于 PyTorch&#xff09; 5. 自定义数据集加载 二、保存最优模型 1. 核心概念 2. 实现步骤 &#xff08;1&#xff09;定义 CNN 模型 &#xff08;2&#xff09;定义训练与测试函数…

tcpdump用法

tcpdump用法tcpdump一、什么是tcpdump二、命令格式与参数三、参数列表四、过滤规则组合逻辑运算符过滤器关键字理解 Flag 标识符五、常用例子tcpdump 一、什么是tcpdump 二、命令格式与参数 option 可选参数&#xff1a;将在后边一一解释。 proto 类过滤器&#xff1a;根据协…

平衡车 - 电机调速

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 在我们的这篇文章当中&#xff0c;我们主要想要实现的功能的是电机调速功能。在我们的这篇文章中&#xff0c;主要实现的是开环的功能&#xff0c;而非闭环&#xff0c;也就是不加…

从利润率看价值:哪些公司值得长期持有?

&#x1f4a1; 为什么盯紧利润率&#xff1f; 投资者常常盯着营收增长&#xff0c;却忽略了一个更关键的指标——利润率。 收入可以靠规模“堆”出来&#xff0c;但利润率却是企业护城河的真实体现。心理学研究表明&#xff1a;当一个产品或服务被消费者认定为“不可替代”&a…