《TypeScript 面试八股:高频考点与核心知识点详解》
“你好啊!能把那天没唱的歌再唱给我听吗? ”
前言
因为主包还是主要学习js,ts浅浅的学习了一下,在简历中我也只会写了解,所以我写一些比较基础的八股,如果是想要更深入的八股的话还是建议找别人的。
Ts基础
为社么要使用ts?ts相对于js的优势在哪?
TypeScript是JS的超集,包括所有的JS规范版本。同时拥有强大的类型系统,包括泛型。是一个面向对象的语言,提供类,接口和模块。
TS对JS的改进主要是静态类型检查(强类型语言)“静态类型更有利于构建大型应用”。 同时TS多了接口,泛型这些JS所没有的特性,内置的数据类型也比较多。
TypeScript的内置数据类型有哪些
在Typescript中,内置的数据类型也称为原始数据类型。
- boolean(布尔类型)
- number(数字类型)
- string(字符串类型)
- void 类型
- null 和 undefined 类型
- array(数组类型)
- tuple(元组类型):允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
- enum(枚举类型):
enum
类型是对JavaScript标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字 - any(任意类型)
- never 类型
- object 对象类型
TypeScript 中 const 和 readonly 的区别?
const可以防止变量的值被修改,在运行时检查,使用const变量保存的数组,可以使用push,pop等方法。
readonly可以防止变量的属性被修改,在编译时检查,使用Readonly Array声明的数组不能使用push,pop等方法
TypeScript 中 any 类型的作用是什么?
为编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库(不确定用户输入值的类型,第三方代码库是如何工作的)。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。使用any就失去了ts带来的类型检查。
any的问题
- 类型污染:any
类型的对象会导致后续的属性类型都会变成
any - 使用不存在的属性或方法而不报错
TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?
any
: 动态的变量类型(失去了类型检查的作用)。never
: 永不存在的值的类型。例如:never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。unknown
: 任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。null & undefined
: 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。当你指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自。void
: 没有任何类型。例如:一个函数如果没有返回值,那么返回值可以定义为void。
TS中any和unknown有什么区别?
unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。
any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查,反而 unknown 因为未知性质,不允许访问属性,不允许赋值给其他有明确类型的变量。
我在之前的博客中提到了:unkonw因为我们不知道他里面是什么,所以我们不能对他进行任何操作。
解释一下TypeScript中的枚举
枚举就是一个对象的所有可能取值的集合(和数学中的思想一样);
enum Day {SUNDAY, MONDAY,TUESDAY, WEDNESDAY,THURSDAY,FRIDAY,SATURDAY}
TypeScript 中如何联合枚举类型的 Key?————用下标
enum Status {xiaoming, xiaohong, xiaogang,}
console.log(Status.xiaoming,Status[0]);
console.log(Status.xiaohong,Status[1]);
console.log(Status.xiaogang,Status[2]);
//输出: 0 xiaoming
// 1 xiaohong
// 2 xiaogang
keyof 和 typeof 关键字的作用?
keyof 关键字
keyof
用于获取对象类型的键的联合类型。它可以让你从一个对象类型中提取出所有键的名称,并将它们组合成一个联合类型。
interface Person {
name: string;
age: number;
address: string;
}
type PersonKeys = keyof Person; // "name" | "age" | "address"
function getProperty(obj: Person, key: PersonKeys) {
return obj[key];
}
const person: Person = {
name: "Alice",
age: 30,
address: "123 Main St"
};
console.log(getProperty(person, "name")); // 输出: Alice
console.log(getProperty(person, "age")); // 输出: 30
typeof
关键字
typeof
用于获取变量或表达式的类型。它可以让你在类型上下文中引用一个变量的类型。
const person = {
name: "Alice",
age: 30,
address: "123 Main St"
};
type PersonType = typeof person;
// PersonType 的类型为:
// {
// name: string;
// age: number;
// address: string;
// }
function printPerson(p: PersonType) {
console.log(p.name, p.age, p.address);
}
printPerson(person); // 输出: Alice 30 123 Main St
TS中的泛型是什么
泛型允许我们在编写代码时使用一些以后才指定的类型,在定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。
泛型的主要作用是:
- 提高代码的复用性:可以编写适用于多种类型的通用代码。
- 增强类型安全性:通过类型参数,可以在编译时捕获类型错误。
- 减少冗余代码:避免为每种类型编写单独的函数或类。
泛型函数
泛型函数可以接受任意类型的参数,并返回相应类型的值。
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("Hello"); // 类型为 string
let output2 = identity<number>(42); // 类型为 number
泛型接口
泛型接口允许你定义可以适用于多种类型的接口
interface KeyValuePair<K, V> {
key: K;
value: V;
}
// 使用泛型接口
let pair1: KeyValuePair<string, number> = { key: "age", value: 30 };
let pair2: KeyValuePair<number, boolean> = { key: 1, value: true };
泛型类
泛型类允许你创建可以处理多种类型的类。
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
// 使用泛型类
let box1 = new Box<string>("Hello"); // 类型为 Box<string>
let box2 = new Box<number>(42); // 类型为 Box<number>
在这个例子中,Box
类可以存储任意类型的值,并通过 getValue
方法返回该值。
any和泛型的区别?
泛型有类型推断,编译器会根据传入的参数自动地帮助我们确定T的类型
any则是不检验
接口和Type
TypeScript 中同名的 interface 或者同名的 interface 和 class 可以合并吗?
同名的interface会自动合并,同名的interface和class会自动聚合。
接口和类型别名的区别?
两者都可以用来描述对象或函数的类型。与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。
TypeScript 中 type 和 interface 的区别?
相同点:
- 都可以描述 '对象' 或者 '函数'
- 都允许拓展(extends):interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。
不同点:
特性 | interface | type |
---|---|---|
扩展方式 | 通过 extends 扩展 | 通过 & (交叉类型)扩展 |
声明合并 | 支持 | 不支持 |
适用场景 | 对象类型、类的接口 | 任何类型(基本类型、联合类型、元组等) |
性能 | 在大型项目中稍好 | 处理复杂类型时可能稍慢 |
兼容性 | 支持 implements | 不能直接用于 implements |
灵活性 | 较低,只能定义对象或函数类型 | 较高,可以定义任何类型 |
类
TS中什么是方法重载?
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。 基本上,它在派生类或子类中重新定义了基类方法。
方法覆盖规则:
- 该方法必须与父类中的名称相同。
- 它必须具有与父类相同的参数。
- 必须存在IS-A关系或继承。
TS中的类及其特性
在 TypeScript 中,类(Class) 是面向对象编程(OOP)的核心概念之一。TypeScript 的类不仅支持 ES6 的类特性,还增加了一些额外的功能,如访问修饰符、抽象类、只读属性等。
1. 基本语法
TypeScript 中定义类的语法与 ES6 类似,使用 class
关键字。
class Person {
// 属性
name: string;
age: number;
// 构造函数
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 方法
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
// 创建类的实例
const person = new Person("Alice", 30);
person.greet(); // 输出: Hello, my name is Alice and I'm 30 years old.
2. 访问修饰符
TypeScript 提供了三种访问修饰符,用于控制类成员的访问权限:
- **
public
**(默认):成员可以在任何地方访问。 - **
private
**:成员只能在类内部访问。 - **
protected
**:成员可以在类内部和子类中访问。
class Person {
public name: string; // 默认是 public
private age: number; // 只能在类内部访问
protected gender: string; // 可以在子类中访问
constructor(name: string, age: number, gender: string) {
this.name = name;
this.age = age;
this.gender = gender;
}
public greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
class Employee extends Person {
constructor(name: string, age: number, gender: string) {
super(name, age, gender);
console.log(this.gender); // 可以访问 protected 成员
// console.log(this.age); // 错误: age 是 private 成员
}
}
const person = new Person("Alice", 30, "female");
console.log(person.name); // 可以访问 public 成员
// console.log(person.age); // 错误: age 是 private 成员
// console.log(person.gender); // 错误: gender 是 protected 成员
3. 只读属性
使用 readonly
关键字可以将属性标记为只读,只能在构造函数中初始化,之后不可修改。
class Person {
readonly name: string;
constructor(name: string) {
this.name = name;
}
changeName(newName: string) {
// this.name = newName; // 错误: name 是只读属性
}
}
const person = new Person("Alice");
// person.name = "Bob"; // 错误: name 是只读属性
4. 静态成员
静态成员属于类本身,而不是类的实例。使用 static
关键字定义静态成员。
class MathUtils {
static PI = 3.14;
static calculateArea(radius: number) {
return this.PI * radius * radius;
}
}
console.log(MathUtils.PI); // 输出: 3.14
console.log(MathUtils.calculateArea(5)); // 输出: 78.5
5. 抽象类
抽象类不能被实例化,只能被继承。它可以包含抽象方法(没有实现的方法)和具体方法。
abstract class Animal {
abstract makeSound(): void; // 抽象方法,子类必须实现
move() {
console.log("Moving...");
}
}
class Dog extends Animal {
makeSound() {
console.log("Woof!");
}
}
const dog = new Dog();
dog.makeSound(); // 输出: Woof!
dog.move(); // 输出: Moving...
// const animal = new Animal(); // 错误: 不能实例化抽象类
6. getter 和 setter
可以使用 get
和 set
关键字定义属性的访问器。
class Person {
private _age: number;
constructor(age: number) {
this._age = age;
}
get age() {
return this._age;
}
set age(value: number) {
if (value >= 0) {
this._age = value;
} else {
throw new Error("Age cannot be negative.");
}
}
}
const person = new Person(30);
console.log(person.age); // 输出: 30
person.age = 25;
console.log(person.age); // 输出: 25
// person.age = -5; // 抛出错误: Age cannot be negative.
7. 类的继承
使用 extends
关键字实现类的继承,子类可以继承父类的属性和方法。
class Animal {
constructor(public name: string) {}
move(distance: number) {
console.log(`${this.name} moved ${distance} meters.`);
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
const dog = new Dog("Buddy");
dog.move(10); // 输出: Buddy moved 10 meters.
dog.bark(); // 输出: Woof! Woof!
8. 方法重写
子类可以重写父类的方法。
class Animal {
move() {
console.log("Moving...");
}
}
class Dog extends Animal {
move() {
console.log("Running...");
}
}
const dog = new Dog();
dog.move(); // 输出: Running...
9. 构造函数重载
TypeScript 支持构造函数重载,可以根据不同的参数创建对象。
class Point {
constructor(public x: number, public y: number);
constructor(x: number, y: number, public z: number);
constructor(public x: number, public y: number, public z?: number) {
// 实现逻辑
}
}
const point1 = new Point(1, 2);
const point2 = new Point(1, 2, 3);
10. 类与接口
类可以实现接口,接口可以定义类的结构。
interface Animal {
name: string;
makeSound(): void;
}
class Dog implements Animal {
constructor(public name: string) {}
makeSound() {
console.log("Woof!");
}
}
const dog = new Dog("Buddy");
dog.makeSound(); // 输出: Woof!
TS原理
简单介绍一下 TypeScript 模块的加载机制?
假设有一个导入语句 import { a } from "moduleA"
;
- 首先,编译器会尝试定位需要导入的模块文件,通过绝对或者相对的路径查找方式;
- 如果上面的解析失败了,没有查找到对应的模块,编译器会尝试定位一个
外部模块声明
(.d.ts); - 最后,如果编译器还是不能解析这个模块,则会抛出一个错误
error TS2307: Cannot find module 'moduleA'.
对 TypeScript 类型兼容性的理解?
TypeScript 类型兼容性指的是 TypeScript 中的类型系统能够自动进行类型检查和类型推导,以确保类型的兼容性。具体来说,如果两个类型 A 和 B 满足一定的关系,那么 A 类型的变量或参数可以赋值给 B 类型的变量或参数。
TypeScript 的类型兼容性规则基于结构子类型化的原则,即只要目标类型(被赋值的类型)的成员属性包含来源类型(待赋值的类型)的成员属性,或者来源类型可以转换为目标类型,就认为这两个类型是兼容的。
例如,下面代码中的 a
变量和 b
变量都是 Animal
类型,但是它们的属性不完全相同:
interface Animal {
name: string;
age: number;
}
class Cat implements Animal {
name = 'Tom';
age = 2;
type = 'mammal';
}
let a: Animal = { name: 'Kitty', age: 1 };
let b: Animal = new Cat();
尽管 Cat
类型比 Animal
类型具有更多的属性,但是由于其中的属性包含了 Animal
的所有属性,因此 Cat
是兼容于 Animal
的。因此,let b: Animal = new Cat()
是合法的。
在 TypeScript 中,类型兼容性是非常重要的,因为它允许我们编写更加灵活和可复用的代码,同时保证类型的安全性。在使用类型兼容性时,需要注意一些细节,如类型保护等,以确保程序的正确性。
TypeScript 中的 this 和 JavaScript 中的 this 有什么差异?
- TypeScript:noImplicitThis: true 的情况下,必须去声明 this 的类型,才能在函数或者对象中使用this。
- Typescript 中箭头函数的 this 和 ES6 中箭头函数中的 this 是一致的。
tsconfig.json有什么作用?
tsconfig.json文件是JSON格式的文件。
在tsconfig.json文件中,可以指定不同的选项来告诉编译器如何编译当前项目。