dep.ts 逐行解读

作者:excel日期:2025/10/8

简化归纳

一、导入与上下文说明(开头几行)

1import { extend, isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
2import type { ComputedRefImpl } from './computed'
3import { type TrackOpTypes, TriggerOpTypes } from './constants'
4import {
5  type DebuggerEventExtraInfo,
6  EffectFlags,
7  type Subscriber,
8  activeSub,
9  endBatch,
10  shouldTrack,
11  startBatch,
12} from './effect'
13

解释:

  • 这些导入给出当前模块依赖的工具函数、类型以及响应式运行时的状态/函数。
  • activeSub:当前正在执行或收集依赖的 Subscriber(副作用/计算)。
  • shouldTrack:是否允许在当前上下文下收集依赖(例如在某些内部读取时关闭追踪)。
  • startBatch / endBatch:用于批处理通知(合并多次触发执行)。
  • EffectFlags:位掩码枚举,用于标识 effect/computed 的状态(如 TRACKING、DIRTY、NOTIFIED 等)。
  • 这些都在后文会反复用到以实现精细控制。

二、全局版本号

1export let globalVersion = 0
2

解释:

  • 每当有 reactive 改变发生,全局版本 globalVersion 随之增长。
  • 作用:给 computed 等提供一个“快速路径”判断,避免在内容未变时重复计算;也可以作为调试或一致性检查的基础。

三、Link:dep ↔ subscriber 的连接节点

1export class Link {
2  version: number
3  nextDep?: Link
4  prevDep?: Link
5  nextSub?: Link
6  prevSub?: Link
7  prevActiveLink?: Link
8
9  constructor(
10    public sub: Subscriber,
11    public dep: Dep,
12  ) {
13    this.version = dep.version
14    this.nextDep =
15      this.prevDep =
16      this.nextSub =
17      this.prevSub =
18      this.prevActiveLink =
19        undefined
20  }
21}
22

解释(要点)

  • Link 表示 单一的 dep (某对象的某个 key) 与 单一的 subscriber(effect / computed)的绑定(many-to-many 的一条边)。
  • 之所以用 Link 而不是直接 Set,是为了:
    • 使用双向链表能在 O(1) 插入/移除节点(减少内存与时间开销)。
    • 允许在 effect 的 deps 列表与 dep 的 subs 列表之间建立双向导航(便于清理,维护访问顺序等)。
  • 字段解释:
    • version:记录 link 与 dep 的版本同步状态(用于清理/重用判定)。
    • nextDep/prevDep:在 effect 的依赖链表 中的双向指针(一个 effect 可能依赖多个 dep)。
    • nextSub/prevSub:在 dep 的订阅者链表 中的双向指针(一个 dep 可能有多个 subscriber)。
    • prevActiveLink:用于在 effect 重新收集依赖时的临时链表(实现细节相关,用于高效重排序/回收)。

四、Dep 类:单个响应式“属性”的依赖容器

1export class Dep {
2  version = 0
3  activeLink?: Link = undefined
4  subs?: Link = undefined
5  subsHead?: Link
6  map?: KeyToDepMap = undefined
7  key?: unknown = undefined
8  sc: number = 0
9  readonly __v_skip = true
10
11  constructor(public computed?: ComputedRefImpl | undefined) {
12    if (__DEV__) {
13      this.subsHead = undefined
14    }
15  }
16
17  track(debugInfo?: DebuggerEventExtraInfo): Link | undefined { ... }
18
19  trigger(debugInfo?: DebuggerEventExtraInfo): void { ... }
20
21  notify(debugInfo?: DebuggerEventExtraInfo): void { ... }
22}
23

字段逐一解释:

  • version:dep 自身的版本(每次 trigger() 增加)。用于和 link.version 协调,判断 link 是否在当前运行中被访问到。
  • activeLink:针对当前 activeSub 的一个快速指针(优化)。当 activeSub 连续多次访问同一个 dep 时,比遍历 subs 更快地找到已有 Link
  • subs(尾指针)和 subsHead(头指针,仅 DEV 用于触发顺序):表示订阅此 dep 的 subscribers 链表。注意实现中以尾为主(方便 append)。
  • map / key:在创建 dep 时,记录回指向所属 target 的 depsMap(用于 debug 或清理)与对应的 key。
  • sc:subscriber counter,dep 的订阅者计数(用于统计/优化)。
  • __v_skip:内部标志(略)——告诉响应式系统此对象在某些流程里要被跳过(实现细节)。

构造参数:

  • computed:如果这个 dep 是由某个 computed 拥有的(computed 内部有自己的 dep),则记录 computed 引用(这将在 computed 首次被订阅时有特殊处理)。

五、Dep.track(debugInfo?) 的完整逻辑(依赖收集)

源码(节选并注释核心流程):

1track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
2  if (!activeSub || !shouldTrack || activeSub === this.computed) {
3    return
4  }
5
6  let link = this.activeLink
7  if (link === undefined || link.sub !== activeSub) {
8    link = this.activeLink = new Link(activeSub, this)
9    // 将 link 添加到 activeSub.deps(作为尾部)
10    if (!activeSub.deps) {
11      activeSub.deps = activeSub.depsTail = link
12    } else {
13      link.prevDep = activeSub.depsTail
14      activeSub.depsTail!.nextDep = link
15      activeSub.depsTail = link
16    }
17    addSub(link)
18  } else if (link.version === -1) {
19    // 重用上次运行中保留的 link:需要将 link.version 同步为当前 dep.version
20    link.version = this.version
21    // 如果该 link 不是 tail,则把它移动到 tail(维护访问顺序)
22    if (link.nextDep) {
23      const next = link.nextDep
24      next.prevDep = link.prevDep
25      if (link.prevDep) {
26        link.prevDep.nextDep = next
27      }
28
29      link.prevDep = activeSub.depsTail
30      link.nextDep = undefined
31      activeSub.depsTail!.nextDep = link
32      activeSub.depsTail = link
33
34      if (activeSub.deps === link) {
35        activeSub.deps = next
36      }
37    }
38  }
39
40  if (__DEV__ && activeSub.onTrack) {
41    activeSub.onTrack( extend({ effect: activeSub }, debugInfo) )
42  }
43
44  return link
45}
46

