☰
TypeScript 面试高频题全解:类型系统、面向对象、装饰器与后端实战(FAQGURU 技术指南)
2026/10/14 18:00:14 网站建设 项目流程
  • 教程

【免费下载链接】FAQGURU

:school_satchel: :rocket: :tada: A list of interview questions. This repository is everything you need to prepare for your technical interview.

项目地址:https://gitcode.com/gh_mirrors/fa/FAQGURU
点击查看免费下载

导读:本文以 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 主要由三部分组成:

  1. Language(语言)——对开发者最重要的一部分,由新语法、关键字组成,让你能编写 TypeScript 代码。
  2. Compiler(编译器)——开源、跨平台、规范公开,且本身由 TypeScript 编写。它把 TypeScript 编译为 JavaScript,遇到错误会输出错误信息;还能把多个文件拼接成单一输出文件,并生成 source map。
  3. 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, readonly

5.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 库

对于没有类型声明的外部库,原文档给出了三步走的方法:

  1. 在编译后的 TypeScript 文件之前引入库的源文件;
  2. 在 TypeScript 文件中、使用库之前声明全局变量:
declare var libGlobal: any;
  1. 之后像普通函数一样使用库的任意功能。

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,而非 String

10.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:

  • null
  • undefined
  • NaN
  • 空字符串''
  • 0
  • false

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.

项目地址:https://gitcode.com/gh_mirrors/fa/FAQGURU
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询