Appearance
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>; // neverinfer 关键字:
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[]>; // string9. 装饰器
问题: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 };