逐件解释(关键点):

  1. 早退条件
    • !activeSub:没有活跃的副作用则不收集。
    • !shouldTrack:当前上下文禁止收集。
    • activeSub === this.computed:如果当前正在运行的 effect 就是这个 dep 所属的 computed(避免自己追自己)——不收集(防止循环/冗余)。
  2. activeLink 快速路径
    • activeLink 用作快速比较:如果保存的 last activeLink.sub 就是当前 activeSub,说明当前 effect 与此 dep 曾经有过绑定,可以直接复用 Link 而不用重新创建或查找整个 subs 链表。
  3. 创建新 Link 并把它 append 到 activeSub.deps 的尾部
    • activeSub.deps / depsTail:effect 自身维护一个依赖链表,收集完成后用于清理那些不再使用的依赖(优化回收)。
  4. addSub(link)
    • 负责把 link 添加到 dep 的 subs 链表(只在 subscriber 处于 TRACKING 时才真正加入,详见 addSub)。
  5. 重用 link(link.version === -1)
    • 在 effect 的重新运行中,开始时会把之前的所有 link 的 version 置为 -1(表示尚未在本次运行被访问)。
    • 当某个 dep 再次被访问到,会把 link.version 同步为 dep.version;如果此 link 在 effect 的 deps 列表不是尾部,会把它移动到尾部以保持“访问顺序”。访问顺序用于在后续清理时将未访问到(仍为 -1)的 link 高效剪除。
  6. 开发者工具 hook
    • 如果存在 onTrack 钩子(开发时),会把调试信息传给用户。

六、Dep.trigger 与 Dep.notify(触发更新)

1trigger(debugInfo?: DebuggerEventExtraInfo): void {
2  this.version++
3  globalVersion++
4  this.notify(debugInfo)
5}
6

触发要点:

  • 每次 trigger() 时:
    • this.version++:dep 自增版本,之后在下一次 track() 时,link.version 会与之对齐(用于判断 link 是否在新一轮中被访问),从而实现依赖清理与缓存失效。
    • globalVersion++:全局版本也增加,供 computed 的快速路径判断等使用。
    • 然后调用 notify() 真正通知订阅者。

