2025前端社招最新面试题汇总- ts篇

738 阅读16分钟

1. ts 与 js对比

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript 与 JavaScript 的区别
TypeScriptJavaScript
JavaScript 的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页
可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误
强类型,支持静态和动态类型弱类型,没有静态类型选项
最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使用
支持模块、泛型和接口不支持模块,泛型或接口
社区的支持仍在增长,而且还不是很大大量的社区支持以及大量文档和解决问题的支持

2. ts 类型

2.1. 基本类型

  • any 允许你对值执行任何操作,但是使用它会放弃类型检查的保护。
  • never 用于函数永远不会正常结束的返回类型。
  • unknown 用在不确定类型时,比 any 更安全因为它不允许你随便操作这个值。
  • nullundefined 用于表示没有值或值未定义。
  • void 用于没有返回任何值的函数。
// 基本类型
let v1: string = 'see'
let v2: number = 32
let v3: boolean = true
// null 是一个表示无值的特殊值,而 undefined 表示未定义。
let v4: null = null
let v5: undefined = undefined
let v6 = Symbol()
//任何类型都可以被归为 any 类型。这让 any 
// 类型成为了类型系统的顶级类型(也被称作全局超级类型)
// any类型进行任何操作和赋值都不会报错
let v7: any = 33 
v7.slice()  // 操作成功
let test2: number = v7 // 赋值成功

// 所有类型也都可以赋值给 unknown。unknown 类型表示任何值。
// 它类似于 any,但是更安全,因为对 unknown 类型的值执行大多数操作都是不允许的
let v8:unknown = 'asd'
v8.slice()  // 操作报错
let test3:number = v8 // 赋值报错, unknown类型只能赋值给any 或者unknown


// never 类型表示永远不存在的值的类型。例如,
// never 类型是那些总是抛出异常或根本就不会有返回值的函数表达式或箭头函数的返回类型。
function error(message: string): never {
  throw new Error(message);
}


//void 类型与 any、never 和 unknown 不同,它表示没有任何类型。
  //在函数中使用 void 类型,表示该函数没有返回值。
function warnUser(): void {
  console.log("This is a warning message");
}

// 联合类型:可以是多种类型
let vv1: string | number = 3
//限定在某几个值中间
let vv2 : 1 | 2 | 3 = 3

2.2. 数组和元组

  • 数组 是同类型元素的集合,适用于处理数据项类型一致的情况。
  • 元组 是固定长度、异质性的数组(不能随意增加元素),适用于需要存储不同类型且位置敏感的数据。

// 数组
let arr1: number[] = [1,2,3]
let arr2: Array<number> = [1,2,4]
// 元组: 加问号说明这个一个是可选的
let arr3: [string,number,boolean?] = ['wwww',2]
arr3[0] = 'asd'

2.3. 枚举 enum

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举。

数字枚举:

数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射。 未设置默认值的从上一个值继续向下编号

// 枚举
enum MyEnum {
    A = 3,
    B,
    C = 3,    // 如果这个不是数字而是字符串,则D会报错,因为无法继续递增
    D
}
console.log(MyEnum.A) // 3  
console.log(MyEnum.B) // 4  
console.log(MyEnum[3])  // C,用最后一个

编译之后,生成一个新的对象,数字和key互相映射

字符串枚举:

enum Direction {
  NORTH = "NORTH2",
  SOUTH = "SOUTH2",
  EAST = "EAS2T",
  WEST = "WEST2",
}
console.log(Direction.NORTH)  // NORTH2
console.log(Direction['NORTH'])   // NORTH2

常量枚举

常量枚举会在 TS 编译期间被删除, 常量枚举的成员在使用的地方会被替换为对应的值

const enum Direction {
  NORTH,
  SOUTH,
  EAST,
  WEST,
}

let dir: Direction = Direction.NORTH; 

// 编译之后
"use strict";
var dir = 0 /* NORTH */;

普通枚举和常量枚举的区别:

  • 普通枚举 enum A {...}, 会将其编译为一个 JS 对象, 对象内就是枚举成员和值的一个相互映射,常量枚举 const enum A {...}, 编译后不会生成任何代码, 会删除 TS 部分内容, 对于使用到的成员只会进行值的替换
  • 由此可见, 使用 常量枚举 会有更好的性能, 避免额外的性能开销; 普通枚举可以用于动态变量

2.4. void 和函数

void 类型像是与 any 类型相反,它表示没有任何类型。通常用于函数没有返回值

函数

  • 参数类型: 默认值、?表示可选值,...rest剩余参数
  • 函数类型:返回
// 
function test(a:number = 10,b?:string,...rest: number[]): void {
    console.log(a + Number(b) + rest[0])
}
// 返回number类型
function test2(a:number = 10,b?:string,...rest: number[]): number {
    return 111
}
test(11,'2',1,2)

