TypeScript深入浅出:函数参数为何逆变?协变与逆变完全解析
最近有同事跑过来问我一个类型报错他给一个事件处理器写了(e: MouseEvent) void正要赋给一个声明为(e: Event) void的回调结果 TypeScript 直接把他拦住了。我解释了两句他冒出一句所以函数参数到底是协变还是逆变这个问题我在面试里也经常拿来问人但说实话大部分候选人能背出“参数逆变、返回值协变”这八个字却讲不清楚为什么。这篇文章就把 TypeScript 的协变、逆变以及“函数参数为什么是逆变的”这条推导链完整拆开从赋值兼容讲到strictFunctionTypes最后落到真实代码里的写法。看完你不仅能答面试还能顺手解决一些平时遇到的“编译器跟我作对”问题。1. 协变、逆变和双变先建立直觉而不是记名词1.1 从赋值兼容性这个最朴素的场景开始类型系统里最基础的一个操作是赋值const b: B a。这句话的意思不是“a 改名成 b”而是“a 的值能被当作 B 来用”。TypeScript 判断能不能赋值靠的是“结构子类型”关系——如果 A 拥有 B 的所有成员或者更多那么 A 就可以赋给 B。class Animal { eat(): void {} } class Dog extends Animal { bark(): void {} } const dog: Dog new Dog(); const animal: Animal dog; // OKDog 天然包含 Animal 的全部成员 const dogAgain: Dog animal; // ErrorAnimal 不一定有 bark()这个例子太简单没人会觉得绕。但一旦类型“套上泛型容器”方向问题就来了Dog[]能不能赋给Animal[]BoxDog能不能赋给BoxAnimal如果容器内部有一个函数类型的字段答案又不是一句话能说清的了。协变、逆变本质上就是回答“当泛型参数或函数位置发生变化时子类型关系往哪个方向走”的术语。1.2 “方向一致”是协变“方向反转”是逆变先看官方定义的口语版协变如果Dog是Animal的子类型那么FooDog也是FooAnimal的子类型。方向没变顺着走。逆变如果Dog是Animal的子类型那么FooAnimal反而是FooDog的子类型。方向反了逆着来。双变两个方向都成立既可以把FooAnimal赋给FooDog也可以把FooDog赋给FooAnimal。这在类型理论里叫不安全但在某些场景下是故意保留的。let dogs: Dog[] []; let animals: Animal[] dogs; // 数组是协变的这段代码能通过编译。但注意数组的协变性其实是带洞的——animals.push(new Animal())也能编译只是运行时dogs里就会混进去一个不会bark的Animal。这个洞后面专门展开讲这里先记住“数组协变”这个现象。1.3 用集合论把方向问题钉死很多人觉得协变逆变抽象其实是忘了类型本身可以理解成“所有合法值的集合”。Dog是Animal的子类型等价于“所有 Dog 值的集合”是“所有 Animal 值的集合”的子集。这个视角一旦建立方向的含义就非常直观泛型容器FooT如果只读比如get(): T那它输出的东西只会“给出去”给出去的东西越具体越慷慨所以FooDog可以当FooAnimal用协变成立。如果容器只写比如set(value: T)那它要“收进来”收进来的东西必须能覆盖所有可能输入所以要求越宽越好FooAnimal可以当FooDog用逆变成立。集合包含的方向一个保留一个反转。函数类型恰好同时包含这两种位置这才是它最特殊的地方。别急着往后翻下一节我们把函数推一遍那个经典结论“参数逆变”其实不是背出来的是安全要求逼出来的。2. 函数参数逆变的完整推导从一段危险代码说起2.1 如果参数允许协变类型系统会发生什么为了看清楚“为什么”参数必须逆变先假设 TS 搞反了允许参数协变。也就是说你觉得“能处理所有 Dog 的函数自然也能处理所有 Animal”。这句话乍一听有道理狗都能处理那动物肯定也行我们把这个假设写成代码验证一下。type EatAnimal (food: Animal) void; type EatDog (food: Dog) void; // 假设参数是协变的下面这个赋值会被放行 const eatDog: EatDog (food: Dog) { food.bark(); // 函数体假设传入的一定是 Dog }; const eater: EatAnimal eatDog; // 参数协变Dog : Animal所以 EatDog : EatAnimal假设成立 eater(new Animal()); // 实际传入的是 Animal但函数体要调用 bark()运行时就崩了问题出在哪EatAnimal这个类型的调用方有权传入任何Animal包括那些不会bark的动物。而eatDog的函数体虽然不知道外部会发生什么但它写死了“传入值一定是 Dog”并且调用了 Dog 特有的方法。参数协变等于允许一个只处理窄集的函数去处理宽集这等于让类型系统给运行时事故盖章放行。反过来说把方向倒过来就安全了能处理“所有动物”的函数拿去做“只处理狗”的场景完全没有问题。const eatAnimal: EatAnimal (food: Animal) { food.eat(); // 只要求 Animal 都有的能力 }; const eatDog: EatDog eatAnimal; // 逆变EatAnimal 可以赋给 EatDog // 调用方无论传来什么 DogeatAnimal 都处理得了这就是“函数参数逆变”的底层逻辑参数位置是函数的输入口输入口需要覆盖调用方可能传入的全部集合而不能假设传入的是某个子集。2.2 厨房类比加工能力必须覆盖输入范围拿厨房打个比方。你有一家餐厅菜单上写着“会做菜”对应函数类型(food: Food) Meal之类的宽参数。另有一家只做“会做牛排”的店。现在要给一个顾客配厨师顾客不知道今天会买到什么食材只知道可能是牛肉、猪肉、鱼。你会选哪个厨师肯定选“会做菜”的那个因为他覆盖了全部可能性。反过来如果顾客明确说了只买牛肉那两位厨师都能上岗会做菜的厨师反正也会做牛肉只做牛排的厨师也只做牛肉。注意这里出现了方向的反转需求越具体对厨师的能力要求反而可以越宽。函数的输入位置也是一样——你提供给外界的“处理能力”必须大于等于外界实际提供给你的值的范围。所以下次如果同事问“我不就是把参数类型写窄一点吗为什么编译不过”你可以这样回答窄参数意味着函数体对这个参数做了更多假设调用方却不知道这些假设它按照接口注明的宽参数塞进来一个更“通用”的值你的函数体就炸了。参数的类型是函数对外做出的“无论你给我什么我都能处理”的承诺。2.3 一套成体系的赋值规则与记忆锚点把上面推导总结成一个可复用的规则函数类型之间的赋值条件是这样的(P) R 可以赋给 (P) R 当且仅当 参数位置P 可以赋给 P参数逆变输入范围要求更宽 返回值位置R 可以赋给 R返回值协变产出内容要求更窄或相同这里强烈建议你把下面这张表贴在手边位置变异性通俗理解记忆锚点返回值协变生产过程承诺不要缩水“给出去的越具体越安全”参数逆变消费过程能力要覆盖全部输入“接进来的越广泛越安全”属性协变/由位置决定取决于它是只读还是只写读出协变写入逆变有了这套规则遇到任何函数类型的赋值题不要凭感觉直接看返回类型能不能从右往左给窄给宽 OK参数类型能不能从左往右给宽给窄 OK窄给宽不行。下节我们把返回值那一半也推导一遍你会发现这其实是同一枚硬币的两面。3. 返回值协变与 unknown/never 的位置反转3.1 返回值位置为什么恰好相反先看返回值的协变推导。假设有这样一个产物函数type AnimalProducer () Animal; type DogProducer () Dog; const produceDog: DogProducer () new Dog(); const produceAnimal: AnimalProducer produceDog; // OK返回值协变Dog 是 Animal 的子类型调用方把produceAnimal()的结果当作 Animal 用但实际拿到的是一只 Dog。Animal 有的成员 Dog 全都有所以没问题。反过来的赋值就不行了const produceDog: DogProducer produceAnimal; // Error可能返回一只蝾螈调用方期望拿到 Dog 并调用bark()但produceAnimal只承诺返回 Animal不一定能bark。所以返回值位置的要求和参数位置正好拧着来生产的承诺不能缩水产出越具体越好因为具体类型才等于“我确定给你够用的内容”。参数是入口要能覆盖所有可能返回值是出口只负责给出最小承诺之上的能力。两者组合在一起就是函数类型里最自然的安全边界。3.2 unknown 与 never两个极端位置的重排协变、逆变的体会最深刻的是拿两个极端类型来摆位置unknown是所有类型的父类型集合最大never是所有类型的子类型集合最小。它们放在函数类型里方向会完全重排function fail(): never { throw new Error(always fail); } const produceNumber: () number fail; // OK返回 never 可以赋给返回 number // never 是最小集合任何类型的值都能当它的大集合用所以返回值位置它最“慷慨” function logEverything(value: unknown): void { console.log(value); } const logString: (s: string) void logEverything; // OK参数 unknown 可以赋给参数 string // unknown 是最大集合参数位置它“什么都能接”所以函数最“通用”这两段代码建议自己在 TS Playground 里敲一遍。返回值位置永远往“小”走never是万能淹没型不管你预期返回什么永不返回值的方法都能顶上。参数位置永远往“大”走unknown函数几乎可以赋给任何单参函数因为它承诺自己什么都接得住。当你意识到“参数取宽、返回值取窄”这个非对称关系时TypeScript 里一半的类型报错就都能看懂了。这里要特别提醒一句别记反(v: never) void一点都不通用因为never在参数位置意味着“接不住任何值”想把它赋给(v: string) void时会因为string : never不成立而被拒绝。参数位置谁更大谁更强这个直觉一旦建立后面看方法双变、泛型变异性就顺了。4. 为什么 TypeScript 的方法参数是双变的4.1 strictFunctionTypes 只约束了一种写法如果你平时开了strict: trueTS 5.x 默认strictFunctionTypes会同时开启。但有一个反直觉的细节经常把人坑住同样的参数、同样的类型用“方法声明”和“函数属性”写出来检查规则并不一样。interface HandlerMethod { handle(event: Event): void; // 方法声明 } interface HandlerProp { handle: (event: Event) void; // 函数属性 } const m1: HandlerMethod { handle(event: Event) {} }; // OK const m2: HandlerMethod { handle(event: MouseEvent) {} // 参数明显更窄 }; // OK方法参数是双变更窄也放行 const p1: HandlerProp { handle(event: Event) {} }; // OK const p2: HandlerProp { handle(event: MouseEvent) {} }; // ErrorstrictFunctionTypes 生效函数属性参数必须逆变这个差异一测一个准。造成它存在的历史原因是strictFunctionTypes是 TypeScript 2.6 才引入的在这之前所有函数参数都是双变。当时团队想把这个漏洞收紧但是担心一次把“方法声明”也收紧会让大量现有代码破裂——尤其是 class 继承、接口实现、DOM 对象这些场景里方法参数双变其实挺贴近 JS 实际运行时的宽容度。于是最终方案是双重标准。函数属性和函数类型表达式严格执行逆变方法声明保留双变。4.2 双变的来由与取舍为什么说方法参数双变“贴近 JS 运行时”因为方法最终是在原型链上的子类重写父类方法时TypeScript 只要保证两者“看起来兼容”就能让 JS 生态继续跑。React 的类型定义里甚至有专门为这个设计的bivarianceHacktype EventHandlerE extends SyntheticEvent { bivarianceHack(event: E): void }[bivarianceHack];它就是用对象的方法声明来故意触发双变让各种回调签名都能通过编译比如参数更宽的、更窄的都行。这件事被写在类型定义注释里注释原文大意是“为了兼容这里故意不做逆变检查”。很多库都有类似操作你需要知道它的存在和代价双变是一个类型安全漏洞只是 TS 为了兼容性暂时保留。自己写业务代码时我建议优先用“函数属性”声明让编译器帮你严格检查只有在明确需要兼容多种回调实现时才考虑利用方法双变。4.3 在代码里该如何选择方法声明还是函数属性实际上这个选择可以变成一个非常简单的判断如果这个函数会被当作一等公民传来传去、被赋值、被作为回调传递写成函数属性handle: (event: Event) void。这样你能获得最严格的参数逆变检查。如果这个函数只是某个类/接口的成员方法不怎么被重新赋值写成方法声明handle(event: Event): void。这样可以避免一些因为 class 重载和接口实现之间参数双变引发的过度报错。再给一个可操作的经验判断写库给外部用优先“函数属性”写内部业务对象用“方法声明”问题不大。这不是玄学而是责任边界对外接口需要尽可能拦截错误内部实现则要减少误报、提高可维护性。5. 把这些规则用在真实场景事件回调、比较器与工具函数签名5.1 事件处理器最容易踩的“参数缩窄”问题回到开头同事那个报错。DOM 事件的类型声明是addEventListener(type, listener: EventListenerOrEventListenerObject)其中回调签名约等于(event: Event) void。如果你直接在回调里写document.body.addEventListener(click, (e: MouseEvent) { console.log(e.clientX); });会发现 TS 报错类型(e: MouseEvent) void的参数不能赋给类型(e: Event) void的参数。这正是函数属性位置的逆变约束在起作用。MouseEvent 比 Event 窄函数却承诺自己只能处理单击事件但 addEventListener 未来可能给你派发任意 Event。正确写法是让参数保持接口声明的宽度然后在函数体内收窄document.body.addEventListener(click, (e: Event) { if (e instanceof MouseEvent) { console.log(e.clientX); } });或者使用addEventListener(click, handler as EventListener)这种断言绕过检查——但我不推荐因为这等于把安全责任转嫁给了运行时。理解逆变之后你会明白这个报错不是 TS 在抬杠它在阻止你写一个表面看起来合理、实际会让函数体拿到超范围入参的代码。5.2 比较器与 Reducer逆变在数据处理中的价值逆变不是只会给你添堵它也是复用利器。看Array.prototype.sort的比较器签名(a: T, b: T) number。假设你已经写了一个能比较任意Animal的比较器function compareAnimal(a: Animal, b: Animal): number { return a.age - b.age; } const dogs: Dog[] []; dogs.sort(compareAnimal); // OK能比较所有 Animal 的函数当然能比较 Dog这就是逆变在实际代码里的价值一个能力范围更广的函数可以自动适配所有需求范围更窄的场景。反过来如果你只写了一个compareDog想塞给Animal[]的排序场景编译器会拒绝因为调用方可能拿两只非狗的 Animal 来比较你的 compareDog 就处理不了。Reducer 也是同理。Array.prototype.reduce的回调(acc: T, current: T) T里累积器和当前项都处于输入位置。一个接受更宽类型的 reducer 常常能复用给多个具体数组。下次写工具函数时可以刻意把参数抬宽一点比较器、转换器、埋点回调参数越宽复用面越广。5.3 一个我常用的签名技巧宽参数函数复用基于“参数位置宽者通用”这条规律我写埋点、日志这类“只要拿到值就行”的函数时会直接把参数声明成unknownfunction trackEvent(event: unknown): void { // 内部再通过收窄处理具体字段 } const onClick: (e: MouseEvent) void trackEvent; // OK逆变 const onInput: (e: InputEvent) void trackEvent; // OK逆变这个技巧特别适合两类场景一是对数据类型不敏感、只关心副作用的外部交互函数二是编写工具库时需要给很多不同回调赋值的场景。把参数抬宽编译代码和重构代码的体验会好很多。但注意这只在“确实不需要关心参数细节”时才成立。如果你的函数体依赖了具体类型字段又声明成unknown那就等于把类型检查的手脚全绑了得不偿失。6. 泛型里的协变与逆变为什么 Array 能绕开规则6.1 泛型类型的三种变异性最后把视野拉高一点。泛型类型FooT到底算协变还是逆变取决于T在类型中出现的“位置”。只用输入输出就能判断interface ReadBoxT { get(): T; // T 只出现在返回值位置协变 } interface WriteBoxT { set(value: T): void; // T 只出现在函数参数位置逆变 } interface ReadWriteBoxT { get(): T; set(value: T): void; // T 同时出现在两个位置不变invariant }理论上ReadBoxDog是ReadBoxAnimal的子类型WriteBoxAnimal是WriteBoxDog的子类型而ReadWriteBoxDog和ReadWriteBoxAnimal互不赋值。这是最规整的类型理论模型。但 TypeScript 并没有提供“方差标注”机制比如 C# 的in/out它只按结构推断所以很多容器实际上是没有做到理论上的严格区分。6.2 Array 的协变漏洞与 ReadonlyArray 的正确姿势前面已经提到TS 把数组当成协变的Dog[]可以赋给Animal[]。但从类型理论来说数组同时有读[index]、find和写push、[index] 两个方向它应该是不变的。TS 选择协变是因为 JavaScript 的数组本来就允许运行时替换元素而且如果数组是不变const arr: Animal[] dogArr这种能力会被禁用代码写起来会非常难受。代价是那个洞const dogs: Dog[] [new Dog()]; const animals: Animal[] dogs; // OK数组协变 animals.push(new Animal()); // 编译完全没问题 dogs[0].bark(); // 运行时才炸数组里混进了不会 bark 的 Animal这个例子和函数参数逆变的本质是镜像关系数组在“写”位置偷偷放进了协变规则等于允许把窄容器当宽容器填填进来的内容就超范围了。想绕开这个洞最好用ReadonlyArrayT或readonly T[]const readonlyDogs: readonly Dog[] [new Dog()]; const readonlyAnimals: readonly Animal[] readonlyDogs; // 安全协变没有写操作 // readonlyAnimals.push(new Animal()); // Errorreadonly 数组不允许 push规则很简单对外暴露集合的时候优先声明成readonly。这样你能获得协变的安全复用同时杜绝别人往里面塞超范围的值。我在实际项目里基本给自己的模块导出接口都写上readonly T[]收益立竿见影。6.3 各家语言都在做同样的“实用主义”妥协这里的坑并不是 TypeScript 独有的。Java 数组也是协变的早期 JDK 为了解决sort(Object[])这种方法论问题容忍了不安全的数组协变代价是运行时可能抛ArrayStoreException。C# 的数组同样协变运行时会抛ArrayTypeMismatchException。当代语言里严格处理数组不变性的反而是少数因为对开发者来说“Dog 数组可以当 Animal 数组读”的便利性远大于运行时风险。C# 则通过泛型IEnumerableout T走出了另一条路读接口协变、写接口逆变、具体实现内部校验。TypeScript 的ReadonlyArray其实思路一样只是 JS 的运行时没有数组结构类型强校验所以只能在类型层面做防护。理解完这一段再回头看标题那个问题为什么函数参数是逆变的因为参数位于函数的输入位置输入位置的安全规则是“覆盖全部可能”只有类型体系在输出返回值位置才允许协变。这是类型系统在“使用者负责”这个朴素原则下的必然产物。你现在如果再听到“函数参数是逆变的”这句话应该能直接推演出来而不是点头附和了。我个人在实际项目里验证过很多次写好readonly边界、把回调参数尽量抬宽、把外部接口的函数属性严格声明这三件事加在一起绝大部分由协变逆变引起的运行时事故都会在编译期提前暴露。理解变异性不是让你去死记八股它是在帮你判断哪些“能编译的代码”只是表面安全。