notify 的实现要点(节选)

1notify(debugInfo?: DebuggerEventExtraInfo): void {
2  startBatch()
3  try {
4    if (__DEV__) {
5      for (let head = this.subsHead; head; head = head.nextSub) {
6        if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
7          head.sub.onTrigger( extend({ effect: head.sub }, debugInfo) )
8        }
9      }
10    }
11    for (let link = this.subs; link; link = link.prevSub) {
12      if (link.sub.notify()) {
13        (link.sub as ComputedRefImpl).dep.notify()
14      }
15    }
16  } finally {
17    endBatch()
18  }
19}
20

解释(重要行为与原因):

  1. 批处理包裹
    • startBatch() / endBatch():确保在一次操作中多次触发会合并为一次批次更新(减少重复渲染/计算)。
  2. DEV 模式的 onTrigger
    • 为了调试与 devtools:按 原始顺序(从 head 向 nextSub)调度 onTrigger 钩子(跟后面实际的通知顺序不同),且跳过已经被标记为 NOTIFIED 的 subscriber(避免重复回调)。
  3. 实际通知顺序
    • 实际通知是从 subs 尾部向前(prevSub)遍历。为什么?
      * 这样能保证按 反序 收集到的依赖被先通知,然后在 batch 结束时以原始顺序执行具体回调(实现上可减少冲突)。
  4. computed 的特别处理
    • if (link.sub.notify())notify() 返回 true 表明该 subscriber 是 computed(返回表示 computed 需要特殊处理)。
    • 当 computed 被标记为需要更新时,会触发其 own dep 的 notify(),将 computed 的变更进一步传播给依赖于 computed 的其他 subscribers。这么做的原因是为了减少调用栈深度:先把 computed 的 change 标记好并在这里触发它的 dep 通知,而不是在 computed 内部深层递归去触发,避免过深的 JS 调用栈。
  5. finally 保证
    • finally { endBatch() }:即便内部抛出异常也要正确结束批处理,保持系统状态一致。

七、addSub(link) 函数(把 link 加入 dep 的 subscribers 链表)

1function addSub(link: Link) {
2  link.dep.sc++
3  if (link.sub.flags & EffectFlags.TRACKING) {
4    const computed = link.dep.computed
5    // computed getting its first subscriber
6    if (computed && !link.dep.subs) {
7      computed.flags |= EffectFlags.TRACKING | EffectFlags.DIRTY
8      for (let l = computed.deps; l; l = l.nextDep) {
9        addSub(l)
10      }
11    }
12
13    const currentTail = link.dep.subs
14    if (currentTail !== link) {
15      link.prevSub = currentTail
16      if (currentTail) currentTail.nextSub = link
17    }
18
19    if (__DEV__ && link.dep.subsHead === undefined) {
20      link.dep.subsHead = link
21    }
22
23    link.dep.subs = link
24  }
25}
26

解释(关键点):

  1. link.dep.sc++:订阅者计数增加(统计用)。
  2. if (link.sub.flags & EffectFlags.TRACKING):只有当 subscriber 正确处于 TRACKING 状态时,才真的把它加入 dep 的 subscribers 列表(某些 effect 在某时刻可能被禁用 tracking)。
  3. Computed 的延迟订阅(lazy subscription)
    • link.dep.computed 存在,且当前 dep 还没有 subscribers(!link.dep.subs),说明这是 computed 的第一个订阅者:
      * 给 computed 标记 TRACKING & DIRTY(开启 tracking 并标记为脏),然后延迟地把 computed 自身所依赖的那些 dep(computed.deps)逐个通过 addSub(l) 添加上去,使计算属性在以后依赖项变化时能被正确通知。
      * 这一步非常关键:computed 在没人订阅时通常不建立自己与底层 deps 的双向引用(节省内存、计算),当第一次有外界订阅 computed 时,computed 才会真正订阅它内部依赖。
  4. 把 link 插入到 dep.subs 的尾部
    • 以尾部为主方便追加,并且保留 subsHead(DEV)用于按正确顺序触发 onTrigger 钩子。
  5. 结论:addSub 是把 effect/computed 正式注册为依赖项的机制,同时处理 computed 的第一次订阅的延迟绑定行为。