2.5. Object和object 和{}

  • Object表示js原型链上的类型,所有数据类型都会指向 Object,所以这里的Object并不是单指一个对象
  • object 表示的是对象类型,只允许引用数据类型,对象或者数组、函数,object,function,array...,
  • {} 相当于js中的 new Object()和Object相同
// Object 和object 的区别
// Object 是原型链上的Object类型,所有数据类型都会指向 Object
// object 表示的是对象类型,只允许引用数据类型,对象或者数组、函数
 
let a:Object = {
  name:'tom',
  age: 18,
};
let a1:Object = '';
let a2:Object = 10;
let a3:Object = [];
let a4:Object = ()=>{};


let b:object = {};
let b1:object = [];


// {} 和 Object 的效果相同,相当于 new Object();
let c:{} = {
  name:'tom',
  age: 18,
};
let c1:{} = '';
let c2:{} = [];
let c3:{} = 10;
let c4:{} = ()=>{};

2.6. 类型断言

就是通过as关键字来将变量强制推断为某种类型。就是绕过ts编译检查,类型断言就是对编译器说:我就是这个类型,无需检查。注意这种转换变量类型和转换的类型必须有重叠的类型,否则是不被允许的

function foo(a: number | string) {
  a = (a as number) + 1 // 将a强制推断为number类型
  return a
}

console.log(foo("2")) // “21”

还有就是如果项目中有些类型编译时报错,我们可以强制断言成any类型。

2.7. 类型守卫

使用类型守卫根据实际情况将类型收窄,然后按不同的类型分别处理。类型守卫能让应用程序的数据类型更安全,不至于程序在编译阶段不报错,但在运行阶段报错。

在语句的块级作用域(if语句内或条目运算符表达式内)缩小变量的一种类型推断的行为。

TS 条件语句中遇到下列条件关键字时,会在语句的块级作用域内缩小变量的类型,这种类型推断的行为称作类型守卫(Type Guard)。类型守卫可以帮助我们在块级作用域中获得更为需要的精确变量类型。

  • 实例判断:instanceof
  • 属性或者方法判断:in
  • 类型判断:typeof
  • 字面量相等判断:==, ===, !==, !=

// in 
function printType(value: User | Student) {
    // grade 只存在 Student 类型上
if ('grade' in value) { 
        // 在 if 块中,value 的数据类型被收窄为 Student 类型
        console.log('它是 Student 类型',value.name + ':' + value.grade)
    } else {
	// 在 else 块中,value 的数据类型被收窄为 User 类型
        console.log('它是 User 类型',value.name)
    }
}


// instanceof 
function printInfo(value: Staff | Student) {
  if (value instanceof Staff) {
    // 在 if 块中,value 的数据类型被收窄为 Staff 类型
    console.log('它是 Staff 类型')
  } else {
    // 在 else 块中,value 的数据类型被收窄为 Student 类型
    console.log('它是 Student 类型')
  }
}

// typeof
function printType(value: string | number) {
    if (typeof value === 'string') {
        // value 的类型被收窄为 string
        console.log('value 是 string 类型')
    } else {
        // value 的类型被收窄为 number
        console.log('value 是 number 类型')
    }
}
// 自定义类型守卫
function predicateStudent(value: Student | User): value is Student {
    return typeof (value as any).garde === 'number'
}
function printObjType(obj: User | Student) {
    if (predicateStudent(obj)) {
        // obj的类型被收窄为 Student
    } else {
        // value 的类型被收窄为 User
    }
}

3. interface 和 type

3.1. interface

interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

  • interface可以重名接口,会将两个接口内的属性进行合并
  • interface可以继承,继承后可以重写属性
  • 可以描述函数类型
interface Myobj  {
    3: string;  
    age: number;
    height?: number; // 可选类型
    [propName: string] :any; // 任意类型
    readonly ss: number // 只读
}
interface Myobj {
  test: number
}

const obj:Myobj = {
    3: '222',
    age: 323,
    ss: 22,
    ss: 'asda',
    test:33
}

// 继承,可以重写
interface Point extends Myobj { 
  y: number; 
}

interface描述函数

// 函数
interface ISum {
    (x:number,y:number):number
}
//使用函数表达式的方式
const add:ISum = (num1, num2) => {
    return num1 + num2
}

3.2. type 类型别名

type关键字可以定义一个类型,类似于 const 定义值(不能重复声明),和interface类似,但是interface只能定义对象的类型,而type不受限,相当于给某一种类型取了一个别的名字便于使用

// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// 联合
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

// 描述函数
type SetPoint = (x: number, y: number) => void;


const a :PartialPoint = {
  x: 33
}

3.3. interface 和type的继承

// interface 继承 interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX { 
  y: number; 
}