相关新闻

无人机芯片模块设计实战:飞控选型、接口调试与可靠性优化

无人机芯片模块设计实战:飞控选型、接口调试与可靠性优化

无人机这行干久了就会发现,真正把飞机调稳、把功能做扎实,拼的往往不是某一个酷炫的算法,而是藏在机身里那一颗颗芯片和围绕它们展开的模块设计。飞控选什么主控、通信走什么接口、电源怎么监测、视觉算力够不够,这些看似枯燥的技…

2026/10/3 9:44:13 阅读更多 →
Coze低代码Agent开发框架实操指南:五个核心模块与工程落地

Coze低代码Agent开发框架实操指南:五个核心模块与工程落地

这篇是系列第二篇,接着上一篇聊。上篇我讲了为什么低代码Agent开发框架会在2025年之后成为企业落地AI的主流选择,以及Agent开发与传统软件开发的本质区别。这一篇直接落到实操工具上,把Coze平台的核心功能彻底拆开揉碎,给出一份“…

2026/10/3 9:44:13 阅读更多 →
七自由度车辆动力学模型:从方程到仿真实战解析

七自由度车辆动力学模型:从方程到仿真实战解析

方向盘刚转到30度,轮胎和地面摩擦发出的尖啸声就穿透了车窗。这熟悉的失控感让我突然想到——搞车辆仿真的那会儿,被七自由度模型折腾的日日夜夜,可不比现在轻松多少。很多人以为车辆仿真就是把坐标和速度扔给计算机,跑一个动画就…