八、全局依赖表与迭代相关 key

1type KeyToDepMap = Map<any, Dep>
2export const targetMap: WeakMap<object, KeyToDepMap> = new WeakMap()
3
4export const ITERATE_KEY: unique symbol = Symbol(__DEV__ ? 'Object iterate' : '')
5export const MAP_KEY_ITERATE_KEY: unique symbol = Symbol(__DEV__ ? 'Map keys iterate' : '')
6export const ARRAY_ITERATE_KEY: unique symbol = Symbol(__DEV__ ? 'Array iterate' : '')
7

解释:

  • targetMap:顶层 WeakMap,把每个 reactive 对象 target 映射到它的 Map(key -> Dep)
    • 使用 WeakMap 的原因:当 target 对象没有外部引用时,GC 能自动回收相关依赖表,避免内存泄漏。
  • KeyToDepMap:对一个 target 来说,每个属性 key 对应一个 Dep 实例。
  • ITERATE_KEY / MAP_KEY_ITERATE_KEY / ARRAY_ITERATE_KEY
    • 这些是用于“迭代依赖”的特殊 key(Symbol),用于处理 for..in / Object.keys / Map.keys / 数组遍历等操作的依赖追踪。
    • 例如:对对象进行 for..in 的副作用应在属性被 ADDDELETE 时触发,而不是只在某个具体 key 变化时触发 —— 所以使用特殊的 iterate-key。

九、track(target, type, key):外部入口(在 proxy 的 getter 中被调用)

1export function track(target: object, type: TrackOpTypes, key: unknown): void {
2  if (shouldTrack && activeSub) {
3    let depsMap = targetMap.get(target)
4    if (!depsMap) {
5      targetMap.set(target, (depsMap = new Map()))
6    }
7    let dep = depsMap.get(key)
8    if (!dep) {
9      depsMap.set(key, (dep = new Dep()))
10      dep.map = depsMap
11      dep.key = key
12    }
13    if (__DEV__) {
14      dep.track({ target, type, key })
15    } else {
16      dep.track()
17    }
18  }
19}
20

解释:

  • track 是数据访问时调用的入口(常在 Proxy 的 get 钩子里)。
  • 流程:
    1. 仅在允许追踪且有活跃副作用时继续。(避免在读取内部字段或初始化时收集无关依赖)
    2. targetMap 拿到 depsMap,若无则创建。
    3. depsMap 根据 key 拿 Dep,若无则新建并把 map/key 回指上去(便于 debug / 清理)。
    4. 调用 dep.track()(传 debugInfo 时为 dev 模式)。
  • 特别注意:这个函数不会直接把 activeSub 添加到 dep;而是通过 Dep.track() 完成(且 Dep.track 内做了许多优化分支)。

十、trigger(...):外部入口(在 proxy 的 setter/delete/collection 操作中被调用)

完整签名(你给出的):

1export function trigger(
2  target: object,
3  type: TriggerOpTypes,
4  key?: unknown,
5  newValue?: unknown,
6  oldValue?: unknown,
7  oldTarget?: Map<unknown, unknown> | Set<unknown>,
8): void { ... }
9

逻辑要点(详解)

  1. 先拿 depsMap