// type 继承 type   交叉类型
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

// interface 继承 type
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

// type 继承 interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

3.4. type和interface的区别

  • interface通过extends增加类型
  • type 通过联合类型增加类型 &
  • interface会自动融合同名接口

4. 泛型

泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

提供泛型,允许我们在定义的时候不具体指定类型,而是泛泛地说一种类型,并在函数调用的时候再指定具体的参数类型。

在定义参数的时候,先指定模板,模板可以用于参数或返回值。在函数执行的时候才确定某一个模板到底应该代表什么

function identity<T, U>(value: T, message: U) : T {
  console.log(message);
  return value;
}

console.log(identity<Number, string>(68, "Semlinker"));
// 也可以省略类型声明,自动推导
console.log(identity(68, "Semlinker"));  

//// 泛型函数类型
type MyFunc<T> = (param: T) => void;

interface myFunc<T> {
    (name:T) : T
}

// 使用箭头函数的泛型函数类型
const myFunc: MyFunc<number> = (param) => {
  console.log(param);
};

泛型约束

泛型约束允许限制泛型类型的范围,使泛型类型必须符合特定条件:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // 我们知道这个参数有 .length 属性,所以没问题
    return arg;
}

// 定义了一个泛型函数 loggingIdentity <T>,并使用了泛型约束 extends Lengthwise,
// 表示泛型类型 T 必须符合 Lengthwise 接口的结构。
// 这样,在函数内部就可以安全地访问 arg 参数的 length 属性。

泛型与默认类型

function createArray<T = number>(length: number, value: T): T[] {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result.push(value);
    }
    return result;
}

let arr = createArray(3, 5);  // arr 的类型为 number[]

//reateArray<T = number> 中的 = number 表示 T 的默认类型为 number,
//如果调用函数时没有明确指定 T 的类型,则默认为 number。泛型是 TypeScript 中非常强大和灵活的特性
 // 它可以帮助开发者编写更加通用和可复用的代码,提高代码的灵活性和可维护性。

**


泛型接口

interface GenericIdentityFn<T> {
  (arg: T): T;
}

// 使用箭头函数的泛型接口
const myInterface: GenericIdentityFn<string> = (param) => {
  console.log(param);
};

泛型类

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;
};

5. 函数重载

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。

当 TypeScript 编译器处理函数重载时,它会查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。另外

type Combinable = string | number;
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: Combinable, b: Combinable):Combinable {
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
  return a + b;
}
add(1,2)
add('1','2')

// 类中的方法也可以进行重载
class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: string, b: number): string;
  add(a: number, b: string): string;
  add(a: Combinable, b: Combinable) {
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
    return a + b;
  }
}

const calculator = new Calculator();
const result = calculator.add('Semlinker', ' Kakuqo');

如果没有重载,当一个函数承载多种能力的时候会十分混乱

使用重载后,编辑器提示会更加友好

6. 类

6.1. 基本定义和使用方法

class User {
    // 默认的都是Public 属性
    name: string = 'wang';  // 默认值
    age: number;
    height?: number; // 可选的
    public key2: string;

    // protected 成员仅对声明它们的类 和它的子类可见。
    protected key3: number;

    // private 只能在类中自己使用,
    // #也表示私有属性,具有更强的封装性,版本较新,需要兼容
    private key4: number;
    #name2: string; 

    // static  属性不与类的特定实例相关联。它们可以通过类构造函数对象本身访问
    static key5: number = 5
     // 叠加使用,只能在当前类中自己使用
    private static autor: string;
    // 只读属性
    readonly autor2: string = 'www'

    constructor(name:string,age: number, key2: string, key3: number) {
        this.name = name;
        this.age = age;
        this.key2 = key2
        this.key3 = key3
        this.key4 = 4  //  pirvate属性在类中自己访问
        User.autor  
    }
}

class Person extends User {
    constructor(name:string,age: number, key2: string, key3: number) {
        super(name,age,key2,key3)
        console.log(this.key3)   // 子类可访问父类中的protected 属性
        console.log(this.key4)   // 子类也不能访问父类中的private 属性
    }
}
User.key5 // 通过类本身访问

const person = new User('li',22,'ss', 33);
console.log(person.key3); // 实例中也不能访问protected 属性
console.log(person.key4);  // 实例中也不能访问private属性
console.log(person.key5);  // 实例中也不能访问static属性

6.2. 访问器

私有属性外部不能直接访问,可以通过访问器间接访问

class User {
    private _password: string = ''

    get password():string {
        return '***'/';
    }

    set password(value:string) {
        this.password = value
    }
}
const user1 = new User();
user1.password    // 可以访问
user1._password   // 报错
user1.password = '2222'

6.3. 抽象类

