Typescript 面试题
Typescript 已经全面普及,尤其大厂大型项目,前端熟悉 Typescript 是标配。
TS 优缺点,使用场景
参考答案
Details
优点
- 静态类型,减少类型错误
- 有错误会在编译时提醒,而非运行时报错 —— 解释“编译时”和“运行时”
- 智能提示,提高开发效率
缺点
- 学习成本高
- 某些场景下,类型定义会过于混乱,可读性不好,如下代码
- 使用不但会变成 anyscript
type ModelFieldResolver<T, TKey extends keyof T = any> = (
this: T,
...params: T[TKey] extends (...args: any) => any ? Parameters<T[TKey]> : never
) => T[TKey]
适用场景
- 大型项目,业务复杂,维护人员多
- 逻辑性比较强的代码,依赖类型更多
- 组内要有一个熟悉 TS 的架构人员,负责代码规范和质量
PS. 虽然 TS 有很多问题,网上也有很多“弃用 TS”的说法,但目前 TS 仍然是最优解,而且各大前端框架都默认使用 TS 。
TS 基础类型有哪些
参考答案
Details
- boolean
- number
- string
- symbol
- bigint
- Enum 枚举
- Array 数组
- Tuple 元祖
- Object 对象
- undefined
- null
- any void never unknown
参考资料
数组 Array 和元组 Tuple 的区别是什么
参考答案
Details
数组元素只能有一种类型,元祖元素可以有多种类型。
// 数组,两种定义方式
const list1: number[] = [1, 2, 3]
const list2: Array<string> = ['a', 'b', 'c']
// 元组
let x: [string, number] = ['x', 10]
枚举 enum 是什么?有什么使用场景?
JS 中没有 enum 枚举,只学过 JS 你可能不知道 enum 。其实在 Java 和 C# 等高级语言中早就有了,TS 中也有。
参考答案
Details
enum 枚举,一般用于表示有限的一些选项,例如使用 enum 定义 4 个方向
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
其他代码中,我们可以获取某一个方向,用于展示或存储。这样代码更具有可读性和维护行。
const d = Direction.Up
参考资料
keyof 和 typeof 有什么区别?
参考答案
Details
typeof
是 JS 基础用法,用于获取类型,这个很简单。
keyof
是 TS 语法,用于获取所有 key 的类型,例如
interface Person {
name: string
age: number
location: string
}
type PersonType = keyof Person
// 等价于 type PersonType = 'name' | 'age' | 'location'
可以把代码拷贝到这里来练习 https://www.tslang.cn/play/index.html
参考资料
any void never unknown 有什么区别
参考答案
Details
主要区别:
any
任意类型(不进行类型检查)void
没有任何类型,和any
相反never
永不存在的值的类型unknown
未知类型(一个更安全的 any)
代码示例
function fn(): void {} // void 一般定义函数返回值
// 返回 never 的函数,必须存在无法达到的终点
function error(message: string): never {
throw new Error(message)
}
function infiniteLoop(): never {
while (true) {}
}
// unknown 比直接使用 any 更安全
const a: any = 'abc'
console.log(a.toUpperCase()) // 不会报错,但不安全
const b: unknown = 'abc'
// console.log( b.toUpperCase() ) // 会报错!!!
console.log((b as string).toUpperCase()) // 使用 as 转换类型,意思是告诉 TS 编译器:“我知道 b 的类型,我对安全负责”
PS:但现在 unknown 用的比 any 少很多,因为麻烦
unknown 和 any 区别
参考答案
Details
unknown
是更安全的 any
,如下代码
const a: any = 'x'
a.toString() // 不报错
const b: unknown = 'y'
// b.toString() // 报错
;(b as string).toString() // 不报错
TS 访问修饰符 public protected private 有什么作用
参考答案
Details
- public 公开的,谁都能用 (默认)
- protected 受保护的,只有自己和子类可以访问
- private 私有的,仅自己可以访问
这些规则很难用语法去具体描述,看代码示例
class Person {
name: string = ''
protected age: number = 0
private girlfriend = '小丽'
// public protected private 也可以修饰方法、getter 等
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
class Employee extends Person {
constructor(name: string, age: number) {
super(name, age)
}
getInfo() {
console.log(this.name)
console.log(this.age)
// console.log(this.girlfriend) // 这里会报错,private 属性不能在子类中访问
}
}
const zhangsan = new Employee('张三', 20)
console.log(zhangsan.name)
// console.log(zhangsan.age) // 这里会报错,protected 属性不能在子类对象中访问,只能在子类中访问
追问:#
和 private
有什么区别呢?
Details
#
在 TS 中可定义私有属性
class Person {
#salary: number
constructor(
private name: string,
salary: number
) {
this.#salary = salary
}
}
const p = new Person('xxx', 5000)
// const n = p.name // 报错
const n = (p as any).name // 可以通过“投机取巧”获取到
console.log('name', n)
// const s = p.#salary // 报错
// const s = (p as any).#salary // 报错
区别:
#
属性,不能在参数中定义private
属性,可通过as any
强制获取#
属性,更私密
type 和 interface 共同和区别,如何选择
type 和 interface 有很多相同之处,很多人因此而产生“选择困难症”,这也是 TS 热议的话题。
共同点
Details
- 都能描述一个对象结构
- 都能被 class 实现
- 都能被扩展
// 接口
interface User {
name: string
age: number
getName: () => string
}
// 自定义类型
type UserType = {
name: string
age: number
getName: () => string
}
// class UserClass implements User {
class UserClass implements UserType {
name = 'x'
age = 20
getName() {
return this.name
}
}
区别
Details
- type 可以声明基础类型
- type 有联合类型和交差类型
- type 可以被
typeof
赋值
// type 基础类型
type name = string
type list = Array<string>
// type 联合类型
type info = string | number
type T1 = { name: string }
type T2 = { age: number }
// interface T2 { age: number } // 联合,还可以是 interface ,乱吧...
type T3 = T1 | T2
const a: T3 = { name: 'x' }
type T4 = T1 & T2
const b: T4 = { age: 20, name: 'x' }
// typeof 获取
type T5 = typeof b
//【补充】还有个 keyof ,它和 typeof 完全不同,它是获取 key 类型的
type K1 = keyof T5
const k: K1 = 'name'
如何选择?
Details
根据社区的使用习惯,推荐使用方式
- 能用 interface 就尽量用 interface
- 除非必须用 type 的时候才用 type
参考资料
PS. 其实你混淆 type 和 interface 不是你的问题,这是 TS 设计的问题,或者说 TS 设计初衷和后来演变带来的副作用。
什么是泛型,如何使用它?
只学过 JS 的同学不知道泛型,其实它早就是 C# 和 Java 中的重要概念了。初学泛型可能会比较迷惑,需要多些代码多练习。
泛型的定义
Details
泛型 Generics 即通用类型,可以灵活的定义类型而无需写死。
const list: Array<string> = ['a', 'b']
const numbers: Array<number> = [10, 20]
interface User {
name: string
age: number
}
const userList: Array<User> = [{ name: 'x', age: 20 }]
泛型的使用
Details
- 用于函数
// Type 一般可简写为 T
function fn<Type>(arg: Type): Type {
return arg
}
const x1 = fn<string>('xxx')
// 可以有多个泛型,名称自己定义
function fn<T, K>(a: T, b: K) {
console.log(a, b)
}
fn<string, number>('x', 10)
- 用于 class
class SomeClass<T> {
name: T
constructor(name: T) {
this.name = name
}
getName(): T {
return this.name
}
}
const s1 = new SomeClass<String>('xx')
- 用于 type
function fn<T>(arg: T): T {
return arg
}
const myFn: <U>(arg: U) => U = fn // U T 随便定义
- 用于 interface
// interface F1 {
// <T>(arg: T): T;
// }
interface F1<T> {
(arg: T): T
}
function fn<T>(arg: T): T {
return arg
}
const myFn: F1<number> = fn
参考资料
什么是交叉类型和联合类型
交叉类型 T1 & T2
交叉类型是将多个类型合并为一个类型,包含了所需的所有类型的特性。例如 T1 & T2 & T3
代码示例
Details
interface U1 {
name: string
city: string
}
interface U2 {
name: string
age: number
}
type UserType1 = U1 & U2
const userA: UserType1 = { name: 'x', age: 20, city: 'beijing' }
// 可在 userA 获取所有属性,相当于“并集”
userA.name
userA.age
userA.city
注意事项
Details
- 两个类型的相同属性,如果类型不同(冲突了),则该属性是
never
类型
// 如上代码
// U1 name:string ,U2 name: number
// 则 UserType1 name 是 never
- 基础类型没办法交叉,会返回
never
type T = string & number // never
参考资料
联合类型 T1 | T2
一种“或”的关系。格式如 T1 | T2 | T3
。代码示例如下
Details
interface U1 {
name: string
city: string
}
interface U2 {
name: string
age: number
}
function fn(): U1 | U2 {
return {
name: 'x',
age: 20,
}
}
注意事项
Details
基础类型可以联合
type T = string | number
const a: T = 'x'
const b: T = 100
但如果未赋值的情况下,联合类型无法使用 string 或 number 的方法
function fn(x: string | number) {
console.log(x.length) // 报错
}
参考资料
是否用过工具类型
TS 工具类型有 Partial
Required
Omit
ReadOnly
等,熟练使用 TS 的人都会熟悉这些工具类型。
参考答案
Details
Partial<T>
属性设置为可选
interface User {
name: string
age: number
}
type User1 = Partial<User> // 属性全部可选,类似 `?`
const u: User1 = {}
Require<T>
属性设置为必选 (和 Partial 相反)
Pick<T, K>
挑选部分属性
interface User {
name: string
age: number
city: string
}
type User1 = Pick<User, 'name' | 'age'> // 只选择两个属性
const u: User1 = { name: 'x', age: 20 }
Omit<T, K>
剔除部分属性(和 Pick 相反)
ReadOnly<T>
属性设置为只读
相当于为每个属性都设置一遍 readonly
interface User {
name: string
age: number
}
type User1 = Readonly<User>
const u: User1 = { name: 'x', age: 20 }
// u.name = 'y' // 报错
TS 这些符号 ?
?.
??
!
_
&
|
#
分别什么意思
参考答案
Details
?
可选属性,可选参数
interface User {
name: string
age?: number
}
const u: User = { name: 'xx' } // age 可写 可不写
function fn(a: number, b?: number) {
console.log(a, b)
}
fn(10) // 第二个参数可不传
?.
可选链:有则获取,没有则返回 undefined ,但不报错。
const user: any = {
info: {
city: '北京',
},
}
// const c = user && user.info && user.info.city
const c = user?.info?.city
console.log(c)
??
空值合并运算符:当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
const user: any = {
// name: '张三'
index: 0,
}
// const n1 = user.name ?? '暂无姓名'
const n2 = user.name || '暂无姓名' // 某些情况可用 || 代替
console.log('name', n2)
const i1 = user.index ?? '暂无 index'
const i2 = user.index || '暂无 index' // 当是 0 (或 false 空字符串等)时,就不能直接用 || 代替
console.log('index', i1)
!
非空断言操作符:忽略 undefined null ,自己把控风险
function fn(a?: string) {
return a!.length // 加 ! 表示忽略 undefined 情况
}
_
数字分隔符:分割数字,增加可读性
const million = 1_000_000
const phone = 173_1777_7777
// 编译出 js 就是普通数字
其他的本文都有讲解
&
交叉类型_
联合类型#
私有属性
什么是抽象类 abstract class
抽象类是 C# 和 Java 的常见语法,TS 也有,但日常前端开发使用并不多。
参考答案
Details
抽象类,不能直接被实例化,必须派生一个子类才能使用。
abstract class Animal {
abstract makeSound(): void
move(): void {
console.log('roaming the earch...')
}
}
// const a = new Animal() // 直接实例化,报错
class Dog extends Animal {
// 必须要实现 Animal 中的抽象方法,否则报错
makeSound() {
console.log('wang wang')
}
}
const d = new Dog()
d.makeSound()
d.move()
参考资料
Details
- https://www.tslang.cn/docs/handbook/classes.html 其中搜索
abstract class
如何扩展 window 属性,如何定义第三方模块的类型
参考答案
Details
declare interface Window {
test: string
}
window.test = 'aa'
console.log(window.test)
是否有过真实的 Typescript 开发经验,讲一下你的使用体验
开放性问题,需要结合你实际开发经验来总结。可以从以下几个方面考虑
Details
- 在 Vue React 或其他框架使用时遇到的障碍?
- 在打包构建时,有没有遇到 TS 语法问题而打包失败?
- 有没有用很多
any
?如何避免any
泛滥?
参考资料