TypeScript面试题

简述TypeScript 中的接口 ?

接口是TypeScript中的一种类型,接口通过interface关键字定义,后面跟接口名称和一对大括号 {},大括号
内定义了接口的成员,包括属性、方法等。
特性
    多重继承:TypeScript的接口支持多重继承,即一个接口可以从多个其他接口继承属性和方法。
    索引签名:接口可以定义索引签名,用于约束对象的索引类型和索引返回值的类型。
    扩展接口:接口可以通过extends关键字扩展其他接口,继承其所有成员并可添加或覆盖成员。
    混合类型:接口可以定义多种类型的成员,包括属性、方法、可选成员、只读成员等,支持函数类型、数组类型、元组等复杂类型结构。

用途
    代码约束和验证:确保对象符合预期的结构,增强代码的健壮性和可维护性。
    类型安全:提高编译时的类型检查,确保代码的正确性。。
    设计共享:接口作为一种约定,可以跨多个组件或模块共享,促进团队协作和代码复用

TypeScript 中如何检查 null 和 undefined?

null:表示一个空的或不存在的值。它是一个赋值给变量的特殊关键字。
undefined:表示一个变量已经声明,但尚未赋值,或者一个属性不存在。
使用 typeof 操作符可以判断一个变量的类型。当一个变量为 undefined 时,typeof 操作符的结果为 “undefined”。
联合类型允许一个变量是多种类型中的一种,包括nullundefinedfunction greet(person: string | null | undefined) {
    if (person !== null && person !== undefined) {
        console.log(`Hello, ${person}!`);
    } else {
        console.log("Person is not defined");
    }
}

空值合并操作符 ?? 允许你为变量提供一个默认值,当它为nullundefined时。

let height: number | null = null;
let safeHeight = height ?? 100; // 如果height为null或undefined,则使用100
console.log(safeHeight); // 输出100

TypeScript 什么是三斜线指令?有哪些三斜杠指令?

三斜线指令(Triple-Slash Directives)是以/// 或 /**/(多行注释形式)开头的特殊注释,用于提供元数据
或者对编译器进行特殊指令。这些指令通常位于文件顶部,用于模块定义、类型声明、引用外部类型定义文件等。

reference 或 reference path格式:此指令用于告诉TypeScript编译器当前文件需要引用其他文件或声明文件。
这对于将类型定义分散在多个文件中非常有用,或者引用外部库的类型定义。
示例:
    单行:/// <reference path="path/to/file.d.ts" />
    多行:///<reference path="path/to/file.d.ts"/>
types用途:用于在tsconfig.json之外指定要包含的类型包。这对于临时或局部地包含某些类型声明特别有用。
示例:
    /// <reference types="node" />
amd-dependency:在使用AMD模块加载器(如RequireJS)时,此指令用于指定模块的依赖关系。
示例:
    ///<amd-dependency path="jquery" name="jQuery" />

注意:
三斜线指令主要适用于旧的或特定的场景,随着TypeScript项目配置和模块系统的演进,很多功能(如类型声明的引
用)现在更多推荐使用模块导入(import语句)或通过配置文件(如tsconfig.json)来管理。
对于大多数现代TypeScript项目,使用import语句来导入类型定义或模块,以及通过tsconfig.json文件来配置类型
引用,已经成为更标准的做法。

TypeScript中如何实现函数重载?

函数重载(Function Overloading)允许你为同一个函数提供多个签名,从而实现根据传入参数的不同,使用不同的
函数实现。这有助于提升代码的灵活性和类型安全性。实现函数重载主要包括两步:定义重载签名和实现函数体。

声明函数示例:
    function add(a: number, b: number): number;
    function add(a: string, b: string): string;
实现函数:会根据实际传入的参数类型来匹配对应的重载签名,并执行相应的逻辑。

    function add(a: number | string, b: number | string): number | string {
        if (typeof a === 'number' && typeof b === 'number') {
            return a + b;
        } else if (typeof a === 'string' && typeof b === 'string') {
            return a.concat(b);
        }
        throw new Error('Parameters must be of the same type, either both numbers or both strings.');
    }

    // 使用示例
    console.log(add(1, 2)); // 输出: 3
    console.log(add('Hello, ', 'world!')); // 输出: Hello, world!

函数的实现必须能够处理所有重载签名指定的情况。
函数重载并不是真正意义上的“重载”(即在编译后的JavaScript代码中不会生成多个同名函数),而是通过
一个函数实现来满足多种类型签名的需求。使得函数能够以更自然、更符合直觉的方式处理多种输入类型。

TypeScript类型any、never、void和unknown使用场景区别?

any: 表示可以是任何类型。用于不关心或不想要类型检查的场景。
never: 表示永远不会出现的值的类型。通常用于表示那些总是会抛出异常或永远不会结束的函数的返回类型。
void: 表示没有任何返回值的函数的返回类型。它主要用于函数,通常用作函数没有返回值的情况。
unknown: 类似于 any,表示未知类型,这个类型可以赋值给任何类型,但是不能直接访问属性或方法,除非进行类型检查。
总结:any 提供最大的灵活性但牺牲了类型安全;never 用于标记不可能发生的场景;void 适用于无需返回值的函
数;而 unknown 则在保持一定安全性的同时,提供了处理未知类型的灵活性。

简述TypeScript 中的泛型是什么,如何使用 ?

泛型:允许我们在保证类型安全前提下参数化类型,以便在使用类、函数或接口时才指定具体的类型。

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

let output = identity<string>("hello"); // T被指定为string类型
console.log(output); // 输出: hello

泛型接口:
interface KeyValuePair<K, V> {
    key: K;
    value: V;
}

const pair: KeyValuePair<string, number> = { key: "name", value: 123 };

泛型类:
class GenericClass<T> {
    private value: T;
    constructor(initValue: T) {
        this.value = initValue;
    }
    getValue(): T {
        return this.value;
    }
}

const intObj = new GenericClass<number>(1);
console.log(intObj.getValue()); // 输出: 1

const strObj = new GenericClass<string>("TypeScript");
console.log(strObj.getValue()); // 输出: TypeScript

优点:   
    类型安全:泛型确保了在编译时就能捕获类型错误,避免了运行时的类型问题。
    代码复用:通过参数化类型,一个泛型实体可以应用于多种类型,减少了代码重复。
    清晰的API:泛型函数和类的用户能够明确看到期望的类型,提高了代码的可读性和可维护性.

TypeScript let 和 const 有什么区别?

let:
    变量声明:let允许你声明一个变量,其作用域限制在最近的块级作用域(如if语句、for循环、函数内等),
    这意味着它不会像var那样提升到包含它的函数或全局作用域。
    可变性:用let声明的变量可以在声明后被重新赋值,但其标识符在其作用域内必须是唯一的,不能在同一
    作用域内重复声明。
    用途:当你需要在块级作用域内声明一个可变的变量时使用letconst:
    变量声明与初始化:const用于声明一个常量,它同样具有块级作用域。与let不同,const声明时必须同时进行初始化。
    不可变性:一旦使用const声明了一个变量并赋予初始值,这个变量就不能被重新赋值。这意味着const声明的是
    一个恒定的引用,对于基本类型(如字符串、数字、布尔值等),其值不能改变;而对于复合类型(如数组、对
    象),虽然其引用不能更改,但若它是可变的(如数组或对象可以修改内部属性),则可以通过该引用修改其内部
    结构。
    用途:当你需要定义一个在整个作用域内都不应改变的值时,应使用const。
区别:
    作用域:两者都具有块级作用域,优于var的函数或全局作用域。
    可变性:let声明的变量可以重新赋值,而const声明的变量一旦初始化后就不能改变(注意对象和数组的内部属性
    可变)。
    初始化:const声明时必须初始化,而let可以在声明后再赋值。

简述如何在 TypeScript 中使用 async/await?

使用async关键字来声明一个异步函数。这样的函数会隐式地返回一个Promise对象。
在异步函数内部,可以使用await关键字来等待一个Promise的结果。
调用异步函数时,可以使用.then和.catch来处理结果或错误,或者将其放在另一个async函数中并使用awaitasync function fetchData(): Promise<string> {
    try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        return data.text;
    } catch (error) {
        console.error('Fetch data failed:', error);
        throw error; // 可以选择重新抛出错误或处理它
    }
}
注意:
    只能在async函数内部使用awaitawait的使用使得代码逻辑更清晰,但过度使用或不当嵌套依然可能导致“回调地狱”的另一种形式,
    需要注意代码结构的优化。
    async函数总是返回一个Promise,即使没有显式使用return语句,它也会隐式返回一个resolved的
    Promise(带有undefined的值)。

简述什么是TS中的声明合并?

声明合并是指当 TypeScript 遇到多个同名的声明时,会将它们合并为一个单一的声明。
这使得开发者可以分散地定义同一个实体的不同部分,最终将它们合并为一个整体。

接口合并:TypeScript会自动将这些接口的成员合并成一个接口。这意味着你可以分步定义接口,
或者在不同的文件中添加接口的成员,而不用担心覆盖或重复定义。
    interface People {
        name: string;
    }
    interface People {
        age: number
    }
    //合并后的接口
    interface People {
        name: string,
        age: number
    }

命名空间合并:命名空间的合并规则类似于接口合并,允许你跨多个文件或声明扩展命名空间的内容。

    namespace MyNamespace {
        export const foo = 'foo';
    }

    namespace MyNamespace {
        export const bar = 'bar';
    }

    // 合并后的命名空间
    namespace MyNamespace {
        export const foo = 'foo';
        export const bar = 'bar';
    }


优点:
    模块化和代码组织:允许将相关类型定义分散到多个文件中,便于管理和维护。
    逐步定义:可以在不同的文件或时刻逐步添加或扩展类型定义,保持代码的灵活性和扩展性。
    避免冗余:通过合并,可以避免为同一功能或类型重复定义多个名称,保持代码的简洁性。
    第三方库集成:使得为现有JavaScript库添加类型定义变得更加容易。

请分别TypeScript中?. , ?? , !. , _ , 等符号的含义?

?.(可选链操作符): 许你安全地访问深层嵌套的属性或方法,而不用担心中间某个部分为nullundefined时抛出错误。
const user = null;
console.log(user?.name); // 输出: undefined 而不是抛出错误

??(空值合并操作符): 用于在左侧操作数为nullundefined时,返回右侧的操作数。
let name = null;
const defaultName = "Guest";
console.log(name ?? defaultName); // 输出: Guest

!.(非空断言操作符): 你确定某个值在运行时不会是nullundefinedconst maybeUser = getUser(); // 假设getUser可能返回null
const userName = maybeUser!.name; // 告诉TypeScript我们确信maybeUser不是null或undefined

_(下划线): 通常用作一个占位符变量名,表示某个变量虽然被定义了但实际并不使用其值
// 解构赋值中忽略某个属性
const { ignoredProp, ...rest } = someObject;

// 函数参数中不关心的参数
function logMessage(message: string, _: any) {
  console.log(message);
}

简述能否自动编译.ts文件,并实时修改.ts文件?

TypeScript 编译器 (tsc) 自带了一个监视模式,可以通过 tsc --watch 或简写为 tsc -w 命令启动,
编译器会监视源代码文件的变化,并在检测到 .ts 文件改动时自动重新编译生成对应的 .js 文件。
但是,这并不会自动刷新浏览器或触发应用重启来反映代码更改.

配置 Webpack 或 Rollup: 可以配置它们来监视 .ts 文件的更改并自动重新打包。这些构建工具通常集成有对 
TypeScript 的支持,并且可以与像 webpack-dev-server 或 rollup-plugin-livereload 这样的插件配合使用,
实现实时加载(LiveReload)功能,即在浏览器中自动刷新页面以显示更新。

解释什么是TypeScript定义管理器?

TypeScript 定义管理器,通常指的是 DefinitelyTyped 库中搜索和安装TypeScript定义文件。

DefinitelyTyped 是一个开源项目,它托管了数以千计的 TypeScript 类型定义文件(.d.ts 文件),是 
TypeScript 社区维护的一个中央仓库;编译器能够理解和验证代码中对该库的使用.

@types 是一个命名空间,NPM(Node.js 包管理器)上的类型定义包都以此为前缀。当你在 TypeScript 
项目中使用 NPM 或 Yarn 安装类型定义时,通常会看到这样的包名,例如 @types/lodash。安装这些包后,
TypeScript 编译器就能识别出这些第三方库的类型信息,而无需手动下载或引用类型定义文件。

TypeScript 定义管理器实质上是一种机制或工具集合,确保开发者能够方便地为自己的项目添加并管理
第三方库的类型定义,从而在 TypeScript 开发环境中获得更好的代码补全、类型检查等功能。
powered by GitbookEdit Time: 2024-07-09 16:59:46