TypeScript面试题总结

550 阅读9分钟

基础知识

TS优缺点

TS优点

  • 跨平台
  • ES6特性
  • 静态类型检查
  • 面向对象的语言,如类、接口、继承、泛型等

缺点

  • 需要长时间编译代码
  • 不支持抽象类

严格模式

通过在tsconfig.json中设置“strict”:true,开启TS的严格模式,会让编译器对类型检查更严格,减少运行时错误。

TS的内置数据类型

  • number:数字类型,TS中的所有数字都存储为浮点值
  • string:字符串类型
  • boolean:布尔类型
  • null:显式赋值,但没有值
  • undefined:已声明但未赋值的变量
  • void:不返回任何类型值的函数的返回类型

TS的联合类型

联合类型:表示取值可以为多种类型中的一种

let a:string | number
a = 'test'
a = 1

TS的接口---interface

接口:定义了行为和动作规范。

对象类型接口,下次定义同类型参数时就可以复用类型

interface Person{
   name:string;
   age?:number;  // 可选属性
   readonly x:number;  // 只读属性,只有初始化的时候可以修改其值  
   [key:string]:any;//可以无限扩展属性
   fn():void;// 也可以定义方法
}
const student = ({name}:Person):string =>{}

⚠️可选属性如果没有赋值,则获取到的值是undefined,对于可选方法,必须先进行判断,再调用,否则会报错。

const p:Person ={

if(p.fn){
  p.fn()
 }
}

函数类型接口。通过interface限制函数的参数类型和返回值类型

interface ComFunc{
   (source:string,subString:string):boolean
}
let fun:ComFunc;
fun = function(source:string,subString:string){
  
}

接口可以合并

interface One {
  color:string;
  name:string;
}
interface One{
  age:number
}
const Two:One = {
  color:'red';
  name:'test';
  age:12;
}

接口可以继承,用extends,多个合并,用,隔开

interface One {
  color:string;
  name:string;
}
interface OneByOne extends One{
  age:number
}
const Two:OneByOne = {
  color:'red';
  name:'test';
  age:12;
}

TS 中的type(类型别名)

TS的interfacetype的不同

相同点

  • 都可以描述一个对象或者函数
  • 都可以扩展

不同点

  • type可以修饰任何类型(值类型和引用数据类型),interface只能修饰引用类型(对象,数组,函数)
  • type语句中可以使用typeof获取实例的类型进行赋值
  • type可以使用in关键字生成映射类型,interface不行
  • interface具有合并能力(同名的接口会自动合并一个接口),type不具有合并能力

TS的类

//最基本的语法
class className {
   属性
   构造函数
   方法
}

修饰符

class Person {
   public name:string; //类中、子类内的任何地方、外部都能调用
   protected age:number; // 类中、子类内的任何地方都能调用,但外部不能调用
   private tel:number; // 类中可以调用,子类内的任何地方、外部都不能调用
}

abstract

用abstract关键字声明的类叫做抽象类,声明的方法叫抽象方法。

  • 抽象类:指不能被实例化,因为它立main包含一个或多个抽象方法。
  • 抽象方法:指不包含具体实现的方法。 ⚠️:抽象类是不能直接实例化,只能实例化实现所有抽象方法的子类。
abstract class Person {
  constructor(public name:string){}
  // 抽象方法
  abstract setAge(age:number):void;
}

class Child extends Person {
  constructor(name:string){
    super(name)
  }
  setAge(age:number):void{
    console.log(`名字是${this.name},年龄是${age}`)
  }
}
let res = new Person('test')  // error
let res1 = new Child('tom')
res1.setAge(12) // 名字是tom,年龄是12

重写、重载

  • 重写:子类重写继承自父类中的方法
  • 重载:指为同一个函数提供多个类型定义,与上述函数的重载类似
// 重写
class Person {
  setName(name:string){
    erturn `我的名字叫${name}`
  }
}

class Child extends Person {
  setName(name:string){
    return `你的名字叫${name}`
  }
}