1const depsMap = targetMap.get(target)  
2if (!depsMap) {  
3  globalVersion++  
4  return  
5}  
  • 如果没有记录任何依赖(从未被 track),直接增加 globalVersion(保持版本一致性),然后返回——没有订阅者就不需要做任何通知工作。
  1. 封装 run helper
    • run(dep) 会根据 DEV 与非 DEV 分别调用 dep.trigger(debugInfo?)dep.trigger(),这是为了统一触发单个 dep。
  2. 开始批处理
    • startBatch() 包裹后续通知操作。
  3. 类型为 CLEAR 的处理
    • 如果是 CLEAR(集合被清空),就对 depsMap 的每个 dep 都执行 run(dep)(因为任何依赖集合结构的 effect 都应被触发)。
  4. 其他情况(SET/ADD/DELETE/普通 SET)
    • 先判断 targetIsArrayisArrayIndex(索引更新)。
    • 数组特殊:长度变化
      * 若 key === 'length':当 length 被设为更短值时,需要触发索引 >= 新长度 的那些 deps(因为元素被移除)。
      * 代码里 if (key === 'length') { depsMap.forEach((dep, key) => { if (key === 'length' || key === ARRAY_ITERATE_KEY || (!isSymbol(key) && key >= newLength)) run(dep) }}) }
      * 解释:触发 length,触发数组迭代依赖(ARRAY_ITERATE_KEY),并且触发所有索引 >= newLength 的键对应的 dep。
    • 普通属性或集合键变化
      * 如果 key !== void 0 || depsMap.has(void 0)run(depsMap.get(key))。注意 depsMap.has(void 0) 是为了处理一些内部用 undefined 作为 key 的场景(实现细节)。
      * 若 isArrayIndex 则还要触发 ARRAY_ITERATE_KEY(因为改变数组中某个索引也会影响到依赖数组遍历的 effect)。
    • 依据 TriggerOpTypes 做额外触发
      * ADD
      * 若目标不是数组:触发 ITERATE_KEY(对象属性新增影响 for..in 等)。
      * 若是 Map:同时触发 MAP_KEY_ITERATE_KEY(Map keys 迭代依赖)。
      * 若是数组且添加的是新索引:触发 'length' 的 dep(新索引使 length 改变)。
      * DELETE
      * 若目标不是数组:触发 ITERATE_KEY,Map 同时触发 MAP_KEY_ITERATE_KEY
      * SET
      * 若是 Map 的 set:触发 ITERATE_KEY(因为 Map 值更新也可能影响某些迭代逻辑)。
    • 结尾 endBatch() 关闭批处理。

目的与设计思想:

  • trigger 的复杂分支是为了准确触发受影响的副作用,避免过度触发(减少渲染/计算),同时确保对集合类型(数组、Map、Set、对象)不同操作语义的正确映射(例如数组 length、迭代器依赖等)。

十一、辅助方法:getDepFromReactive

1export function getDepFromReactive(
2  object: any,
3  key: string | number | symbol,
4): Dep | undefined {
5  const depMap = targetMap.get(object)
6  return depMap && depMap.get(key)
7}
8

解释:

  • 简单的调试/工具用的便捷函数:直接从 targetMap 取出某对象 key 对应的 Dep(若存在)。
  • 在调试工具或开发时会用到该接口来检查当前依赖图。

十二、设计上的若干深入说明(总结与边界细节)

  1. 为什么要用 Link 而不是 Set?
    • Link 允许在 effectdep 两侧分别维护链表(effect 的 deps 列表与 dep 的 subs 列表)。
    • 这样能在 effect 重新执行后把没有被再次访问的依赖(link.version 仍为 -1)快速从链表断开,避免在 GC 或 clear 时遍历大型集合。
    • 双链表便于 O(1) 的插入与移除(不需要在 Set 中搜索并分配大量内存)。
  2. version / link.version 的工作机制
    • 在每次 effect 执行前,effect 会把自己所有的 link.version 标成 -1(表示“未在本次执行被访问”)。
    • 在执行时访问某个 dep->track,会把对应 link.version 同步为 dep.version(标记为已访问)。
    • 执行结束后,仍然为 -1 的 link 表示这个依赖已不再需要,可以清理(在 effect 的收集结束清理流程中实现,这段代码不在你给出的片段中,但 link.version 的语义正是为此设计)。
  3. computed 的懒订阅策略
    • computed 在无人订阅时通常处于惰性(lazy)状态:内部不会把自己与底层依赖建立双向引用,数据只在被读取时计算并缓存。
    • 当 computed 第一次被外部 subscriber 订阅时(在 addSub 里发现 dep 没有 subs),需要把 computed 标记为 TRACKING,并把 computed 已知的依赖通过 addSub 逐个注册到底层 deps 中,这样底层 deps 变更时才能通知到 computed。
    • 这是一种折中的策略:节约内存(无人订阅的 computed 不需占用订阅链表)并在有需要时建立完整订阅链。
  4. 通知顺序与 onTrigger 的差异
    • 源码在 notify 中先按 subsHead 顺序触发 onTrigger(用于 dev 品质的回调),但真正对 subscribers 的通知是按 subs 的反序(从尾到头)。这样设计一方面保证 dev hooks 能看到“原始顺序”的调用(对调试更友好),另一方面允许 runtime 在最终 batch 执行时以另一种顺序处理(通常是为了减少副作用冲突与保证某些顺序语义)。
  5. 对数组的特殊处理
    • 数组是既有索引访问又有 length 语义的特殊容器:改变某个索引可能触发遍历依赖;改变 length 可能截断元素,需要触发被截断元素对应的 deps。
    • 所以代码中对 key === 'length' 的判断和 isArrayIndex 的判断非常重要,能避免在数组操作时无意义或漏掉通知。
  6. 批处理(startBatch / endBatch)
    • 批处理通常会收集本轮触发的 effect,延迟实际的执行到 endBatch 时再按合并策略触发。好处是当一次操作内部改变多处 reactive 时不会重复渲染或重复计算(例如在一个 setter 里做多个属性改动)。
  7. 性能考量
    • 大量的设计选择(弱引用 WeakMap、Link 链表、activeLink 快速路径、computed 延迟订阅、批处理)都是为在现代大型应用中既保证正确性又尽量降低内存与 CPU 开销而设计的。

