avatar
【面试精选】TypeScript 常见8道面试题
🪅

【面试精选】TypeScript 常见8道面试题

keywords
TypeScript, JavaScript, 面试, 静态类型检查, 代码编辑体验, 可维护性, 泛型, 装饰器, interface, type
published_date
最新编辑 2024年10月14日
Created time
2024年08月30日
slug
本文详细介绍了TypeScript的优势、与JavaScript的主要区别、基本数据类型、泛型、unknown与any的区别、readonly修饰符与const关键字的区别、装饰器的使用及其类型,以及interface与type的区别。TypeScript通过静态类型检查、增强的代码编辑体验和更好的工具支持,提高了代码的可维护性和开发效率。
tags
JavaScript
面试
TypeScript(TS)和JavaScript(JS)是两种不同的编程语言。TypeScript 是 JavaScript 的超集,提供了类型系统和更多高级功能。本文将详细介绍为何要使用 TypeScript 以及 TypeScript 和 JavaScript 之间的主要区别。

1.为什么要使用 TypeScript?

1. 静态类型检查
ts的核心优势之一是静态类型检查,类型检查可以在编译阶段发现许多常见的错误,而不是在运行时,有助于早期发现和修复问题,提升代码的可靠性和稳定性
2.增强代码编辑体验
类型信息和智能提示可以极大地提高开发效率,ts的类型系统是编辑器可以提供更准确的代码自动完成、重构、跳转和文档提示等功能
3.代码的可维护性
通过使用类型声明,开发者可以更容易理解代码的意图和结构,尤其是在团队开发中,这种文档化的效果更为明显。
4.高级语言特性
TypeScript 支持一些 JavaScript 还不支持的高级语言特性,如接口(Interfaces)、枚举(Enums)、泛型(Generics)等,这些特性有助于编写更为结构化和可扩展的代码。
5.更好的工具支持
TS 提供了多种编译选项,可以优化生成的 js 代码,提高运行时的性能。并且,ts 代码可以通过编译器转换为兼容的 js 代码,支持各种 js 环境。

2.TS和JS的区别

TypeScript
JavaScript
类型系统
静态类型语言,支持在编译时检查类型错误。可以为变量、函数参数和返回值指定类型,从而捕获潜在的错误。
动态类型语言,变量的类型是在运行时决定的,可能导致类型相关的运行时错误
类型注解
支持类型注解,可以明确指定变量、函数参数、返回值等的类型,例如 let name: string = "Alice";。
不支持类型注解,所有的类型信息都在运行时动态决定。
编译过程
需要经过编译器编译成 JavaScript 代码才能运行。TypeScript 编译器会检查类型错误并生成等效的 JavaScript 代码。
可以直接在浏览器或 Node.js 中运行,无需编译过程。
面向对象编程
TypeScript 提供了更丰富的面向对象编程特性,如类(Class)、接口(Interface)、抽象类(Abstract Class)、访问修饰符(Access Modifiers)等,支持更复杂的面向对象编程模型。
JavaScript 的面向对象编程特性较为基础,主要使用原型继承。
高级语言特性
包含了 ES6 和 ES7 的所有功能,并添加了如泛型、枚举、装饰器等额外的语言特性。
支持 ES6 及之后的标准,但某些高级语言特性在语言本身中不包含。

3.TypeScript 的基本数据类型

  • Boolean:布尔类型,true 或 false。
  • Number:数值类型,支持整数和浮点数。
  • String:字符串类型,使用单引号或双引号表示。
  • Array:数组类型,使用 type[] 或 Array<type> 表示。
  • Tuple:元组类型,表示固定数量和类型的数组。
  • Enum:枚举类型,用于定义一组命名的常量。
  • Any:任意类型,表示可以是任意类型的值。
  • Void:无返回值类型,通常用于函数返回值。
  • Null 和 Undefined:表示值的空和未定义状态。
  • Never:表示不可能的类型,通常用于不返回值的函数(如抛出异常或无限循环的函数)
  • Object:表示非原始类型的对象。

4.TypeScript 中的泛型是什么?如何使用?

范型是一种让函数、类或接口在保持类型安全的前提下能够处理多种类型的特性
以下是泛型的示例:
function identity<T>(arg: T): T { return arg; } const numberIdentity = identity<number>(42); const stringIdentity = identity<string>("Hello");
在这个例子中,identity 函数使用了泛型 <T>,表示它可以接受任何类型的参数并返回相同类型的结果。

5.TypeScript 中的 unknown 和 any 区别

  • any 类型:表示任意类型,使用 any 类型的变量可以被赋予任何类型的值,不会进行类型检查。
  • unknown 类型:表示未知类型,使用 unknown 类型的变量时需要进行类型检查或类型断言,才能对其进行操作。unknown 类型提供了更安全的方式来处理不确定的类型。
let anyVar: any; let unknownVar: unknown; anyVar = 5; anyVar.toUpperCase(); // 不会报错,但在运行时会出错 unknownVar = "Hello"; if (typeof unknownVar === "string") { unknownVar.toUpperCase(); // 类型检查后不会出错 }

6.readonly 修饰符和 const 关键字区别

  • readonly 修饰符:用于对象属性,表示属性只能在初始化时赋值,之后无法更改。
  • const 关键字:用于声明变量,表示变量绑定的引用不能更改,但对象的属性仍然可以被修改。
const obj = { name: "John" }; obj.name = "Doe"; // 正确,`obj` 的引用没有改变 interface User { readonly id: number; name: string; } const user: User = { id: 1, name: "John" }; user.name = "Doe"; // 正确 user.id = 2; // 错误,`id` 是只读属性

7.装饰器(Decorators)