2026/10/3 9:44:13 阅读更多 →

最新新闻

Agent工程化实战:框架选型、并发网关与RAG增强全解析

Agent工程化实战:框架选型、并发网关与RAG增强全解析

今天的Agent/LLM技术圈依旧没有让人失望,InfoQ、GitHub Trending、知乎问答和几个开源群里同时冒出了不少值得反复看的内容。我花了一整天时间扒完了这批热搜词背后的实际场景和技术细节,整理成这份相对偏工程实践的日报,给正在做Agent开发、…

2026/10/3 10:19:06 阅读更多 →
前端单元测试实战指南:从Vitest选型到组件测试覆盖率落地

前端单元测试实战指南:从Vitest选型到组件测试覆盖率落地

最近带前端小组做技术基建,发现很多同学一听到“写单测”就皱眉,觉得是给项目拖后腿。但只要把工具链和流程理顺,前端单元测试反而是我目前回报率最高的一笔技术投入——它不光是验证某个函数返回值,更多是在帮你守住组件行为、接…

2026/10/3 10:19:06 阅读更多 →
DeepSeek Harness桌面端实操:大模型测试工作台从配置到批量执行

DeepSeek Harness桌面端实操:大模型测试工作台从配置到批量执行

DeepSeek Harness 官方桌面端终于发布了。作为从命令行时代一路配置过来的老用户,我的第一反应不是“又多了一个客户端”,而是“那套折磨人半年的 YAML 配置总算可以甩到后台了”。这个工具说白了就是给大模型做测试和评测的工作台:你把它装上…