十二、开发者(DEV)模式的辅助特性

  • onTrack / onTrigger:开发时工具 hook,帮助 devtools 展示依赖关系与触发的上下文信息。
  • subsHead:仅在 DEV 中用于保证 onTrigger 的原始调用顺序。
  • key/map 在 dep 中保存,是为了在 devtools 或 debug 输出中能把 dep 回溯到源对象与属性,便于诊断。

结束语与可扩展建议

  • 上述解释覆盖了你给出的源码中所有逻辑路径、字段和关键分支(包括 computed 交互、数组/集合/迭代的特例、dev hooks、版本/重用机制等)。
  • 如果你愿意,我可以继续:
    • effect 模块中与 activeSubstartBatch/endBatchEffectFlags 相关的实现也贴出来并逐行解释,或者
    • 给出一个图示(依赖图、链表关系、触发顺序)来帮助可视化理解,或
    • 针对某一段(例如 link 重排序、computed 延迟绑定)做性能与复杂度分析并提供改进建议。
  • 现在我已经把文件里你贴出来的所有代码路径与语义做了完整详解;若还想把周边模块(effectcomputed 的实现)也纳入“全部”解释,请直接告诉我要展开哪个模块,我会在同一回复中继续展开(不做异步等待)。

本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。


dep.ts 逐行解读》 是转载文章,点击查看原文


相关推荐


[特殊字符]️ Spring Cloud Eureka 三步通:搭建注册中心 + 服务注册 + 服务发现,通俗易懂!
绝顶少年2025/10/7

📌 引言:什么是服务注册与发现? 在微服务架构中,我们通常会将一个大型系统拆分成多个小服务,比如: 用户服务(user-service) 订单服务(order-service) 支付服务(payment-service) 这些服务可能会​​相互调用​​,比如订单服务要查询用户信息,就需要调用用户服务。 但问题来了:​​订单服务怎么知道用户服务在哪里(IP + 端口)?​​ 👉 ​​这就是服务注册与发现要解决的问题!​​ 🤖 什么是 Eureka? ​​E


最新版 Python 的内置函数大全
IMPYLH2025/10/5

Python 的内建函数 Python 提供了大量开箱即用的内置函数,这些函数就像是你编程工具箱中的瑞士军刀——小巧但功能强大,随时准备帮你解决各种编程挑战。从简单的数据转换到复杂的迭代操作,内置函数都能让我们的代码更加简洁、优雅和高效。 无论你是刚刚踏入编程大门的新手,还是希望提升编码效率的资深开发者,掌握Python内置函数都将为你的编程之旅带来质的飞跃。 让我们放下繁琐的重复代码,拥抱Python内置函数带来的简洁与力量。 Python 3.13 共计 71 个内置函数,以下按字母


人工智能技术的广阔前景
南鸢1.02025/10/4