装饰器(Decorators)是 TypeScript 提供的一种特殊语法,用于在类、方法、属性或参数上附加元数据或修改行为。装饰器在编译时执行,是一种面向切面编程(AOP,Aspect-Oriented Programming)的实现方式,可以在不修改原始代码的情况下扩展功能。
装饰器的常见用途包括日志记录、权限检查、缓存、依赖注入等

类型

  • 类装饰器(Class Decorator)
应用于类的构造函数。它可以用来修改类的定义或为类添加额外的元数据。
function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } // @sealed 装饰器会将 Greeter 类及其原型对象设为不可扩展的(即不能添加新属性)。 @sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return `Hello, ${this.greeting}`; } }
  • 方法装饰器(Method Decorator)
应用于类的方法。它可以用来修改方法的行为或为方法添加额外的元数据。
function logMethod(target: any, propertyName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Method ${propertyName} called with args: ${JSON.stringify(args)}`); return originalMethod.apply(this, args); }; } // @logMethod 装饰器用来拦截 add 方法的调用,并在调用时记录日志。 class Calculator { @logMethod add(a: number, b: number): number { return a + b; } } const calculator = new Calculator(); calculator.add(2, 3); // 控制台输出:Method add called with args: [2,3]
  • 访问器装饰器(Accessor Decorator)
应用于类的 get 或 set 存取器,它可以用来修改存取器的行为。
// @configurable(false) 装饰器用于设置 x 和 y 访问器的 configurable 属性为 false。 function configurable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.configurable = value; }; } class Point { private _x: number; private _y: number; constructor(x: number, y: number) { this._x = x; this._y = y; } @configurable(false) get x() { return this._x; } @configurable(false) get y() { return this._y; } }
  • 属性装饰器(Property Decorator)
用于类的属性。它不能直接修改属性的初始化值,但可以修改属性的元数据。
// @logProperty 装饰器会为 name 属性添加 getter 和 setter,以便在访问或修改属性时输出日志 function logProperty(target: any, key: string) { let value = target[key]; const getter = () => { console.log(`Getting value for ${key}: ${value}`); return value; }; const setter = (newVal) => { console.log(`Setting value for ${key} to ${newVal}`); value = newVal; }; Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true, }); } class Example { @logProperty public name: string; constructor(name: string) { this.name = name; } } const example = new Example("John Doe"); console.log(example.name); // Getting value for name: John Doe example.name = "Jane Doe"; // Setting value for name to Jane Doe
  • 参数装饰器(Parameter Decorator)
用于类的构造函数或方法的参数。它可以用来修改参数的元数据,但不能修改参数的实际值。
// @logParameter 装饰器会记录 greet 方法的参数的元数据。 function logParameter(target: any, propertyName: string, index: number) { console.log(`Parameter decorator called for ${propertyName} at position ${index}`); } class ExampleClass { greet(@logParameter message: string): void { console.log(message); } }

语法

装饰器的语法是在类或类的成员前面使用 @ 符号,然后跟上装饰器的名称。
@decoratorName class MyClass { // ... } class MyClass { @decoratorName myMethod() { // ... } }

如何使用

要使用装饰器,必须在 tsconfig.json 文件中启用 experimentalDecorators 选项:
{ "compilerOptions": { "experimentalDecorators": true } }

执行顺序

装饰器的执行顺序是从下到上,从右到左:
  • 多个装饰器应用于一个声明时,它们的执行顺序是从下到上(从最靠近声明的装饰器开始)
  • 在方法装饰器和参数装饰器的组合中,先执行参数装饰器,再执行方法装饰器。

使用场景

  • 日志记录:可以在方法调用时自动记录日志信息。
  • 权限检查:在执行方法前检查用户权限。
  • 缓存:可以在某些计算密集型方法上应用缓存。
  • 依赖注入:通过装饰器自动注入依赖项。

8.interface和type的区别

interface 和 type 是 TypeScript 中用于定义对象类型的两种主要方法。它们在大多数情况下是可以互换使用的,但在某些特定场景中,它们有不同的用法和限制。
interface
type
基础用法
用于定义对象的结构,包括属性和方法。
用来定义原始类型、对象类型、联合类型、交叉类型等
扩展
支持声明合并(Declaration Merging)。如果定义了两个同名的接口,它们会被自动合并。
不支持
联合类型和交叉类型
不支持
支持
原始类型别名
不支持
支持
类型推断和声明方式
通常用于定义对象类型,并且支持继承,可以继承多个接口,适用于描述对象的形状和行为
用于定义对象类型、联合类型、交叉类型等,并且可以通过交叉类型(&)来组合多个类型。
映射类型
不支持
可以使用映射类型来动态创建类型。
类实现
支持
不支持
总结
更适合用来定义对象类型、类的接口和函数签名,具有更好的扩展性(如声明合并)
更灵活,适合定义联合类型、交叉类型、原始类型别名、映射类型等。
// 扩展 interface User { name: string; } interface User { age: number; } const user: User = { name: "John", age: 30, }; type User = { name: string; }; // 会报错,不能重新定义同名类型 type User = { age: number; }; // 定义联合类型和交叉类型 type NameOrAge = string | number; // 联合类型 type Person = { name: string } & { age: number }; // 交叉类型 //定义原始类型别名 type StringAlias = string; type NumberAlias = number; //映射类型 type ReadOnly<T> = { readonly [P in keyof T]: T[P]; }; type User = { name: string; age: number; }; type ReadOnlyUser = ReadOnly<User>; //类实现 interface User { name: string; age: number; } class Person implements User { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } }
 

关于我

My desire to practice my skills and share my acquired knowledge fuels my endeavors.

联系 : znjessie858@gmail.com

订阅

订阅我的文章,收到我的最新发布通知,可随时取消订阅!