2026/10/3 10:19:06 阅读更多 →
Python手写CFD求解器:从涡量-流函数到收敛可视化

Python手写CFD求解器:从涡量-流函数到收敛可视化

简介:本资源是西北工业大学(NWPU)计算流体力学课程高分大作业的完整Python实现方案,面向高校流体力学、航空航天或工程仿真方向的本科生与研究生,用于辅助理解偏微分方程数值解法、网格生成与流场可视化等核心内容。压…

2026/10/3 10:19:06 阅读更多 →
Go 多级排序实战:sort.Interface、稳定排序与泛型封装

Go 多级排序实战:sort.Interface、稳定排序与泛型封装

1. 从一次业务需求说起:为什么 Go 的排序这么“麻烦” 先说我最近遇到的一件事。后台管理系统要导出一张订单列表,排序规则大概是这样的:先按订单状态分组,状态相同就按金额降序,金额也一样就按创建时间升序&#xff0…

2026/10/3 10:19:06 阅读更多 →
AI NAS外接GPU实战:OCuLink加持下的本地LLM推理方案

AI NAS外接GPU实战:OCuLink加持下的本地LLM推理方案

1. 从一台“带显卡接口的NAS”说起:这个项目到底在解决什么问题 第一次看到“AI NAS 外接 GPU”这个组合的时候,我脑子里冒出来的第一个念头是:这不就是把家里那台只会存电影的盒子,硬生生改造成一台能跑大模型的小型工作站吗&am…