博主主页:【南鸢1.0】 本文专栏:创作灵感 | 人工智能 文章目录  简介 未来前景与可能性 应对策略与建议 1. 技能提升与再培训 2. 制定与遵循伦理准则 3. 强调人机协作 4. 重视数据安全与隐私 更好的AI工具 1.TensorFlow & PyTorch 2.Tableau 3.ChatGPT和其他对话AI模型 4.Notion AI 总结  简介 人工智能技术的应用前景确实非常广阔,且其在各个领域的潜力和效益也愈加显著。在这一背景下,以下


复盘:第一个 AI 出海站,我做错了哪些事?
孟健AI编程2025/10/3

大家好,我是孟健。 上周,我把自己的第一个出海站点部署上线。 算是把需求挖掘、开发、部署、获客到变现的闭环跑完了。 真的是:惊喜和意外齐飞,踩坑和成长并存。 这篇文章就把整个过程拆开复盘,希望能给正在筹备 AI 出海的你一些参考。 01 选词太随意,开局就挖了坑 当时我只打开 Google Trends,看着趋势线还不错就直接选词。 上线后数据一出才发现,词的全球热度在持续下滑,而且几乎只有巴西有流量。 美国、英国、加拿大等英语主流市场几乎没人搜,打榜和运营正反馈极低。 站点一上来就被迫做多


四种对象型创建模式:抽象工厂、 build模式、原型ProtoType与单例模式
CoderIsArt2025/10/2

1. 抽象工厂模式 (Abstract Factory) 组件框图 ┌─────────────────┐ ┌──────────────────┐ │ Client │ │ AbstractFactory │ ├─────────────────┤ ├──────────────────┤ │ │───────>│+createProductA() │ │


垃圾分类魔法互动墙-垃圾分类展厅设备-VR垃圾分类软件
佩京科技VR2025/10/2

想让垃圾分类知识变有趣?来体验环保魔法墙软件。搭配投影融合硬件,普通墙面秒变知识乐园,每幅环保手绘图都藏着分类小秘密。 在这里,你可以用指尖开启奇妙互动:轻轻触摸手绘图上的图标,原本静态的画面瞬间 “动” 起来。 当用户用手掌触摸墙面后,灯带与灯光效果组合会一起显示,讲述了有害垃圾、厨余垃圾、其他垃圾、可回收垃圾四类垃圾回收的过程。用户可以观察垃圾的回收过程,加深对每种类型垃圾分类回收的认知。 人们将日常生活中所产生的有害垃圾投放到属于有害垃圾的垃圾桶内,就会有专门的运输垃圾车将有害垃圾运


分布式专题——21 Kafka客户端消息流转流程
失散1310/2/2025

Kafka 的 Java 客户端、客户端工作机制(消费者分组消费机制、生产者拦截器机制、消息序列化机制、消息分区路由机制、生产者消息缓存机制、发送应答机制、生产者消息幂等性、生产者数据压缩机制、生产者消息事务)、客户端流程总结、SpringBoot集成Kafka


# vim中给变量添加双引号
猪猪侠|ZZXia10/1/2025

摘要:本文介绍在vim中为shell变量添加双引号的三种方法:1) 手动快捷键选中变量后添加引号;2) 使用全局替换命令;3) 自定义快捷键实现快速添加。还提供了.vimrc配置示例,包含单变量、单行变量和全部变量添加双引号的快捷键定义,帮助开发者避免shell语法警告和潜在风险。


删除Notepad++关于弹窗的反动字样
cpych9/30/2025

删除Notepad++关于弹窗的反动字样


ASM1042芯片在汽车BCM项目的工程化应用探索
国科安芯2025/10/10

摘要 随着汽车产业的快速发展,车身控制模块(BCM)作为汽车电子系统的核心组件之一,对芯片的性能、可靠性和适应性提出了更高的要求。本文综合分析了国科安芯推出的ASM1042芯片的技术特性、可靠性测试结果以及实际应用案例,结合汽车BCM项目的需求背景,系统性地探讨了ASM1042芯片在汽车电子领域的工程化应用潜力。通过对芯片性能的深度剖析以及实际应用中的挑战与解决方案的详细阐述,本文旨在为汽车电子系统的设计与优化提供参考,同时推动高性能通信芯片在汽车领域的广泛应用。 一、引言 在现代汽车架构

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0