Skip to content

TypeScript 基础面试题

1. TypeScript 基本类型

问题:TypeScript 有哪些基本类型?

答案

基本类型

typescript
// 原始类型
let str: string = 'hello';
let num: number = 42;
let bool: boolean = true;
let u: undefined = undefined;
let n: null = null;
let sym: symbol = Symbol('key');
let big: bigint = 100n;

// 数组
let arr1: number[] = [1, 2, 3];
let arr2: Array<string> = ['a', 'b', 'c'];

// 元组
let tuple: [string, number] = ['hello', 42];

// 枚举
enum Color {
  Red,
  Green,
  Blue
}
let color: Color = Color.Red;

// any
let anything: any = 'hello';
anything = 42;
anything = true;

// unknown
let value: unknown = 'hello';
if (typeof value === 'string') {
  console.log(value.toUpperCase());  // 类型守卫后可以使用
}

// void
function log(message: string): void {
  console.log(message);
}

// never
function error(message: string): never {
  throw new Error(message);
}

// object
let obj: object = { name: 'John' };

2. 接口(Interface)

问题:TypeScript 接口如何使用?

答案

基本接口

typescript
interface Person {
  name: string;
  age: number;
}

const person: Person = {
  name: 'John',
  age: 30
};

// 可选属性
interface User {
  id: number;
  name: string;
  email?: string;  // 可选
}

// 只读属性
interface Point {
  readonly x: number;
  readonly y: number;
}

const point: Point = { x: 10, y: 20 };
// point.x = 20;  // 错误:只读属性

// 函数类型
interface SearchFunc {
  (source: string, subString: string): boolean;
}

const mySearch: SearchFunc = (source, subString) => {
  return source.includes(subString);
};

// 可索引类型
interface StringArray {
  [index: number]: string;
}

const myArray: StringArray = ['Bob', 'Fred'];

// 类类型接口
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
}

接口继承

typescript
interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

const square: Square = {
  color: 'red',
  sideLength: 10
};

// 多重继承
interface PenStroke {
  penWidth: number;
}

interface SquareWithStroke extends Square, PenStroke {
  sideLength: number;
}

const square2: SquareWithStroke = {
  color: 'blue',
  sideLength: 20,
  penWidth: 5
};

3. 类型推断

问题:TypeScript 的类型推断是如何工作的?

答案

基本推断

typescript
// 变量初始化
let x = 3;  // 推断为 number
x = 'hello';  // 错误:不能将 string 分配给 number

// 函数返回值
function add(a: number, b: number) {
  return a + b;  // 推断为 number
}

// 最佳通用类型
let zoo = [new Rhino(), new Elephant(), new Snake()];
// 推断为 (Rhino | Elephant | Snake)[]

// 上下文类型
window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button);  // 推断为 MouseEvent
};

类型断言

typescript
// 尖括号语法
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;

// as 语法(JSX 中使用)
let strLength2: number = (someValue as string).length;

// 非空断言
function fixed(name: string | null) {
  const s: string = name!;  // 断言 name 不为 null
}

4. 泛型

问题:TypeScript 泛型如何使用?

答案

基本泛型

typescript
// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

let output = identity<string>('hello');
let output2 = identity(42);  // 类型推断

// 泛型接口
interface Box<T> {
  value: T;
}

let box: Box<number> = { value: 42 };

// 泛型类
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
  return x + y;
};

泛型约束

typescript
// 约束泛型
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

loggingIdentity({ length: 10, value: 3 });

// 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let obj = { a: 1, b: 2, c: 3 };
getProperty(obj, 'a');  // OK
getProperty(obj, 'z');  // 错误

5. 联合类型和交叉类型

问题:联合类型和交叉类型有什么区别?

答案

联合类型

typescript
// 联合类型
let value: string | number;
value = 'hello';
value = 42;

// 联合类型函数
function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value;
  }
  return padding + value;
}

// 类型守卫
function isString(value: string | number): value is string {
  return typeof value === 'string';
}

if (isString(value)) {
  console.log(value.toUpperCase());
}

交叉类型