2026/10/3 10:18:05 阅读更多 →

日新闻

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南 【免费下载链接】ex-skill 前任 skill 项目地址: https://gitcode.com/gh_mirrors/exsk/ex-skill 前任.skill 是一个运行在 Claude Code 上的开源 Skill:导入微信、iMessage、短信、…

2026/10/3 0:00:27 阅读更多 →
45个经典Linux面试题:从命令到网络排障的完整考点解析

45个经典Linux面试题:从命令到网络排障的完整考点解析

刚开始带应届生的时候,我最头疼的就是他们拿着一摞Linux面试题背得滚瓜烂熟,一上机全露馅。后来自己从被面的人变成面别人的人,才慢慢摸清楚:Linux面试题考的根本不是答案本身,而是你面对一个不确定的系统问题时&#…

2026/10/3 0:01:28 阅读更多 →
SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

简介:本资源是一份面向SAP ABAP开发人员、生产计划专员及ERP实施顾问的实操型操作指南,聚焦SAP生产预留核心业务场景,系统解决物料预留创建、查询、校验与批量处理等高频问题。文档以结构化方式覆盖预留背景原理、OMC2编码规则、工厂级参数配…

2026/10/3 0:01:28 阅读更多 →

周新闻

如何划分训练/验证集: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/10/3 9:14:33 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

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

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

2026/10/3 9:47:50 阅读更多 →
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/10/3 9:42:31 阅读更多 →

月新闻

我发现了一个新思路:用 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/2 10:36:31 阅读更多 →
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/3 9:42:35 阅读更多 →
黑夜航拍船只数据集训练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/3 9:42:36 阅读更多 →