- 教程
【免费下载链接】FAQGURU
:school_satchel: :rocket: :tada: A list of interview questions. This repository is everything you need to prepare for your technical interview.
导读:本文以 FAQGURU 面试题库中韩文版 TypeScript 专题(topics/ko/typeScript.md)为骨架,系统梳理 TypeScript 的定位与价值、内置类型、泛型、接口与类、装饰器、模块与类型声明等 33 个高频面试考点,并补充 Node.js 后端编译运行全流程与易错点剖析。读完本文,你将能完整回答 TypeScript 面试中的概念题与代码判断题,并具备把 TypeScript 应用到实际项目(含后端)的实战能力。
一、TypeScript 是什么:为什么需要它
1.1 从 JavaScript 的痛点说起
JavaScript 是唯一被所有浏览器普遍支持的客户端语言,但它并非一门设计完美的语言:它不是基于类的面向对象语言,不支持基于类的继承,动态类型不可靠,并且缺乏编译期的错误检查。TypeScript 正是为了解决这些问题而生的——可以认为,TypeScript 是"修复" JavaScript 问题的一次尝试。
1.2 TypeScript 的官方定位
TypeScript 是由 Microsoft 开发并维护的免费开源编程语言,它是 JavaScript 的严格超集(strict superset),在语言层面增加了可选静态类型(optional static typing)与基于类的面向对象编程。对于熟悉 C#、Java 等强类型语言的开发者来说,TypeScript 极易上手。用一句话概括:"TypeScript 是一种生成纯 JavaScript 文件的语言。"
正如 readme.md 所列,TypeScript 是 FAQGURU 仓库面试主题之一;本文所依据的韩文版文档(topics/ko/typeScript.md)正是英文原版(topics/en/typeScript.md)的翻译,二者内容一一对应。
TypeScript 官网对它的描述是:"TypeScript 让你以真正想要的方式编写 JavaScript。TypeScript 是 JavaScript 的类型化超集,可编译为纯 JavaScript,适用于任何浏览器、任何宿主、任何操作系统,并且开源。"这里的"typed(类型化)"意味着它会考虑变量、参数和函数的类型。
1.3 为什么选择 TypeScript 而非 JavaScript
在面试中,"为什么用 TypeScript 替代 JavaScript"是必考题,其核心论据可以概括为以下几点:
- 支持新 ECMAScript 标准并向下编译:TypeScript 支持 ES2015 及以后的新特性(模块、箭头函数、类、展开运算符、解构赋值等),并可将它们编译为你指定的更老(或不那么老)的 ECMAScript 目标版本,今天即可使用明天的语法。
- JavaScript 代码本身就是合法的 TypeScript 代码:因为 TypeScript 是 JavaScript 的超集,迁移成本低,可以渐进式改造。
- 为 JavaScript 增加类型支持:TypeScript 的类型系统相当丰富,包含接口(interface)、枚举(enum)、混合类型、泛型、联合类型(union)、交叉类型(intersection)、访问修饰符等;并通过**类型推断(type inference)**让类型标注更省力、更少冗余。
- 显著改善开发体验:IDE 能实时从 TypeScript 编译器获得丰富的类型信息,实现更强大的补全与错误提示。
- 严格的空值检查:开启
--strictNullChecks编译器标志后,除非显式声明为可空类型,否则 TypeScript 编译器不允许把undefined赋给变量。 - 需要构建流程但带来调试便利:使用 TypeScript 需要一个编译到 JavaScript 的构建过程;编译器可以把 source map 信息内联进生成的
.js文件,也可以生成独立的.map文件,从而支持在运行时直接对 TypeScript 源码打断点、检查变量。 - 开源且有强大背书:TypeScript 开源(Apache 2 许可证),由 Microsoft 支持,C# 首席架构师Anders Hejlsberg主导该项目。
二、TypeScript 的三大组成与编译模型
2.1 三个核心组件
TypeScript 主要由三部分组成:
- Language(语言)——对开发者最重要的一部分,由新语法、关键字组成,让你能编写 TypeScript 代码。
- Compiler(编译器)——开源、跨平台、规范公开,且本身由 TypeScript 编写。它把 TypeScript 编译为 JavaScript,遇到错误会输出错误信息;还能把多个文件拼接成单一输出文件,并生成 source map。
- Language Service(语言服务)——为 Visual Studio、VS Code、Sublime、TypeScript Playground 等编辑器提供交互式 TypeScript 体验的能力层。
2.2 为什么需要编译(编译 vs 转译)
浏览器无法直接解释 TypeScript,所以必须把 TypeScript 转换成 JavaScript,这个过程称为编译(compile)。需要澄清的是:这里的"编译"并不产生二进制代码,而是一种源码到源码的翻译,因此也常被称为转译(transpilation)。
2.3 什么是 .map 文件
.map文件是source map(源码映射)文件,它让调试工具能够在"生成的 JavaScript 代码"与"产生它的 TypeScript 源文件"之间建立映射。许多调试器(如 Visual Studio、Chrome DevTools)都能消费这类文件,从而让你直接调试 TypeScript 文件而不是编译后的 JavaScript 文件。配置项sourceMap: true会生成独立的.map文件,inlineSourceMap: true则将其内联到输出文件。
三、类型系统入门:内置类型与类型声明
3.1 内置基本类型
原文档列出了 TypeScript 的 5 种内置基本类型:
- Number(数字):表示数字类型的值,对应双精度浮点数。
var variable_name: number;- String(字符串):表示以 Unicode UTF-16 码元存储的字符序列,与 JavaScript 原生字符串一致。
var variable_name: string;- Boolean(布尔):表示逻辑值,取值只有
true或false。
var variable_name: boolean; // 注意:原文档中写作 bool,TypeScript 实际使用 boolean纠错提示:原文档示例中写的是
bool,但 TypeScript 并没有bool类型,正确的关键字是boolean。面试与编码时务必注意这一细节。
- Null:表示
null字面量,null 类型值本身无法被直接引用。
var variable_name: number = null; // 仅在未开启 strictNullChecks 时合法- Undefined:表示
undefined字面量的类型,是所有类型的子类型。
var variable_name: number = undefined;版本说明:上面两条把
null/undefined赋给number变量的写法,只在未开启--strictNullChecks时被允许;现代 TypeScript 项目默认开启严格模式,此时必须显式声明可空类型,如let a: number | null。
在基本类型之外,现代 TypeScript 还内置了any、unknown、void、never、object、symbol、bigint以及enum、元组(tuple)等类型,面试中常被追问的类型大多出自这一族。
3.2 可选静态类型:any 与类型推断
TypeScript 被称为可选静态类型(optionally statically typed)语言,意思是你可以要求编译器忽略某个变量的类型。使用any数据类型,可以给变量赋任意类型的值,编译期间 TypeScript 不会做错误检查:
var unknownType: any = 4; unknownType = "Okay, I am a string"; unknownType = false; // A boolean.与之相对,编译器也会主动推断类型。例如let num = 42会被推断为number,这正是"输入更少、更少显式标注"的原因。
3.3 类型声明文件与 Typings
TypeScript 的核心工作就是给 JavaScript"加上类型"。当你使用 jQuery、moment.js 等外部库时,库本身的代码里没有类型信息,因此还需要获取描述这些代码类型的文件——即类型声明文件(type declaration file),通常以.d.ts为扩展名。好在大多数常见 JavaScript 库都已有社区编写的类型声明文件。
原文档提到的Typings只是早期安装这些文件的工具;如今的最佳实践是直接用npm安装以@types/开头的包,例如npm install --save-dev @types/jquery。
3.4 interface 与 type 的区别
面试高频题之一:interface与type(类型别名)声明有何区别?
interface声明总是引入一个命名对象类型(named object type);而type别名声明可以为任意类型引入名字,包括基本类型、联合类型、交叉类型等。- 使用
type替代interface会丢失以下能力:interface可以被用在extends或implements子句中命名;而针对对象类型字面量的type别名不能。interface支持多个同名声明的声明合并(declaration merging);而针对对象类型字面量的type别名不能。
因此,描述"对象形状"优先用interface,需要联合类型/交叉类型/基本类型别名时用type。
四、泛型(Generics)
泛型允许我们创建能够作用于多种类型而非单一类型的组件或函数,从而在保持类型安全的同时复用逻辑。原文档给出了一个经典队列示例:
/** A class definition with a generic parameter */ class Queue<T> { private data = []; push = (item: T) => this.data.push(item); pop = (): T => this.data.shift(); } const queue = new Queue<number>(); queue.push(0); queue.push("1"); // ERROR : cannot push a string. Only numbers allowed在Queue<T>中,T是类型参数:实例化为Queue<number>后,push只接受number,因此queue.push("1")会在编译期报错。泛型同样适用于函数(如function identity<T>(arg: T): T)和接口,是构建通用数据结构和类型安全 API 的基础工具。
五、面向对象编程
5.1 支持的面向对象术语
TypeScript 支持的面向对象术语包括:
- Modules(模块)
- Classes(类)
- Interfaces(接口)
- Data Types(数据类型)
- Member functions(成员函数)
5.2 四大面向对象原则
答案是肯定的,面向对象编程有四大核心原则,TypeScript 都能以更小巧、更简洁的语法实现:
- Encapsulation(封装)——通过访问修饰符与 getter/setter 控制成员可见性。
- Inheritance(继承)——通过
extends与super()实现类继承。 - Abstraction(抽象)——通过接口、抽象类定义契约。
- Polymorphism(多态)——通过重载、接口实现等表现多态。
5.3 默认访问修饰符
TypeScript 中,类的每个成员默认都是public(公有),无需显式标注;private与protected需要显式声明。
5.4 getter / setter
TypeScript 支持getter/setter,作为拦截对象成员访问的手段,从而对每个对象上成员的访问方式进行更精细的控制:
class foo { private _bar:boolean = false; get bar():boolean { return this._bar; } set bar(theBar:boolean) { this._bar = theBar; } } var myBar = myFoo.bar; // correct (get) myFoo.bar = true; // correct (set)(说明:示例中var myBar = myFoo.bar应为var myBar = foo.bar,面试时可借机讨论实例化细节。)
5.5 类常量:readonly
在 TypeScript 中,const关键字不能用来声明类属性,否则编译器会报错:"A class member cannot have the 'const' keyword."(类成员不能使用 const 关键字)。自 TypeScript 2.0 起,可以使用readonly修饰符实现只读属性:
class MyClass { readonly myReadonlyProperty = 1; myMethod() { console.log(this.myReadonlyProperty); } } new MyClass().myReadonlyProperty = 5; // error, readonly5.6 子类调用基类构造器
在子类中,通过super()调用基类构造函数:
class Base { constructor(public name: string) {} } class Child extends Base { constructor() { super("base-name"); // 必须先调用 super() 再访问 this } }注意:在派生类构造函数体内,this只能在super()调用之后使用。
5.7 类与接口:区别与选择
- **类(Class)**是对象的工厂(factory)。类定义了对象"长什么样、怎么运作"的蓝图,并通过初始化属性、定义方法来实现该蓝图;类贯穿代码的所有阶段,编译后仍存在(生成构造函数与原型)。
- **接口(Interface)**只存在于 TypeScript 的上下文中,编译器仅用于类型检查;代码被转译到目标语言后,接口会被完全剔除,不产生任何运行时代码。
何时用接口、何时用类?原文档给出的判断标准非常实用:
- 如果你需要(或希望)创建某个自定义对象的实例,同时获得对参数、返回值或泛型等进行类型检查的好处——用类。
- 如果你不创建实例——用接口,好处是不产生任何源码,却仍能"虚拟地"对代码做类型检查。
关于接口的进一步解释:TypeScript 的核心原则之一是类型检查关注值的形状(shape)。接口是一个仅在 TypeScript 上下文内存在的虚拟结构,编译器仅为类型检查而使用它。当你定义接口时,你实际上是在声明:任何遵循这一契约的对象(不一定是类的实例)都必须包含接口所要求的属性。
5.8 模块与类的作用域
原文档以旧式内部模块(internal module)语法演示了作用域规则:模块内定义的类只能在模块内使用,模块外部无法访问。
module Vehicle { class Car { constructor ( public make: string, public model: string) { } } var audiCar = new Car("Audi", "Q7"); } // This won't work var fordCar = Vehicle.Car("Ford", "Figo");上面的fordCar会触发编译期错误。要让模块外的代码能访问该类,需为类加上export关键字:
module Vehicle { export class Car { constructor ( public make: string, public model: string) { } } var audiCar = new Car("Audi", "Q7"); } // This works now var fordCar = Vehicle.Car("Ford", "Figo");版本说明:这里的
module Vehicle { ... }是早期 TypeScript 的"内部模块"写法,如今已由namespace关键字替代;现代代码更推荐使用 ES Module 的import/export语法。原文档"Typescript 的模块是什么"一题也明确指出:**内部模块(Internal Modules)**现在可以用 TypeScript 的 namespace 替代;**外部模块(External Modules)**用于指定并加载多个外部 js 文件之间的依赖,若只使用一个 js 文件,则外部模块不相关。
5.9 类声明产生"类型"与"构造函数"
面试中的一道判断题:下面的代码有效吗?
class Point { x: number; y: number; } let p: Point = { x: 1, y: 2 }; // 有效:Point 同时作为类型使用答案是有效。一个类声明会同时产生两样东西:一个代表"类实例"的类型(type)和一个构造函数(constructor function)。因为类能产生类型,所以在任何可以使用接口(interface)的位置,你都可以使用类。
六、函数与构造器重载
6.1 函数重载
TypeScript 支持函数重载,但实现方式与典型的面向对象语言略有不同:你实际上只写一个实现,外加多个声明(overload signatures),让编译器在调用时做重载匹配。代码编译为 JavaScript 后,只会保留那个具体实现;由于 JavaScript 函数可以接受任意数量的实参,所以运行期一切正常:
class Foo { myMethod(a: string); myMethod(a: number); myMethod(a: number, b: string); myMethod(a: any, b?: string) { alert(a.toString()); } }6.2 构造器重载的四种技巧
TypeScript允许声明多个构造器重载,但只能有一个实现,且该实现必须与所有重载签名兼容。原文档给出了四种常用技巧:
(1)可选参数
class Box { public x: number; public y: number; public height: number; public width: number; constructor(); constructor(obj: IBox); constructor(obj?: any) { this.x = obj && obj.x || 0 this.y = obj && obj.y || 0 this.height = obj && obj.height || 0 this.width = obj && obj.width || 0; } }(2)默认参数
class Box { public x: number; public y: number; public height: number; public width: number; constructor(obj : IBox = {x:0,y:0, height:0, width:0}) { this.x = obj.x; this.y = obj.y; this.height = obj.height; this.width = obj.width; } }(3)静态工厂方法形式的附加重载
class Person { static fromData(data: PersonData) { let { first, last, birthday, gender = 'M' } = data return new this( `${last}, ${first}`, calculateAge(birthday), gender ) } constructor( public fullName: string, public age: number, public gender: 'M' | 'F' ) {} } interface PersonData { first: string last: string birthday: string gender?: 'M' | 'F' } let personA = new Person('Doe, John', 31, 'M') let personB = Person.fromData({ first: 'John', last: 'Doe', birthday: '10-09-1986' })(4)联合类型
class foo { private _name: any; constructor(name: string | number) { this._name = name; } } var f1 = new foo("bar"); var f2 = new foo(1);6.3 强类型的函数参数(回调类型)
在 TypeScript 中,可以为回调声明类型别名,实现强类型的函数参数:
type NumberCallback = (n: number) => any; class Foo { // Equivalent save(callback: NumberCallback): void { console.log(1) callback(42); } } var numCallback: NumberCallback = (result: number) : void => { console.log("numCallback: ", result.toString()); } var foo = new Foo(); foo.save(numCallback)NumberCallback声明了一个"接收number参数"的回调类型,save方法要求传入符合该签名的函数,从而在编译期杜绝传参错误。
七、装饰器(Decorators)
7.1 什么是装饰器
装饰器(Decorator)是一种特殊的声明,可以附加到类声明、方法、访问器(accessor)、属性或参数上。装饰器本质上是接收目标(target)作为参数的函数。借助装饰器,我们可以在目标执行前后运行任意代码,甚至完全用新定义替换目标。
在 ECMAScript 2016(以及 TypeScript)中,有 4 类东西可以被装饰:构造器(constructor)、方法(method)、属性(property)和参数(parameter)。
7.2 属性/方法装饰器的应用:@admin 与 @owner
装饰器可用于修改类行为,在集成到框架中时更加强大。例如,若框架中存在带访问限制(仅限管理员)的方法,可以编写@admin方法装饰器来拒绝非管理员用户的访问,或用@owner装饰器只允许对象的所有者修改它:
class CRUD { get() { } post() { } @admin delete() { } @owner put() { } }实操提示:使用装饰器需要在
tsconfig.json中开启"experimentalDecorators": true,并通常配合"emitDecoratorMetadata": true(框架依赖元数据反射时需要)。这是现代 Angular/NestJS 生态大量使用的能力。
八、在 Node.js 后端使用 TypeScript
TypeScript 不只服务于浏览器或前端代码,你同样可以选择用它编写后端应用。例如选择 Node.js,获得额外的类型安全以及语言带来的其他抽象。原文档给出了一条完整的操作链:
第 1 步:安装 TypeScript 编译器(全局)
npm i -g typescript第 2 步:创建 tsconfig.json
TypeScript 编译器以tsconfig.json的形式接收选项,它决定构建文件输出到哪里,总体上与 babel 或 webpack 配置很相似:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "declaration": true, "outDir": "build" } }各选项含义:
target:编译目标的 ECMAScript 版本(如es5、es2017、es2020);module:模块系统(Node.js 后端通常用commonjs,浏览器构建常用es2015/esnext);declaration:是否同时生成.d.ts声明文件;outDir:编译产物输出目录(如build)。
实际项目中常补充"strict": true、"sourceMap": true、"esModuleInterop": true等选项以开启严格检查与调试支持。
第 3 步:编译 ts 文件
tsc第 4 步:运行
node build/index.js按此流程,src下的.ts文件会被编译到build目录,之后用 Node 直接运行编译产物即可。若按module: commonjs编译,产物是 CommonJS 模块,Node.js 可直接执行。
九、与外部 JavaScript 库协作
9.1 在 TypeScript 中使用普通 JS 库
对于没有类型声明的外部库,原文档给出了三步走的方法:
- 在编译后的 TypeScript 文件之前引入库的源文件;
- 在 TypeScript 文件中、使用库之前声明全局变量:
declare var libGlobal: any;- 之后像普通函数一样使用库的任意功能。
9.2 declare 关键字与 Ambient 声明
declare 关键字用于声明那些"可能并非源自 TypeScript 文件"的变量。例如,假设有一个名为myLibrary的库,它没有 TypeScript 声明文件,但在全局命名空间中有一个myLibrary命名空间。要在 TypeScript 中使用它,可以写:
declare var myLibrary;TypeScript 会给myLibrary变量any类型。代价是设计期(design time)对该变量没有 Intellisense,但代码中可以使用该库。不用declare也能达到同样效果——直接声明一个any类型的变量:
var myLibrary: any;两段代码产生相同的 JavaScript 输出,但declare写法更易读,且表达了一种ambient declaration(环境声明)。
9.3 Ambients(环境声明)是什么、何时使用
Ambients(又称ambient declaration)是告诉 TypeScript 编译器"真正的源码存在于别处"的一种方式,它帮助把其他 js 库无缝集成进 TypeScript。按惯例,ambient 声明放在以d.ts为扩展名的类型声明文件中。声明环境变量或环境模块的语法如下:
declare module Module_Name { }在客户端 TypeScript 文件中,需要按如下方式引用环境文件:
/// <reference path = " Sample.d.ts" />(注:现代 TypeScript 项目中,引用.d.ts文件更推荐通过tsconfig.json的include/files字段或模块导入完成,/// <reference>指令多用于早期或特殊场景。)
9.4 能否从 JS 库自动生成声明文件
JavaScript 并非总是包含足够的类型信息供 TypeScript 编译器推断代码结构,因此"基于 JavaScript 自动生成定义"很少是现成选项,但仍存在一些工具:
- Microsoft/dts-gen——微软在创建类型时使用的官方起点;
- dtsmake——看起来很有前景,依赖 Ternjs(一些编辑器用它为 JS 代码提供自动补全)。
实践建议:优先查找
@types/社区声明包;确实没有时,可先用declare var x: any过渡,再逐步把any细化为精确类型。
十、易错点与编码规范
10.1 不要使用 Number / String / Boolean / Object 包装类型
原文档专门有一题"这段代码有什么问题":永远不要使用Number、String、Boolean或Object作为类型注解。这些类型指的是非原始(non-primitive)的装箱对象(boxed objects),在 JavaScript 代码中几乎不会被正确使用。应改用小写的基本类型,并用非原始的object类型代替Object:
function reverse(s: string): string; // 正确:使用小写 string,而非 String10.2 回调中的可选参数陷阱
为什么下面这类代码会被标记为WRONG?
interface Fetcher { getObject(done: (data: any, elapsedTime?: number) => void): void; }除非你真的有意如此,否则不要在回调中使用可选参数(optional parameters)。上面这段代码的含义非常具体:done回调可能以 1 个参数被调用,也可能以 2 个参数被调用。作者的本意可能是"回调不在乎elapsedTime参数",但要做到这一点根本不需要让参数可选——提供一个接受更少参数的函数始终是合法的。也就是说,回调应声明为接收全部参数,由调用方决定传几个。
10.3 如何检查 null 与 undefined
最简单的做法:
if (value) { }当value不为以下任一值时,该表达式为true:
nullundefinedNaN- 空字符串
'' 0false
TypeScript 遵循 JavaScript 的真值(truthy/falsy)规则。需要精确区分null与undefined时,可进一步使用=== null/=== undefined或??空值合并运算符、?.可选链。
十一、TypeScript 的总体优势
把以上内容收拢起来,TypeScript 带来的收益可以归纳为:
- 有助于代码结构化;
- 使用基于类的面向对象编程;
- 施加编码规范(coding guidelines);
- 提供类型检查(type checking);
- 提供编译期错误检查;
- 提供Intellisense(智能感知)。
面试中还常出现一道开放题:"如果让你改变 TypeScript 的一件事,你会改变什么?"原文档并未给出标准答案——这正是考察候选人思考深度的开放讨论题,可以从类型系统的严格性、装饰器的标准化进度、与 ECMAScript 的演进关系等角度展开自己的观点。
十二、FAQGURU 中的其他参考资源
- 本文所依据的韩文版:topics/ko/typeScript.md
- 英文原版(内容一一对应,可作为术语对照):topics/en/typeScript.md
- 仓库目录索引(TypeScript 位列面试主题之一):readme.md
- 韩文版目录(含 TypeScript 专题入口):topics/ko/readme.md
上述面试题库中的题目多来自真实面试,答案相对精炼,适合作为技术面试前的速查与演练手册;关于本文覆盖的 TypeScript 语法细节,也可以在实际项目中结合官方文档进一步深入。
- 教程
【免费下载链接】FAQGURU
:school_satchel: :rocket: :tada: A list of interview questions. This repository is everything you need to prepare for your technical interview.
相关推荐
FAQGURU JavaScript 面试题全解析:从类型系统、闭包原型到高频算法实战
FAQGURU JavaScript 面试题全解析:从类型系统、闭包原型到高频算法实战 本文以 FAQGURU 开源面试题库中的 JavaScript 主题文档
教程TypeScript 类完全指南:继承、访问修饰符与面向对象高级技巧
TypeScript 类完全指南:继承、访问修饰符与面向对象高级技巧 本篇指南以《TypeScript 使用手册(中文版)》的 类章节 https://link
文档教程FinRobot类装饰器:面向对象编程实践
FinRobot类装饰器:面向对象编程实践 引言:金融AI代理的装饰器革命 在金融科技领域,代码的可维护性和扩展性至关重要。FinRobot项目通过创新的类装饰
人工智能AI Agent金融科技AI 应用大模型RAG
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考