面试官:
typescript中如何将一个类型扩展其他字段
答:
在 TypeScript 中,可以使用交叉类型(Intersection Types)来扩展其他类型的字段。交叉类型可以将多个类型合并为一个类型,其中包含了这些类型的所有属性和方法。
下面是一个示例,假设我们有一个名为 Person
的接口,它有 name
和 age
两个字段:
interface Person {
name: string;
age: number;
}
现在我们想要创建一个新的类型 Employee
,它扩展了 Person
并添加了一个 jobTitle
字段。我们可以使用交叉类型来实现:
type Employee = Person & {
jobTitle: string;
}
这样,Employee
就包含了 Person
中的所有字段以及一个额外的 jobTitle
字段。我们可以像这样使用 Employee
类型:
const employee: Employee = {
name: 'Alice',
age: 30,
jobTitle: 'Software Engineer'
};
注意,使用交叉类型扩展其他类型的字段时,如果新添加的字段与原有类型中的字段重名,那么新的类型中的字段将覆盖原有类型中的字段。
面试官:
typescript中有哪些高频的高级用法
答:
TypeScript 作为一种静态类型语言,在编写大型项目时提供了很多有用的高级功能。以下是一些 TypeScript 中常见的高级用法:
- 泛型(Generics):使用泛型可以编写可重用的代码组件,能够适用于不同类型的数据。常见的使用场景包括数组、Promise 和函数等。示例:
function identity<T>(arg: T): T {
return arg;
}
- 高级类型:TypeScript 提供了很多高级类型,包括联合类型、交叉类型、类型别名、类型保护等。这些类型可以帮助我们更好地组织和描述代码的结构。示例:
type User = {
name: string;
age: number;
};
type Admin = {
name: string;
role: string;
};
type UserOrAdmin = User | Admin;
- 接口继承:在 TypeScript 中,接口可以继承其他接口,从而创建出更具体的接口。这使得我们可以创建出可重用的接口,同时还可以避免代码冗余。示例:
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
jobTitle: string;
}
- 类型推断:TypeScript 可以根据上下文自动推断出变量的类型,从而减少类型注释的冗余。示例:
const myName = "Alice"; // TypeScript 自动推断 myName 的类型为 string
- 枚举类型:在 TypeScript 中,可以使用枚举类型来表示一组有限的取值。这可以使代码更加清晰和可读。示例:
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;
这些是 TypeScript 中常见的高级用法,当然还有很多其他有用的功能和特性,比如装饰器、类型检查等,这些都可以使得 TypeScript 在编写大型项目时更加方便和高效。
面试官:
详细说一下联合类型、交叉类型、类型别名、类型保护等
答:
- 联合类型(Union Types):联合类型允许一个变量具有多种不同类型的值,用竖线
|
连接多种类型即可定义联合类型。例如:
let myVariable: string | number;
myVariable = "hello";
myVariable = 42;
上述代码中,myVariable
可以是 string
类型或 number
类型,这样可以增加变量的灵活性,但是使用联合类型的变量在类型检查时需要注意其可能的值类型。
- 交叉类型(Intersection Types):交叉类型允许一个变量具有多个类型的属性和方法,用
&
连接多个类型即可定义交叉类型。例如:
interface Printable {
print(): void;
}
interface Loggable {
log(): void;
}
type LoggerPrinter = Printable & Loggable;
function logAndPrint(obj: LoggerPrinter) {
obj.log();
obj.print();
}
上述代码中,LoggerPrinter
是 Printable
和 Loggable
两个接口的交叉类型,表示它既有 print()
方法也有 log()
方法。在 logAndPrint()
函数中,接受一个 LoggerPrinter
类型的参数,可以确保传入的参数具有 print()
和 log()
方法。
- 类型别名(Type Aliases):类型别名允许为一个类型起一个新名字,可以使代码更加简洁易懂。使用
type
关键字即可定义类型别名,例如:
type MyString = string;
function processString(str: MyString) {
// ...
}
上述代码中,MyString
是 string
类型的别名,可以在函数参数中使用,使代码更加易读。
- 类型保护(Type Guards):类型保护允许在运行时检查变量的类型,以确保代码的正确性。可以使用
typeof
、instanceof
和自定义类型谓词等方式进行类型保护。例如:
function isString(str: any): str is string {
return typeof str === "string";
}
function processValue(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
上述代码中,isString()
是一个自定义的类型谓词函数,用于判断变量是否是 string
类型。在 processValue()
函数中,可以使用 isString()
函数进行类型保护,确保变量具有正确的类型。
面试官:
TypeScript 中的 this 和 JavaScript 中的 this 有什么差异?
答:
在 TypeScript 中,this 的行为与 JavaScript 中的 this 有些许差异。
在 JavaScript 中,this 的值是在运行时确定的,它指向调用当前函数的对象。如果当前函数是作为对象的方法调用的,那么 this 指向该对象;如果当前函数是作为普通函数调用的,那么 this 指向全局对象(在浏览器环境中是 window,在 Node.js 环境中是 global)。
在 TypeScript 中,this 的类型是静态确定的。在类中,this 指向当前类的实例。在箭头函数中,this 指向定义函数时所在的上下文,而不是调用时的上下文。这个特性对于编写高效的回调函数很有用,可以避免 this 指向错误的问题。
下面是一个 TypeScript 类的例子,展示了 this 在 TypeScript 中的行为:
class MyClass {
private value: number;
constructor(value: number) {
this.value = value;
}
public add(other: number): this {
this.value += other;
return this;
}
public multiply(other: number): this {
this.value *= other;
return this;
}
public getValue(): number {
return this.value;
}
}
const obj = new MyClass(10);
const result = obj.add(5).multiply(2).getValue(); // result = 30
在上述代码中,add()
和 multiply()
方法都返回 this
,这样可以实现链式调用。在这个例子中,this 始终指向当前类的实例,而不会像 JavaScript 中那样指向全局对象或其他对象。
面试官:
TypeScript 中如何联合枚举类型的 Key?
答:
在 TypeScript 中,可以使用联合类型来联合枚举类型的键。下面是一个示例:
enum Fruit {
Apple = "apple",
Banana = "banana",
Orange = "orange",
}
enum Vegetable {
Carrot = "carrot",
Onion = "onion",
Potato = "potato",
}
type Food = Fruit | Vegetable;
function getFoodName(food: Food): string {
if (food in Fruit) {
return Fruit[food];
} else {
return Vegetable[food];
}
}
console.log(getFoodName(Fruit.Apple)); // "apple"
console.log(getFoodName(Vegetable.Carrot)); // "carrot"
在上述代码中,我们使用了联合类型 Food
来包含了 Fruit
和 Vegetable
两个枚举类型。在 getFoodName()
函数中,我们使用了 in
操作符来判断变量 food
是否属于枚举类型 Fruit
,如果属于,就返回对应的枚举值;否则返回 Vegetable
枚举类型的值。这里的 Fruit[food]
和 Vegetable[food]
表示使用枚举的键 food
获取对应的值,这种方式可以避免硬编码,增加代码的可读性和可维护性。
面试官:
enum str {
A,
B,
C
}
type strUnion = keyof typeof str;
答:
这段代码的作用是定义了一个名为 str
的枚举类型,其中包含了三个枚举值 A
、B
、C
。接着,定义了一个名为 strUnion
的类型别名,它的值是 keyof typeof str
。这个类型别名的作用是将枚举类型 str
的键组成的联合类型作为 strUnion
的类型。也就是说,strUnion
的取值只能是 A
、B
、C
三个字符串中的一个。
在 TypeScript 中,keyof
操作符可以用于获取某个类型的所有键组成的联合类型。在这个例子中,typeof str
表示获取 str
枚举类型的类型(也就是 typeof
操作符的作用),然后使用 keyof
操作符获取其所有键组成的联合类型。因此,keyof typeof str
的结果是 "A" | "B" | "C"
。
需要注意的是,枚举类型的值是数字或字符串常量,而不是字符串字面量。如果要将枚举类型的值作为字符串字面量使用,可以使用字符串枚举。例如:
enum Str {
A = "a",
B = "b",
C = "c",
}
type StrUnion = keyof typeof Str; // "A" | "B" | "C"
在上述代码中,Str
是一个字符串枚举类型,其枚举值都是字符串常量。因此,keyof typeof Str
的结果是 "A" | "B" | "C"
。
面试官:
TypeScript 中 ?.、??、!、!.、_、** 等符号的含义?
答:
在 TypeScript 中,有以下符号的含义:
-
?.
:可选链操作符,用于在对象属性访问时避免出现空指针异常。例如:obj?.prop
,如果obj
为null
或undefined
,则返回undefined
,否则返回obj.prop
。 -
??
:空值合并操作符,用于提供默认值。例如:let x = a ?? b
,如果a
为null
或undefined
,则x
的值为b
,否则x
的值为a
。 -
!
:非空断言操作符,用于告诉 TypeScript 编译器一个变量一定不会是null
或undefined
,可以强制使用。例如:let x: string = str!
,表示str
一定不为null
或undefined
,可以强制转换为string
类型。 -
!.
:强制属性访问操作符,用于告诉 TypeScript 编译器一个属性一定存在,可以强制访问。例如:let x = obj.prop!.value
,表示obj.prop
一定存在,可以强制访问其value
属性。 -
_
:通配符,用于忽略某个变量或参数的值。例如:function foo(_: number, b: number) {}
,表示忽略第一个参数的值。 -
**
:幂运算符,用于计算一个数的指数幂。例如:let x = 2 ** 3
,表示计算2
的3
次方,x
的值为8
。
需要注意的是,?.
和 ??
是 TypeScript 3.7 引入的新特性,需要使用 TypeScript 3.7 及以上的版本才能使用。而其他符号在较早的 TypeScript 版本中已经支持。
面试官:
ts如何在代码中判断某一个变量具体属于联合类型中的哪个类型?
答:
在 TypeScript 中,可以使用类型保护来判断某个变量具体属于联合类型中的哪个类型。类型保护是一种特殊的语法,用于在代码中判断一个变量是否属于某个特定类型,从而可以在代码中使用该变量的特定属性或方法。
下面介绍几种常见的类型保护方式:
typeof
类型保护
当判断一个变量的类型是否是基本类型时,可以使用 typeof
类型保护。例如:
function foo(x: string | number) {
if (typeof x === 'string') {
console.log(x.toUpperCase());
} else {
console.log(x.toFixed(2));
}
}
在上述代码中,当变量 x
的类型是 string
时,可以调用 toUpperCase()
方法;当类型是 number
时,可以调用 toFixed()
方法。
instanceof
类型保护
当判断一个变量的类型是否是一个类的实例时,可以使用 instanceof
类型保护。例如:
class Animal {}
class Dog extends Animal {}
function foo(x: Animal) {
if (x instanceof Dog) {
console.log('This is a dog');
} else {
console.log('This is not a dog');
}
}
在上述代码中,当变量 x
的类型是 Dog
类型的实例时,可以输出 'This is a dog'
;否则输出 'This is not a dog'
。
- 自定义类型保护
当判断一个变量的类型是否符合自定义条件时,可以使用自定义类型保护。自定义类型保护是一种函数,它的返回值是一个布尔值,表示变量是否符合特定条件。例如:
interface A {
a: number;
}
interface B {
b: string;
}
function isA(x: A | B): x is A {
return (x as A).a !== undefined;
}
function foo(x: A | B) {
if (isA(x)) {
console.log(x.a);
} else {
console.log(x.b);
}
}
在上述代码中,自定义类型保护函数 isA
判断变量 x
是否是 A
类型。在 foo
函数中,当 x
是 A
类型时,可以输出 x.a
属性的值;否则可以输出 x.b
属性的值。
需要注意的是,使用类型保护可以避免在代码中使用类型断言(as
操作符),从而避免一些潜在的类型错误。
面试官:
ts如何写一个重载方法
答:
在 TypeScript 中,可以使用函数重载来定义多个函数签名,从而实现对不同参数类型的重载处理。函数重载需要先定义多个函数签名,再定义函数实现。函数实现需要根据参数类型的不同,选择正确的函数签名进行处理。
下面是一个例子,定义一个重载函数,实现两个数字相加或两个字符串拼接的功能:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
} else if (typeof a === 'string' && typeof b === 'string') {
return a + b;
} else {
throw new Error('Invalid arguments');
}
}
console.log(add(1, 2)); // 3
console.log(add('Hello', 'World')); // HelloWorld
console.log(add(1, 'Hello')); // Error: Invalid arguments
在上述代码中,先定义了两个函数签名 add(a: number, b: number): number
和 add(a: string, b: string): string
,分别对应两个数字相加和两个字符串拼接的情况。然后定义函数实现 add(a: any, b: any): any
,在实现中根据参数类型的不同,选择正确的函数签名进行处理。
需要注意的是,在 TypeScript 中,函数签名必须按照顺序排列,且最后一个函数签名必须是实现签名,否则会编译出错。此外,函数签名只是定义了函数的输入和输出类型,不包括函数实现,函数实现需要在最后一个函数签名中进行定义。
面试官:
参数个数不同可以重载吗
答:
在 TypeScript 中,可以对函数参数的类型和数量进行重载,但不能仅仅通过参数数量的不同来进行重载。
因为 TypeScript 中函数的参数个数是可选的,而且可以使用剩余参数语法,比如:
function foo(a: string, b?: number, ...c: boolean[]) {
// ...
}
上述函数中,第二个参数 b
是可选的,而剩余参数 c
可以接受任意数量的布尔值参数。
如果仅仅通过参数数量的不同来进行重载,就会导致重载定义冲突或歧义,无法达到预期的效果。
因此,函数重载应该基于参数类型的不同,而不是参数数量的不同,这样可以保证函数的调用符合预期的重载定义。
文章评论