typescript
// 交叉类型
interface Person {
  name: string;
}

interface Employee {
  id: number;
}

type PersonEmployee = Person & Employee;

const personEmployee: PersonEmployee = {
  name: 'John',
  id: 123
};

// 混合模式
function extend<T, U>(first: T, second: U): T & U {
  const result = <T & U>{};
  for (const key in first) {
    (<any>result)[key] = (<any>first)[key];
  }
  for (const key in second) {
    (<any>result)[key] = (<any>second)[key];
  }
  return result;
}

6. 类型守卫

问题:TypeScript 类型守卫如何使用?

答案

typeof 类型守卫

typescript
function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value;
  }
  return padding + value;
}

instanceof 类型守卫

typescript
interface Padder {
  getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
  constructor(private spaces: number) {}
  getPaddingString() {
    return Array(this.spaces + 1).join(' ');
  }
}

class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}

function getRandomPadder() {
  return Math.random() < 0.5
    ? new SpaceRepeatingPadder(4)
    : new StringPadder('  ');
}

let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
  padder;  // 类型为 SpaceRepeatingPadder
}

自定义类型守卫

typescript
function isFish(pet: Fish | Bird): pet is Fish {
  return (<Fish>pet).swim !== undefined;
}

if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

7. 映射类型

问题:TypeScript 映射类型如何使用?

答案

基本映射

typescript
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface User {
  name: string;
  age: number;
}

type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }

type PartialUser = Partial<User>;
// { name?: string; age?: number; }

高级映射

typescript
// 添加修饰符
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

// 条件映射
type NonNullable<T> = {
  [P in keyof T]: NonNullable<T[P]>
};

// 键重映射
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
};

interface Person {
  name: string;
  age: number;
}

type LazyPerson = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
// }

8. 条件类型

问题:TypeScript 条件类型如何使用?

答案

基本条件类型

typescript
type NonNullable<T> = T extends null | undefined ? never : T;

type T1 = NonNullable<string | null>;  // string
type T2 = NonNullable<null>;  // never

// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;

type T3 = ToArray<string | number>;  // string[] | number[]

条件类型约束

typescript
type MessageOf<T> = T extends { message: any } ? T['message'] : never;

interface Email {
  message: string;
}

interface Dog {
  bark(): void;
}

type T4 = MessageOf<Email>;  // string
type T5 = MessageOf<Dog>;  // never

infer 关键字

typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

function fn(x: number) {
  return x * x;
}

type T6 = ReturnType<typeof fn>;  // number

type Unpacked<T> = T extends (infer U)[] ? U : T;

type T7 = Unpacked<string[]>;  // string

9. 装饰器

问题:TypeScript 装饰器如何使用?

答案

类装饰器

typescript
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hello, ' + this.greeting;
  }
}

方法装饰器

typescript
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${key} with`, args);
    const result = originalMethod.apply(this, args);
    console.log(`${key} returned`, result);
    return result;
  };
}

class Calculator {
  @log
  add(a: number, b: number) {
    return a + b;
  }
}

属性装饰器

typescript
function format(target: any, key: string) {
  let value = target[key];
  
  const getter = () => {
    return value;
  };
  
  const setter = (newVal: string) => {
    value = newVal.trim();
  };
  
  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Person {
  @format
  name: string;
}

10. 模块和命名空间

问题:TypeScript 模块和命名空间如何使用?

答案

模块

typescript
// math.ts
export const PI = 3.14159;
export function add(a: number, b: number): number {
  return a + b;
}

// main.ts
import { PI, add } from './math';
import * as math from './math';

命名空间

typescript
namespace MyNamespace {
  export interface User {
    name: string;
    age: number;
  }
  
  export function createUser(name: string, age: number): User {
    return { name, age };
  }
}

const user: MyNamespace.User = MyNamespace.createUser('John', 30);

模块和命名空间结合

typescript
// shapes.ts
namespace Shapes {
  export interface Point {
    x: number;
    y: number;
  }
}

// main.ts
import { Shapes } from './shapes';

const point: Shapes.Point = { x: 10, y: 20 };