const yourName = new Child()
console.log(yourName.setName('tom')) // "你的名字叫tom"
// 重载
class Person1{ 
  setNameAge(name: string):void; 
  setNameAge(name: number):void; 
  setNameAge(name:string | number){ 
      if(typeof name === 'string'){ 
          console.log(`我的名字是${name}`) 
      }else{ 
         console.log(`我的年龄是${name}`) 
      } 
   }; 
  } 
  const res = new Person1() 
  res.setNameAge('小杜杜') // "我的名字是小杜杜" 
  res.setNameAge(7) // "我的年龄是7"

TS迭代器

背景

从一个数据集中获取一个数据项,对这个数据集进行迭代

(用于处理集合数据【数组、对象】的工具)可以通过 for…of 循环、扩展运算符、解构赋值等方式来使用迭代器。

定义

迭代器是一个对象,它定义了一个序列,提供一种方法访问这个序列的元素,迭代器对象实现了Iterator接口,返回一个next()方法

使用场景

使用方法

迭代器是一种特殊对象,迭代器对象上有个next方法, 每次调用next()方法都会返回一个对象{value:'', done:false}, 其中value属性是每次迭代的值, done属性是一个布尔值, 判断当前是否迭代完毕, 迭代完成后,返回true

生成器

TS的命名空间

命名空间:解决重名问题,定义了标识符的可见范围

// TS中命名空间使用namespace定义,语法格式如下

namespace Test{
   export interface Person{
      name:string = 'one'
   }
   export class Student{}
}
console.log(Test.Person.name)  // one

可以通过<reference path="xxx.ts"/>导入已存在的命名空间

// one.ts
namespace Food {
  export interface Fruits {
    name:string;
  }
}

// two.ts
<reference path="one.ts"/>
let fruits:Food.Fruits

TS的模块

import、export

TS的命名空间与模块的区别

  • 命名空间:内部模块,主要是防止命名冲突
  • 模块是TS外部模块的简称,侧重于代码的复用
  • 一个模块里可能会有多个命名空间

TS的断言

TS断言分为三种:类型断言、非空断言、确定赋值断言

类型断言

在特定的环境中,具体是什么类型,不需要TS去判断。 共有两种方式:

  • 「尖括号」
  • 「as」:推荐使用
// 尖括号
let num:any = 'tom'
let res1:number = (<string>num).length; // react会报错
// as语法
let str:any = 'tom'
let res:number = (str as string).length

非空断言

在上下文中当类型检查器无法断定类型时,出现一个新的后缀表达式操作符!可以用于断言操作对象是「非null和非undefined类型」

function test(a:number | undefined){
    const b:number = a; //error:undefined is not assignable to number
    const c:number = a!;//ok 
}

确定赋值断言

允许在实例属性和变量声明后面放置一个!号,告诉TS该属性会被明确赋值。

let num:number;
let num1!:number;

const setNumber = () => num = 12
const setNumber1 = () => num1 = 12

setNumber()
setNumber1()

console.log(num) // error
console.log(num1) // ok

TS的类型守卫

类型守卫是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 常见的四种类型守卫:「in关键字」,「typeof关键字」,「instanceof」,「类型谓词(is)」

in 关键字:用于判断这个属性是哪个里面的

interface Info{
  name:string;
  age:number;
}
interface Info1{
  name:string ;
  flage:true
}
const setInfo = (data:Info | Info1)=>{
  if("age" in data){
    console.log(`名字是${data.name},年龄是${data.age}`)
  }
  if("flage" in data){
    console.log(`名字是${data.name},性别是${data.flage}`)
  }
}
setInfo({name:'tom',age:12}) //名字是tom,年龄是12
setInfo({name:'test',flage:true}) //名字是test,性别是true

typeof关键字。用于判断基本类型

const setInfo=(data:number | string |undefined)=>{
  if(typeof data === 'string'){
    console.log(`名字是${data}`)
  }
  if(typeof data === 'number'){
    console.log(`年龄是${data}`)
  }
}
setInfo('tom')//名字是tom
setInfo(12)//年龄是12

instanceof关键字 用于判断一个实例是不是构造函数,或使用类的时候

