Vue 3响应式系统原理与Proxy实战解析
1. Vue 3响应式系统的设计哲学Vue 3的响应式系统重构是框架演进过程中最具突破性的变革之一。作为前端开发者我们早已习惯了Vue 2中基于Object.defineProperty的响应式实现但Vue 3彻底转向了ES6的Proxy和Reflect这一现代JavaScript特性组合。这种转变绝非简单的API替换而是对响应式编程范式的重新思考。1.1 从defineProperty到Proxy的技术跃迁在Vue 2时代响应式系统主要依赖Object.defineProperty来实现数据劫持。这种方式存在几个明显的技术限制属性检测的局限性defineProperty只能对已知属性进行拦截无法感知动态添加的属性。这也是为什么Vue 2中需要使用Vue.set来添加新响应式属性。数组处理的特殊性对于数组操作Vue 2需要重写数组的push、pop等原型方法来实现响应式这种实现方式既不优雅也存在兼容风险。性能开销问题深度嵌套对象的响应式转换需要递归遍历整个对象树初始化时的性能损耗随着数据规模增大而显著增加。Proxy的出现完美解决了这些问题。作为ES6引入的元编程特性Proxy可以拦截对象的基本操作包括属性访问、赋值、删除等甚至能捕获in操作符和Object.keys()等反射操作。这种能力使得Vue 3的响应式系统能够自动追踪动态添加的属性统一处理对象和数组的操作实现更细粒度的依赖收集支持Map、Set等ES6集合类型1.2 Reflect的协同作用Reflect API常常与Proxy配合使用它提供了与Proxy trap相对应的方法。在Vue 3的响应式实现中Reflect主要发挥以下作用保持默认行为在Proxy的handler中我们经常看到类似Reflect.get(target, key, receiver)的调用这确保了在自定义拦截逻辑后仍然能够保持JavaScript默认的对象行为。receiver参数的正确传递Reflect方法接收的receiver参数确保了在继承场景中this绑定的正确性这是直接使用target[key]无法实现的。操作标准化Reflect提供了一组与语言内部方法对应的标准化操作使得元编程代码更加可靠和可预测。2. Proxy在Vue响应式中的核心实现2.1 响应式代理的创建过程Vue 3中创建响应式对象的核心是reactive函数。让我们深入分析其实现要点function reactive(target) { // 只对对象类型进行响应式转换 if (!isObject(target)) { return target } // 避免重复代理 if (target[ReactiveFlags.RAW]) { return target } // 不同类型的对象使用不同的处理器 const proxy new Proxy( target, targetType TargetType.COLLECTION ? collectionHandlers : baseHandlers ) // 缓存代理对象 reactiveMap.set(target, proxy) return proxy }这个基础实现中包含了几个关键设计决策类型区分处理普通对象和集合类型(Map/Set等)使用不同的handler因为它们的操作语义不同。代理缓存使用WeakMap缓存已代理的对象避免重复创建代理带来的性能开销。原始对象访问通过ReactiveFlags.RAW提供访问原始对象的逃生舱这在某些需要绕过代理的场景非常有用。2.2 基础拦截器实现baseHandlers是处理普通对象的核心其典型结构如下const baseHandlers { get(target, key, receiver) { // 处理特殊标志访问 if (key ReactiveFlags.IS_REACTIVE) return true if (key ReactiveFlags.RAW) return target // 依赖收集 track(target, key) // 获取原始值 const res Reflect.get(target, key, receiver) // 深层响应式转换 if (isObject(res)) { return reactive(res) } return res }, set(target, key, value, receiver) { // 获取旧值用于比较 const oldValue target[key] // 执行默认设置行为 const result Reflect.set(target, key, value, receiver) // 触发依赖更新 if (hasChanged(value, oldValue)) { trigger(target, key) } return result } // 其他trap如deleteProperty, has等 }这个实现中有几个值得注意的技术点惰性代理只有在访问嵌套对象时才会进行响应式转换这相比Vue 2的递归遍历更加高效。变化检测set操作中通过hasChanged比较新旧值避免不必要的触发。反射方法的正确使用始终通过Reflect调用原始操作确保行为一致性。2.3 集合类型的特殊处理对于Map、Set等集合类型Vue 3使用了专门的collectionHandlers。这是因为集合类型的操作语义与普通对象不同const collectionHandlers { get(target, key, receiver) { // 拦截size访问 if (key size) { track(target, ITERATE_KEY) return Reflect.get(target, key, target) } // 拦截方法调用 return function(...args) { // 执行原始方法 const result Reflect.apply( target[key], target, args ) // 根据方法类型触发相应依赖 switch(key) { case add: case delete: case clear: trigger(target, ITERATE_KEY) break case set: trigger(target, args[0]) break } return result } } }这种实现方式使得集合类型的方法调用也能触发响应式更新同时保持了与原生API完全一致的调用方式。3. 依赖收集与派发更新的实现机制3.1 依赖关系的存储结构Vue 3的依赖收集系统相比Vue 2有了显著改进。核心数据结构如下// 全局存储依赖关系 const targetMap new WeakMap() function track(target, key) { if (!activeEffect) return // 获取target对应的depsMap let depsMap targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap new Map())) } // 获取key对应的dep集合 let dep depsMap.get(key) if (!dep) { depsMap.set(key, (dep new Set())) } // 建立双向关联 dep.add(activeEffect) activeEffect.deps.push(dep) }这种设计具有以下优势弱引用存储使用WeakMap作为外层存储不会阻止垃圾回收。精确追踪为每个对象的每个属性维护独立的依赖集合。双向关联effect与dep相互引用便于清理。3.2 派发更新的优化策略trigger函数的实现体现了Vue 3在更新派发上的优化function trigger(target, key) { const depsMap targetMap.get(target) if (!depsMap) return // 收集所有需要执行的effect const effects new Set() // 添加直接关联的effect if (key ! void 0) { const dep depsMap.get(key) dep dep.forEach(effect effects.add(effect)) } // 处理迭代key的依赖 if (key length || isArray(target)) { depsMap.get(length)?.forEach(effect effects.add(effect)) depsMap.get(ITERATE_KEY)?.forEach(effect effects.add(effect)) } // 执行effect effects.forEach(effect { if (effect.options.scheduler) { effect.options.scheduler(effect) } else { effect() } }) }关键优化点包括批量执行使用Set避免重复effect。调度控制支持自定义scheduler这是实现异步批处理的基础。数组特殊处理对length变化和迭代操作进行专门处理。4. 性能优化与实战技巧4.1 响应式系统的性能边界虽然Proxy提供了强大的拦截能力但也需要注意其性能特点初始代理成本创建Proxy对象比普通对象慢约5-10倍但后续操作差异不大。内存开销每个Proxy会额外占用约2-3倍于原对象的内存。冷启动惩罚首次访问属性时JavaScript引擎需要优化Proxy的handler调用。实战建议避免频繁创建/销毁响应式对象对于大型数据集考虑使用shallowRef或markRaw在性能关键路径上减少不必要的响应式访问4.2 Composition API的最佳实践结合Composition API使用时有几个提高响应式效率的技巧// 推荐合并相关状态 const state reactive({ loading: false, data: null, error: null }) // 不推荐分散的ref const loading ref(false) const data ref(null) const error ref(null) // 大型列表优化 const bigList shallowRef([]) // 列表本身不深度响应 const selected reactive(new Set()) // 使用集合存储选中状态4.3 调试与问题排查当响应式行为不符合预期时可以使用以下工具进行调试Vue Devtools查看组件依赖和响应式树。手动检查import { toRaw } from vue console.log(toRaw(myReactiveObj)) // 查看原始对象自定义Proxy handler临时添加日志来跟踪操作const debugHandler { get(target, key) { console.log(GET ${key}) return Reflect.get(...arguments) } } const debugObj new Proxy(myObj, debugHandler)5. 响应式系统的边界情况处理5.1 原始值包装问题Vue 3使用ref来处理原始值的响应式问题其实现本质是function ref(value) { return { get value() { track(this, value) return value }, set value(newVal) { if (hasChanged(newVal, value)) { value newVal trigger(this, value) } } } }这种设计带来了几个需要注意的特点.value访问必须通过.value访问内部值这在组合式函数中可能造成心智负担。模板自动解包在模板中使用时顶层ref会自动解包但嵌套在对象中则不会。ref对象稳定性ref对象本身是稳定的重新赋值.value不会改变引用。5.2 循环引用处理Proxy在处理循环引用时需要特别注意const obj {} obj.self obj const proxy reactive(obj) console.log(proxy.self proxy) // trueVue 3通过以下机制正确处理循环引用代理缓存已经代理过的对象直接返回缓存。原始值标记通过ReactiveFlags.RAW保持对原始对象的访问。相等性保持确保proxy.self proxy维持对象一致性。5.3 不可变数据集成与Immutable.js等库配合使用时推荐模式是import { immutable } from some-immutable-lib import { shallowRef, watchEffect } from vue const state shallowRef(immutable(initialState)) function update() { state.value state.value.set(key, value) } watchEffect(() { console.log(state.value.get(key)) // 仍然响应式 })这种模式结合了不可变数据的优势和Vue的响应式系统。6. 响应式原理的进阶应用6.1 自定义响应式转换基于Vue的响应式原语我们可以构建自己的响应式工具function reactiveRange(start, end) { const range reactive({ start, end, get length() { return this.end - this.start 1 } }) return range } const range reactiveRange(1, 10) watchEffect(() { console.log(Range length: ${range.length}) }) range.end 20 // 触发重新计算6.2 响应式状态机结合Proxy可以实现优雅的状态管理模式function createStateMachine(states) { const state reactive({ current: states.initial, transition(event) { const next states[this.current]?.[event] if (next) this.current next } }) return new Proxy(state, { get(target, key) { if (key in states) { return key target.current } return Reflect.get(...arguments) } }) } const machine createStateMachine({ initial: idle, idle: { start: running }, running: { stop: idle } })6.3 响应式数据验证构建响应式验证系统function createValidator(target, rules) { const errors reactive({}) watchEffect(() { for (const [key, rule] of Object.entries(rules)) { errors[key] rule(target[key]) } }) return { errors, get valid() { return Object.values(errors).every(e !e) } } } const form reactive({ name: , age: 0 }) const { errors, valid } createValidator(form, { name: v v ? null : Name required, age: v v 18 ? null : Must be adult })7. 性能优化深度实践7.1 响应式大列表渲染处理大型列表时的优化策略import { reactive, computed, onMounted } from vue function useVirtualList(dataSource, itemHeight, containerRef) { const state reactive({ startIndex: 0, endIndex: 0, scrollTop: 0 }) const visibleData computed(() dataSource.slice(state.startIndex, state.endIndex) ) const paddingTop computed(() state.startIndex * itemHeight) const paddingBottom computed(() (dataSource.length - state.endIndex) * itemHeight ) onMounted(() { const container containerRef.value const visibleCount Math.ceil(container.clientHeight / itemHeight) state.endIndex state.startIndex visibleCount container.addEventListener(scroll, () { state.scrollTop container.scrollTop state.startIndex Math.floor(state.scrollTop / itemHeight) state.endIndex state.startIndex visibleCount }) }) return { visibleData, paddingTop, paddingBottom } }7.2 响应式数据去抖优化频繁更新的场景import { ref, watch } from vue function useDebouncedRef(value, delay 200) { const debounced ref(value) let timeout watch(() value, (newVal) { clearTimeout(timeout) timeout setTimeout(() { debounced.value newVal }, delay) }) return debounced }7.3 响应式数据分区将大型响应式对象拆分为多个部分function createPartitionedReactive(obj, partitions) { const proxyCache new WeakMap() return partitions.map(keys { const handler { get(target, key) { if (keys.includes(key)) { track(target, key) const value Reflect.get(...arguments) return isObject(value) ? reactive(value) : value } return undefined }, set(target, key, value) { if (keys.includes(key)) { const oldValue target[key] const result Reflect.set(...arguments) if (hasChanged(value, oldValue)) { trigger(target, key) } return result } return true } } return new Proxy(obj, handler) }) }8. 响应式系统设计模式8.1 响应式事件总线基于响应式系统构建事件通信机制function createEventBus() { const listeners reactive({}) return { on(event, callback) { if (!listeners[event]) { listeners[event] [] } listeners[event].push(callback) }, off(event, callback) { if (listeners[event]) { listeners[event] listeners[event].filter(cb cb ! callback) } }, emit(event, ...args) { if (listeners[event]) { listeners[event].forEach(cb cb(...args)) } } } }8.2 响应式状态共享跨组件状态共享模式// shared.js export const sharedState reactive({ count: 0, increment() { this.count } }) // ComponentA.vue import { sharedState } from ./shared // ComponentB.vue import { sharedState } from ./shared8.3 响应式本地存储自动同步到localStorage的响应式状态function usePersistentReactive(key, initialValue) { const stored localStorage.getItem(key) const state reactive(stored ? JSON.parse(stored) : initialValue) watch(() state, (newValue) { localStorage.setItem(key, JSON.stringify(newValue)) }, { deep: true }) return state }9. 测试与调试响应式代码9.1 单元测试策略测试响应式代码的特殊考虑import { reactive, effect } from vue/reactivity describe(reactive system, () { test(should trigger effect, () { const obj reactive({ count: 0 }) let dummy effect(() { dummy obj.count }) expect(dummy).toBe(0) obj.count expect(dummy).toBe(1) }) test(should not trigger unrelated effect, () { const obj reactive({ a: 1, b: 2 }) const fn jest.fn() effect(() { fn(obj.a) }) expect(fn).toHaveBeenCalledTimes(1) obj.b 3 expect(fn).toHaveBeenCalledTimes(1) }) })9.2 性能测试方法测量响应式操作的开销function measureReactivePerf() { const normalObj { data: Array(1000).fill(null).map((_, i) i) } const reactiveObj reactive({ data: Array(1000).fill(null).map((_, i) i) }) // 测试普通对象访问 console.time(normal access) for (let i 0; i 10000; i) { normalObj.data[i % 1000] i } console.timeEnd(normal access) // 测试响应式对象访问 console.time(reactive access) for (let i 0; i 10000; i) { reactiveObj.data[i % 1000] i } console.timeEnd(reactive access) }9.3 内存泄漏检测识别和避免响应式内存泄漏function checkMemoryLeaks() { const obj reactive({ data: initial }) let effectCleanup // 模拟组件setup function setup() { effectCleanup effect(() { console.log(obj.data) }) } // 模拟组件卸载 function teardown() { effectCleanup() } setup() // 检查依赖关系 console.log(targetMap.get(obj)) // 应该存在依赖 teardown() // 再次检查 console.log(targetMap.get(obj)) // 应该为空 }10. 响应式系统的未来演进10.1 Vue 3响应式系统的潜在优化方向虽然Vue 3的响应式系统已经相当成熟但仍有一些可能的改进方向更细粒度的依赖追踪目前基于属性的追踪在某些场景下可能过于粗放未来可能引入基于路径或表达式的追踪。编译时优化在编译阶段静态分析模板中的响应式依赖减少运行时的追踪开销。更好的不可变数据集成提供更优雅的方式与Immutable.js等库集成同时保持响应式特性。WebAssembly加速对于性能关键的响应式操作可以考虑使用Wasm进行优化。10.2 响应式编程与其他范式的融合响应式系统可以与其他编程范式相结合响应式函数式结合Ramda等FP库创建声明式数据流。响应式面向对象使用装饰器等语法糖创建响应式类。响应式事件驱动构建复杂的异步事件处理管道。10.3 跨框架响应式标准随着前端生态的发展可能会出现响应式协议标准定义跨框架的响应式接口规范。响应式数据交换格式标准化组件间的响应式数据传递方式。通用响应式调试工具不依赖特定框架的响应式系统调试器。在实际项目中我发现响应式系统的设计需要权衡灵活性和性能。过度使用深层响应式会导致性能问题而过于保守的设计又会丧失响应式的便利性。一个实用的建议是根据数据变化的频率和范围来设计响应式结构高频变化的数据使用浅响应式复杂对象图使用惰性代理。

相关新闻

跨境小包物流成本优化与利润提升策略

跨境小包物流成本优化与利润提升策略

1. 跨境小包业务的新利润空间探索最近欧洲市场出现了一个值得关注的现象:多家物流服务商开始对跨境小包收取每单3欧元左右的附加费用。这个看似微小的数字,对于主打"9.9欧元包邮"模式的跨境卖家来说,却可能成为压垮利润的最后一根稻…

2026/9/29 6:02:44 阅读更多 →
如何快速批量下载抖音无水印视频:5分钟掌握终极解决方案

如何快速批量下载抖音无水印视频:5分钟掌握终极解决方案

如何快速批量下载抖音无水印视频:5分钟掌握终极解决方案 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback sup…

2026/9/30 7:44:20 阅读更多 →
UE5 PCG框架GPU编程实战:用CustomHLSL实现点云地形交互

UE5 PCG框架GPU编程实战:用CustomHLSL实现点云地形交互

1. 项目概述:当PCG框架遇上GPU,点云与地形的化学反应最近在捣鼓UE5.5的PCGFrameWork,想搞点不一样的地形交互效果。传统的PCG节点虽然强大,但处理海量点云数据时,CPU的瓶颈就非常明显了,尤其是涉及到实时生…

2026/9/30 7:44:13 阅读更多 →

最新新闻

论文阅读的完整流程:拆解、追问与复现验证

论文阅读的完整流程:拆解、追问与复现验证

前阵子要读一篇医学影像相关的论文,不算难懂,但术语多、细节密。按我以前从头硬啃的读法,读完大概率又是 "好像懂了,说不上来"。其实不少科研从业者都开始反思逐字精读的局限性,《AI 速读时代,谁…

2026/10/1 15:59:30 阅读更多 →
餐饮连锁品牌AI搜索推广怎么做?三家擅长连锁的GEO服务商

餐饮连锁品牌AI搜索推广怎么做?三家擅长连锁的GEO服务商

先把怎么做的思路说在前面。餐饮连锁品牌做AI搜索推广,核心不是去AI平台投广告,而是围绕品牌词、品类词、城市词三类提问,系统经营AI回答里的品牌呈现:先摸清消费者和加盟商在DeepSeek、豆包、千问、元宝、文心等主流AI工具里搜“某某品牌怎么样”“某某城市聚餐推荐”“某某品…

2026/10/1 15:59:30 阅读更多 →
简历解析:AI简历解析智能体是如何把简历读准读透的?

简历解析:AI简历解析智能体是如何把简历读准读透的?

简历是招聘的第一道关:读得准,筛选、匹配、面试才有好起点;读不准,后面全盘跟着偏。可简历格式五花八门,技能经验藏在各种写法里,光靠人眼很难又快又准。AI简历解析要解决的,就是这个“第一道关…

2026/10/1 15:59:30 阅读更多 →
从 Loop 到 Graph:一套让 AI Agent 落地的工程架构设计

从 Loop 到 Graph:一套让 AI Agent 落地的工程架构设计

今年 7 月,Agent 圈子里又冒出一个新词:Graph Engineering。 先别急着皱眉。听到 Graph,很多人第一反应是图神经网络、知识图谱、图数据库这些老面孔,觉得无非是旧概念翻新。但这次不一样。Graph Engineering 不是旧概念的堆叠&am…

2026/10/1 15:59:30 阅读更多 →
MCP 从 0 接入 Cursor:mcp.json 安装配置到最小调用与常见报错

MCP 从 0 接入 Cursor:mcp.json 安装配置到最小调用与常见报错

MCP 从 0 接入 Cursor:mcp.json 安装配置到最小调用与常见报错想让 Cursor Agent「去 GitHub 查 Issue」「读你们内部文档库」,却卡在:MCP 面板一直红灯、改完 JSON 没变化、Token 写进仓库又心虚。工具协议本身不复杂,复杂的是 配…

2026/10/1 15:59:30 阅读更多 →
传统企业数字化——从 Excel 台账到业务系统

传统企业数字化——从 Excel 台账到业务系统

制造业、贸易、工程这些传统行业里,普遍存在一种"数字化夹缝":上了 ERP、上了财务软件,但大量长尾业务——供应商台账、采购订单、设备巡检、样品送检、合同登记——仍然活在 Excel 里。买标准软件嫌贵嫌重,自研又没队伍…

2026/10/1 15:58:30 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →