最后
为了帮助大家更好的了解前端,特别整理了《前端工程师面试手册》电子稿文件。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
fly()
layEggs()
}
interface Fish {
swim()
layEggs()
}
function getSmallPet(): Fish | Bird {
// …
}
let pet = getSmallPet()
pet.layEggs() // okay
pet.swim() // error
这里的联合类型可能有点复杂:如果一个值的类型是 `A | B`,我们能够确定的是它包含了 `A` 和 `B` 中共有的成员。这个例子里,`Fish` 具有一个 `swim` 方法,我们不能确定一个 `Bird | Fish` 类型的变量是否有 `swim`方法。 如果变量在运行时是 `Bird` 类型,那么调用 `pet.swim()` 就出错了。
### 类型保护
联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解是否为 `Fish` 或者是 `Bird` 时怎么办? JavaScript 里常用来区分这 2 个可能值的方法是检查成员是否存在。如之前提及的,我们只能访问联合类型中共同拥有的成员。
let pet = getSmallPet()
// 每一个成员访问都会报错
if (pet.swim) {
pet.swim()
} else if (pet.fly) {
pet.fly()
}
为了让这段代码工作,我们要使用**类型断言**:
let pet = getSmallPet()
if ((pet as Fish).swim) {
(pet as Fish).swim()
} else {
(pet as Bird).fly()
}
#### 用户自定义的类型保护
这里可以注意到我们不得不多次使用类型断言。如果我们一旦检查过类型,就能在之后的每个分支里清楚地知道 `pet` 的类型的话就好了。
TypeScript 里的**类型保护**机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个**类型谓词**:
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined
}
在这个例子里,`pet is Fish` 就是类型谓词。谓词为 `parameterName is Type` 这种形式, `parameterName` 必须是来自于当前函数签名里的一个参数名。
每当使用一些变量调用 `isFish` 时,`TypeScript` 会将变量缩减为那个具体的类型。
if (isFish(pet)) {
pet.swim()
}
else {
pet.fly()
}
注意 `TypeScript` 不仅知道在 `if` 分支里 `pet` 是 `Fish` 类型;它还清楚在 `else` 分支里,一定不是 Fish类型而是 `Bird` 类型。
#### typeof 类型保护
现在我们回过头来看看怎么使用联合类型书写 `padLeft` 代码。我们可以像下面这样利用类型断言来写:
function isNumber (x: any):x is string {
return typeof x === ‘number’
}
function isString (x: any): x is string {
return typeof x === ‘string’
}
function padLeft (value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(’ ') + value
}
if (isString(padding)) {
return padding + value
}
throw new Error(Expected string or number, got '${padding}'.
)
}
然而,你必须要定义一个函数来判断类型是否是原始类型,但这并不必要。其实我们不必将 `typeof x === 'number'`抽象成一个函数,因为 TypeScript 可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。
function padLeft (value: string, padding: string | number) {
if (typeof padding === ‘number’) {
return Array(padding + 1).join(’ ') + value
}
if (typeof padding === ‘string’) {
return padding + value
}
throw new Error(Expected string or number, got '${padding}'.
)
}
这些 `typeof` 类型保护只有两种形式能被识别:`typeof v === "typename"` 和 `typeof v !== "typename"`, `"typename"`必须是 `"number"`, `"string"`,`"boolean"` 或 `"symbol"`的基本类型。 但是 TypeScript 并不会阻止你与其它字符串比较,只是 TypeScript 不会把那些表达式识别为类型保护。
#### instanceof 类型保护
如果你已经阅读了 `typeof` 类型保护并且对 JavaScript 里的 `instanceof` 操作符熟悉的话,你可能已经猜到了这节要讲的内容。
`instanceof` 类型保护是通过构造函数来细化类型的一种方式。我们把之前的例子做一个小小的改造:
class Bird {
fly () {
console.log(‘bird fly’)
}
layEggs () {
console.log(‘bird lay eggs’)
}
}
class Fish {
swim () {
console.log(‘fish swim’)
}
layEggs () {
console.log(‘fish lay eggs’)
}
}
function getRandomPet () {
return Math.random() > 0.5 ? new Bird() : new Fish()
}
let pet = getRandomPet()
// 如果是类,可以直接通过instanceof来获取类的构造函数名
if (pet instanceof Bird) {
pet.fly()
}
if (pet instanceof Fish) {
pet.swim()
}
### 可以为 null 的类型
TypeScript 具有两种特殊的类型,`null` 和 `undefined`,它们分别具有值 `null` 和 `undefined`。我们在[基础类型](/chapter2/type)一节里已经做过简要说明。 默认情况下,类型检查器认为 `null` 与 `undefined` 可以赋值给任何类型。 `null` 与 `undefined` 是所有其它类型的一个有效值。 这也意味着,你阻止不了将它们赋值给其它类型,就算是你想要阻止这种情况也不行。`null`的发明者,Tony Hoare,称它为[价值亿万美金的错误](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)。
`--strictNullChecks` 标记可以解决此错误:当你声明一个变量时,它不会自动地包含 `null` 或 `undefined`。 你可以使用联合类型明确的包含它们:
let s = ‘foo’
s = null // 错误, ‘null’不能赋值给’string’
let sn: string | null = ‘bar’
sn = null // 可以
sn = undefined // error, ‘undefined’不能赋值给’string | null’
注意,按照 JavaScript 的语义,TypeScript 会把 `null` 和 `undefined` 区别对待。`string | null`,`string | undefined` 和 `string | undefined | null` 是不同的类型。
#### 可选参数和可选属性
使用了 `--strictNullChecks`,可选参数会被自动地加上 `| undefined`:
function f(x: number, y?: number) {
return x + (y || 0)
}
f(1, 2)
f(1)
f(1, undefined)
f(1, null) // error, ‘null’ 不能赋值给 ‘number | undefined’
可选属性也会有同样的处理:
class C {
a: number
b?: number
}
let c = new C()
c.a = 12
c.a = undefined // error, ‘undefined’ 不能赋值给 ‘number’
c.b = 13
c.b = undefined // ok
c.b = null // error, ‘null’ 不能赋值给 ‘number | undefined’
#### 类型保护和类型断言
由于可以为 `null` 的类型能和其它类型定义为联合类型,那么你需要使用类型保护来去除 `null`。幸运地是这与在 `JavaScript` 里写的代码一致:
function f(sn: string | null): string {
if (sn === null) {
return ‘default’
} else {
return sn
}
}
这里很明显地去除了 `null`,你也可以使用短路运算符:
function f(sn: string | null): string {
return sn || ‘default’
}
如果编译器不能够去除 `null` 或 `undefined`,你可以使用类型断言手动去除。语法是添加 `!` 后缀: `identifier!` 从 `identifier` 的类型里去除了 `null` 和 `undefined`:
### 最后
总的来说,面试官要是考察思路就会从你实际做过的项目入手,考察你实际编码能力,就会让你在电脑敲代码,看你用什么编辑器、插件、编码习惯等。所以我们在回答面试官问题时,有一个清晰的逻辑思路,清楚知道自己在和面试官说项目说技术时的话就好了
![](https://img-blog.csdnimg.cn/img_convert/a3969270d98a6e67f2837e6475ccb8b7.webp?x-oss-process=image/format,png)
![](https://img-blog.csdnimg.cn/img_convert/e51021ebf34746aad70ff7a8a5e13dd8.webp?x-oss-process=image/format,png)
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**
路就会从你实际做过的项目入手,考察你实际编码能力,就会让你在电脑敲代码,看你用什么编辑器、插件、编码习惯等。所以我们在回答面试官问题时,有一个清晰的逻辑思路,清楚知道自己在和面试官说项目说技术时的话就好了
[外链图片转存中...(img-sNLy9yE0-1715796163692)]
[外链图片转存中...(img-zT6xtbaz-1715796163692)]
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**