class Name{
   name:string= 'tom'
}
class Age extends Name {
  age:number = 12
}
const setInfo = (data:Name)=>{
 if(data instanceof Age){
   console.log(`我的年龄是${data.age}`)
 }else {
   console.log(`我的名字是${data.name}`)
 }
}
setInfo(new Name()) // 我的名字是tom
setInfo(new Age()) //我的年龄是12

类型谓词 is

function isNumber(x:any):x is number {
  return typeof x === 'number'
}
console.log(isNumber(12))// true
console.log(isNumber('12')) // false

TS内置的常用工具类型

typeof 操作符用来获取一个变量声明或对象的类型

keyof 用于获取某种类型的所有键,返回类型是联合类型

interface Person {
  name:string;
  age:number;
}
type K1 = keyof Person; // "name" | "age"

in 遍历枚举类型

infer 条件类型语句中,可以用infer声明一个类型变量并且对它进行使用

extends 关键字添加泛型约束

Omit:Omit<T,U>从类型T中剔除U中的所有属性(Omit<Type,keys>)

泛型

泛型:Generics,是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用时再指定类型的一种特性。泛型会保留参数类型。

泛型语法

// 基础泛型语法
function identity<T>(value:T):T {
   return value
}

// 多类型传参
const calcArray = <T,U>(name:T,age:U):{name:T,age:U}=>{
  const res:{name:T,age:U} = {name,age}
  return res
}
const res = calcArray<string,number>('tom',12)
console.log(res) // {name:'tom',age:12}

// 泛型接口
interface A<T>{
  data:T
}
const Info:A<string> = {data:'1'}
console.log(Info.data) // 1

// 泛型类
class clacArray<T>{
  private arr:T[] = []
  
  add(value:T){
    this.arr.push(value)
  }
  getValue():T{
    let res = this.arr[0]
    return res
  }
  
}
// 泛型类型别名
type Info<T> = {
  name?:T
  age?:T
}
const res:Info<string> = {name:'tom'}
// 泛型默认参数
const calcArray = <T = string>(data:T):T[] =>{
  let list:T[] = []
}

泛型常用字母

  • 「T」:代表Type,定义泛型时通常用作第一个类型变量名称
  • 「K」:代表Key,表示对象中的键类型
  • 「V」:代表Value,表示对象中的值类型
  • 「E」:代表Element,表示元素类型
const calcArray = <T>(data:T):T[]=>{
 let list:T[] = []
 for(let i = 0; i<3;i++){
   list.push(data)
 }
 return list
}
const res:string[] = calcArray<string>('d') // ok
const res1:number[] = calcArray<number>(12) // ok
type Props = {
   name:string,
   age:number
 }
const res2:Props[] = calcArray<props>({name:'tom',age:12}) // ok

将多个ts文件合并为一个js文件

//将三个文件file1.ts file2.ts file3.ts合并到common.js文件中
tsc --outFile common.js file1.ts file2.ts file3.ts

declare,declare global是什么?

  • declare是用来定义全局变量、全局函数、全局命名空间
  • declare global为全局对象window增加新的属性

协变、逆变、双变、抗变

  • 协变: X = Y,Y类型可以赋值给X类型的情况。X类型兼容Y类型

编译上下文

tsconfig.json的作用

  • 用于标识TS项目的根路径
  • 用于配制TS编译器
  • 用于指定编译的文件

tsconfig.json重要字段

  • files:设置要编译的文件名称
  • include:设置需要进行编译的文件,支持路径模式匹配
  • exclude:设置无需进行编译的文件,支持路径模式匹配
  • compilerOptions:设置与编译流程相关的选项
    • baseUrl:'./' //用于解析非相对模块名称的基目录
    • outFile:'./' //将输出文件合并为一个文件
    • outDir:'./' //指定输入目录

装饰器

装饰器是一种特殊类型的声明,它能过被附加到类声明,方法,属性或者参数上,可以修改类的行为

通俗的来说就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性,方法,参数的功能

装饰器的分类: 类装饰器、属性装饰器、方法装饰器、参数装饰器