
文章目录一、Interface 和 type 有什么区别如何区分他们的应用场景1. 核心区别对比2. 功能特性的深度差异**A. 声明合并 (Declaration Merging)****B. 定义能力的上限**3. 应用场景归纳该选哪一个**优先使用 Interface 的场景****优先使用 Type 的场景**4. 继承表现的细微差别总结建议二、 映射类型知识点1. 基本语法2. 核心应用场景与示例**A. 基础转换将键映射为特定类型****B. 配合 keyof克隆/修改对象类型**3. 映射修饰符readonly 与 ?**示例将所有属性变为必填且非只读**4. 键名重映射as 断言**示例生成 Getter 函数类型**5. 内置映射工具类型Standard Utility Types**Partial 的底层实现原理**总结为什么要用映射类型三、declare 关键字的核心作用1. declare 的核心作用2. 什么情况下使用它A. 声明全局变量 (Global Variables)B. 声明外部模块 (Ambient Modules)C. 处理非 JS 资源 (Assets)D. 扩展现有对象 (Global Augmentation)3. 代码对比declare vs 正常声明4. 常见误区总结四、 declare不会产生任何实际的 JavaScript 代码是什么意思1. 深度对比普通声明 vs. declare**场景 A普通声明有产物****场景 Bdeclare 声明零产物**2. 为什么需要这种“虚空声明”3. 类型Shape是什么意思4. 总结五、typeScript 中什么是函数重载 如何实现函数重载它和使用联合类型的参数有什么不同1. 如何实现函数重载示例代码2. 与联合类型Union Types的区别核心区别**精确的类型关联**对比演示3. 什么时候用重载一、Interface 和 type 有什么区别如何区分他们的应用场景在 TypeScript 中interface接口和type类型别名确实非常相似甚至在很多场景下可以互换。但它们在设计理念、功能特性和扩展性上有着本质的区别。我们可以通过以下三个维度来深度解析。1. 核心区别对比特性Interface (接口)Type (类型别名)定义范围只能描述对象结构(Object, Function)。可以定义任何类型(基本类型、联合类型、元组等)。同名合并支持。同名接口会自动合并Declaration Merging。不支持。定义同名 type 会报错。继承方式使用extends关键字。使用(交叉类型) 模拟继承。映射类型不支持。支持。可以基于现有类型生成新类型。2. 功能特性的深度差异A. 声明合并 (Declaration Merging)这是interface最独特的特性。如果你定义了两个同名的接口TypeScript 会自动将它们合并成一个。interfaceUser{name:string;}interfaceUser{age:number;}// 最终 User 包含 name 和 ageconstperson:User{name:Tom,age:18};用途常用于扩展第三方库的类型如在window对象上挂载全局变量。B. 定义能力的上限type的能力比interface更广它不仅能定义对象还能定义“组合拳”。// 基本类型别名typeIDstring|number;// 元组typeData[number,string];// 联合类型 (Interface 无法直接做到)typeStatussuccess|error|pending;// 映射类型 (Interface 无法做到)typeKeysname|age;typeUserMap{[KinKeys]:string};3. 应用场景归纳该选哪一个优先使用 Interface 的场景定义 API 接口或对象模型如果你在定义一个对象的形状如 User, Productinterface的语义更贴合“契约”的概念。**需要被其他类implements**如果你在写面向对象代码接口是最佳选择。开发第三方库为了方便库的使用者通过“声明合并”来扩展你的类型应该暴露interface。优先使用 Type 的场景定义基本类型的别名如type Name string。定义联合类型或交叉类型如type Transport Bus | Car。使用提取、映射等高级类型操作当你需要用到keyof、typeof或工具类型如Partial,Record进行复杂变换时。定义元组类型。4. 继承表现的细微差别Interface 继承编译器会检查属性冲突。如果子类和父类属性冲突但类型不兼容会报错。Type 交叉 ()它会尝试合并。如果类型冲突例如一个是string一个是number该属性会变成never而不会直接在定义时报错这可能导致后续调试困难。总结建议在团队开发中业内通用的“最佳实践”通常是能用 interface 解决的就用 interface只有当 interface 无法实现如联合类型、映射类型时才使用 type。这样可以保持代码风格的统一同时利用 interface 的性能优势TypeScript 编译器在处理 interface 的递归引用和缓存时通常比 type 更快。二、 映射类型知识点在 TypeScript 中映射类型Mapped Types是一种非常强大的“类型工厂”。它的核心思想是基于旧的类型通过某种规则“加工”出一套新的类型。如果你熟悉 JavaScript 的Array.prototype.map()映射类型就是它的“类型空间版”。1. 基本语法映射类型通常结合type关键字和in操作符使用。typeNewType{[KinKeys]:ValueType};K循环变量代表每一个属性名。in遍历操作符。Keys通常是字符串联合类型Union Type或keyof获取的键名集合。2. 核心应用场景与示例A. 基础转换将键映射为特定类型假设我们有一组权限需要将其转换为布尔值结构typePermissionsread|write|delete;typePermissionStatus{[KinPermissions]:boolean;};// 结果相当于// { read: boolean; write: boolean; delete: boolean; }B. 配合keyof克隆/修改对象类型这是最常用的场景基于一个已有的接口生成变体。interfaceUser{id:number;name:string;age:number;}// 将 User 的所有属性都变为 string 类型typeUserStringfied{[KinkeyofUser]:string;};3. 映射修饰符readonly与?在映射过程中你可以使用默认或-来添加或移除属性的修饰符。readonly使属性只读。?使属性变为可选。示例将所有属性变为必填且非只读interfacePartialUser{readonlyid?:number;readonlyname?:string;}typeConcreteUser{-readonly[KinkeyofPartialUser]-?:string;// -readonly 移除了只读限制-? 移除了可选变为必填};4. 键名重映射as断言在 TypeScript 4.1 之后你可以使用as关键字在映射时修改键名。这在生成模板字符串类型时非常有用。示例生成 Getter 函数类型interfacePerson{name:string;age:number;}typeGettersT{[KinkeyofTasget${CapitalizestringK}]:()T[K];};typePersonGettersGettersPerson;// 结果// { getName: () string; getAge: () number; }5. 内置映射工具类型Standard Utility TypesTypeScript 官方利用映射类型原理内置了许多非常实用的工具PartialT将所有属性变为可选。ReadonlyT将所有属性变为只读。PickT, K从 T 中挑选出一组属性 K。RecordK, T构造一个键为 K、值为 T 的类型。Partial的底层实现原理// 源码本质就是映射类型typeMyPartialT{[PinkeyofT]?:T[P];};总结为什么要用映射类型减少重复DRY不需要手动维护多个相似的接口比如一个必填版一个可选版。类型安全当原始接口User增加属性时基于它的映射类型会自动更新不会遗漏。动态生成可以根据业务逻辑批量修改属性的特征如统一加前缀、统一变函数。一句话理解映射类型就是类型的循环语句它让你的类型定义从“静态死板”变得“动态灵活”。/K,/T,三、declare 关键字的核心作用在 TypeScript 中declare关键字的核心作用是“告诉编译器某个变量、类或模块已经存在了你直接编译就行不需要在生成的 JavaScript 中创建它。”它就像是一份“免死金牌”让 TypeScript 在静态检查阶段闭嘴不要报错。1.declare的核心作用类型声明而非实现declare只是用来定义类型Shape它不会产生任何实际的 JavaScript 代码。弥补缺失的上下文当你引用的变量不是由当前项目定义的比如 HTML 中的全局变量、CDN 引入的库TypeScript 默认会因为找不到定义而报错declare用来手动补齐这些定义。2. 什么情况下使用它A. 声明全局变量 (Global Variables)当你通过script标签引入了一些库如 jQuery在main.ts中直接使用$会报错。// 告诉 TS全局已经有个变量叫 $ 了它的类型是函数declareconst$:(selector:string)any;$(#app).hide();// 现在 TS 不会报错了B. 声明外部模块 (Ambient Modules)当你安装了一些非常老的库或者只有.js文件的库且它们没有提供types声明文件时。// 在 typings.d.ts 文件中declaremodulesome-old-utils{exportfunctiondoSomething(n:number):string;}// 在业务代码中import{doSomething}fromsome-old-utils;// 不再报错C. 处理非 JS 资源 (Assets)在 Vite 或 Webpack 项目中我们经常import图片或 CSS。但 TS 只认识.ts/js。// 声明所有的 .png 文件都是模块防止 import 报错declaremodule*.png{constvalue:string;exportdefaultvalue;}D. 扩展现有对象 (Global Augmentation)比如你想给window对象增加一个自定义属性。declareglobal{interfaceWindow{__MY_APP_CONFIG__:any;}}window.__MY_APP_CONFIG__{api:...};// 安全使用3. 代码对比declarevs 正常声明动作代码是否生成 JS 代码目的正常声明const x 1;是创建并初始化变量declaredeclare const x: number;否描述环境中已有的变量4. 常见误区它不是“万能药”declare只是骗过了编译器。如果你的变量在运行时真的不存在代码依然会报错报Uncaught ReferenceError。不需要初始化因为declare不产生 JS所以你不能给它赋值。❌declare const x 1;✅declare const x: number;总结当你遇到“Cannot find name ‘xxx’”或者“Property ‘xxx’ does not exist on type ‘window typeof globalThis’”这种错误而你又百分之百确定这个东西在运行时是存在的通常是外部注入的那就是使用declare的最佳时机。它是连接“纯 JavaScript 环境”和“TypeScript 类型系统”之间的桥梁。四、 declare不会产生任何实际的 JavaScript 代码是什么意思这个问题触及了 TypeScript 的本质TypeScript 是 JavaScript 的“马甲”编译后马甲会脱掉只剩下肉身JS。所谓“不产生实际的 JavaScript 代码”我们可以从编译前后的对比来直观理解。1. 深度对比普通声明 vs.declare场景 A普通声明有产物你在 TS 里写下constversion:string1.0.0;编译成 JS 后constversion1.0.0;// 代码依然存在占内存结论这里的const既参与了 TS 的类型检查也生成了实实在在的 JS 运行逻辑。场景 Bdeclare声明零产物你在 TS 里写下declareconstversion:string;编译成 JS 后// (此处是一片空白没有任何代码生成)结论declare告诉编译器“别操心这个变量怎么来的我保证运行环境里肯定有一个叫version的字符串。” 编译器听完后在检查时会放行但在转换成 JS 时直接把这句话“扔掉”了。2. 为什么需要这种“虚空声明”想象一下你在 HTML 中通过 CDN 引入了一个地图库比如高德地图AMap现实情况当你运行网页时浏览器全局环境下确实有window.AMap。TS 的困境TypeScript 编译器扫描你的本地项目文件发现没写过AMap这个变量它会急得大喊“Cannot find name AMap”你的对策你用declare var AMap: any;。效果TS 编译器安静了它知道AMap存在了。关键点由于 JS 运行环境里已经有了这个库TS 编译时绝不能再生成一个var AMap否则会覆盖掉原有的地图库。3. 类型Shape是什么意思这里的Shape指的是“变量长什么样”。declarenamespaceMyTool{functionstart():void;letcount:number;}这段代码只是给编译器看的一张说明书Shape它告诉 TS有一个叫MyTool的东西它有一个方法叫start一个属性叫count。它没有真正去内存里分配空间创建一个对象也没有写出function start() { ... }的具体逻辑。4. 总结普通代码 逻辑给浏览器执行 类型给 IDE 检查。declare代码 只有类型。它的核心语义是“描述已有的”而不是“创建新的”。如果你在 TS 里写了declare却忘记在 HTML 或其他地方引入对应的 JS 库代码在编译时是完美的但运行时会直接报ReferenceError: xxx is not defined。五、typeScript 中什么是函数重载 如何实现函数重载它和使用联合类型的参数有什么不同在 TypeScript 中函数重载Function Overloads是一种允许同一个函数根据接收不同数量或类型的参数返回不同类型结果的机制。虽然 JavaScript 本身不支持真正的函数重载后面的函数会覆盖前面的但 TypeScript 通过编译时的静态检查模拟了这一特性。1. 如何实现函数重载实现函数重载通常分为两个部分重载签名Overload Signatures定义函数的各种调用方式不包含函数体。实现签名Implementation Signature包含具体的业务逻辑且必须兼容所有的重载签名。示例代码假设我们要写一个reverse函数输入字符串返回字符串输入数组返回数组。// --- 重载签名 ---functionreverse(x:string):string;functionreverse(x:any[]):any[];// --- 实现签名 (通用逻辑) ---functionreverse(x:string|any[]):string|any[]{if(typeofxstring){returnx.split().reverse().join();}else{returnx.slice().reverse();}}// 使用conststrreverse(hello);// 自动推断为 string 类型constarrreverse([1,2,3]);// 自动推断为 number[] 类型注意实现签名对外部是不可见的。在调用时你只能选择定义的“重载签名”之一。2. 与联合类型Union Types的区别你可能会问既然x可以是string | any[]为什么不直接用联合类型核心区别精确的类型关联特性函数重载联合类型输入输出对应强绑定。输入 A 必返回 A输入 B 必返回 B。松散。输入 A 或 B返回结果通常是 ResultA语义化非常清晰。开发者能一眼看出哪些组合是合法的。逻辑混合。需要开发者自己判断参数组合。代码提示编辑器会根据参数给出精确的返回类型建议。返回值通常需要手动类型断言或二次判断。对比演示使用联合类型functioncombine(x:string|number,y:string|number):string|number{return(typeofxnumbertypeofynumber)?xy:${x}${y};}constresultcombine(1,2);// result 的类型是 string | number缺点虽然我们知道传入两个数字会得到数字但 TS 编译器此时只知道结果是string | number你可能需要用as number强转。使用函数重载functioncombine(x:number,y:number):number;functioncombine(x:string,y:string):string;functioncombine(x:any,y:any):any{returnxy;}constresultcombine(1,2);// result 类型精确为 number优点编译器能够根据你传入的1, 2锁定第一个重载签名直接确定返回值为number。3. 什么时候用重载当你需要根据参数类型改变返回类型时如上面的reverse例子。当你需要根据参数数量改变返回类型时。库开发为了给用户提供最完美的 API 文档和自动补全。小贴士如果你的函数逻辑非常简单且不同参数返回的类型相同优先使用联合类型或泛型因为重载会让代码变得冗长。只有在返回类型取决于输入类型时重载才是真正的“大杀器”。