使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,因为它里面包含一个或多个抽象方法。所谓的抽象方法,是指不包含具体实现的方法:相当于是一个模板,定义一下整体的类型。

abstract class Animal {
    abstract name:string 
    // 也可以有普通属性
    age: number = 33
    constructor(name:string) {}

    abstract say(language:string) :void
}

const animal = new Animal()  // 抽象类不能直接实例化,报错

class Cat extends Animal {
    name: string;
    constructor(name:string) {
        super(name)
        this.name = name;
        this.age
    }
    say(language:string) {
        console.log(language)
    }
}
const cat = new Cat('lili')
console.log(cat.age)

6.4. 使用接口 或 type定义类

可以使用接口定义类的结构,一个类可以实现多个接口

 // 定义一个接口
interface Animal {
    name: string;
    get sound() :string;
    run(): void
}
type B = {
    age: number;
}
type C = Animal | B
 // 类不能实现使用类型别名定义的联合类型
class cat implements C 
// 可以继承多个
class cat implements Animal,B {
    name: string = 'wang';
    age: number = 111;
    get sound() {
        return ''
    }
    run() {
        console.log('a')
    }
}

6.5. 类与泛型结合

class Animal<T> {
    name:T;
    constructor(name:T) {
        this.name = name
    }
    run(value: T):T {
        return value
    }
}

const anin1 = new Animal('lili');

const anin2 = new Animal(222);

7. tsconfig.json

tsconfig.json是 TypeScript 项目的配置文件,它指定了用于编译该项目的根文件及编译器选项。以下是一些重要的配置项和它们的作用:

7.1.1. compilerOptions

这部分包含了一系列用来告诉 TypeScript 编译器如何编译代码的标志。

  • target: 设置编译后的 JavaScript 目标版本,比如"ES5""ES6"等。
  • module: 指定生成的代码所使用的模块系统,如"CommonJS""AMD""System""UMD""ES6""ES2015"等。
  • lib: 指定编译过程中需要包含的库文件的列表,如["dom", "es6"]等。
  • outDir: 指定输出目录,编译后的文件将放在这个目录下。
  • outFile: 将所有文件输出到一个文件中,仅在module"system""amd"时有效。
  • rootDir: 指定输入文件的根目录,用于控制输出目录结构。
  • allowJs: 允许编译.js文件,让 TypeScript 和 JavaScript 代码可以共存。
  • checkJs: 允许在.js文件中报告错误。
  • jsx: 在.tsx文件中支持 JSX,例如:"react"、"preserve"等。
  • declaration: 生成相应的.d.ts文件。
  • sourceMap: 生成相应的.map文件,用于调试。
  • strict: 启用所有严格类型检查选项。
  • noImplicitAny: 不允许具有隐式any类型的表达式和声明。
  • strictNullChecks: 在严格的null检查模式下,nullundefined值不包含在任何类型里,只允许用作它们各自的类型使用。
  • esModuleInterop: 通过为所有导入创建命名空间对象,实现 CommonJS 和 ES 模块之间的互操作性。
7.1.2. filesincludeexclude

这三个配置项控制 TypeScript 编译器应该编译哪些文件:

  • files: 指定一个确切的文件列表,只有这些文件会被编译。
  • include: 指定一个匹配模式列表,编译器会编译匹配上的文件。
  • exclude: 指定一个匹配模式列表以排除某些文件。
7.1.3. 示例tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "jsx": "react",
    "outDir": "./dist",
    "esModuleInterop": true,
    "sourceMap": true,
    "allowJs": true,
    "skipLibCheck": true
},
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

这只是tsconfig.json中常用配置项的概览。根据项目的不同需求,可能会有更多的配置项需要了解和调整。通过适当配置tsconfig.json文件,可以有效控制 TypeScript 项目的编译过程

8. ts编译工具

juejin.cn/post/695430…

  • 如果没有使用 Babel,首选 TypeScript 自带编译器(配合 ts-loader 使用)
  • 如果项目中有 Babel,安装 @babel/preset-typescript,配合 tsc 做类型检查。
  • 两种编译器不要混用。

9. vue3 + ts

<script setup lang="ts">
import { ref, reactive } from 'vue';
import Child from './child';
const props = withDefault(defineProps<
  name: string;
  age: number;                      
>(),{
  name: 'www',
  age: 22  
})

const emits = defineEmits(['dd','s'])

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
  change: [id: number] // 具名元组语法
  update: [value: string]
}>()


let ts_ref1 = ref<string>("字符类型");
let ts_ref3 = ref<number>(1);
let ts_ref2: Ref<number[]> = ref([1, 2]);
ts_ref3.value = 2; // 通过.value访问和修改ref创建的响应式数据

const ts_reactive: { name: string; age?: number } = reactive({ name: "小明", age: 18 });
</script>