【2023】前端高频八股文——JavaScript+TypeScript篇
JavaScript相关
Promise常见API
Promise.all()
会在任何一个输入的Promise被拒绝时立即拒绝 ,并带有第一个被拒绝的原因可以想象为Array.prototype.every()
function myPromiseAll(array) {
return new Promise((resolve, reject) => {
if (array && typeof array[Symbol.iterator] === 'function') {
let arrayLength = array.length
let resultArray = []
array.forEach((value, index) => {
Promise.resolve(value).then((res) => {
resultArray[index] = res
if (resultArray.length === arrayLength) {
resolve(resultArray)
}
}, (err) => {
reject(err)
})
})
}
})
}
Promise.allSettled()
会等待所有的Promise完成,不管是否拒绝
function myPromiseAllSettled(array) {
return new Promise((resolve, reject) => {
if (array && typeof array[Symbol.iterator] === 'function') {
let arrayLength = array.length
let resultArray = []
array.forEach((value, index) => {
Promise.resolve(value).then((res) => {
resultArray[index] = res
if (resultArray.length === arrayLength) {
resolve(resultArray)
}
}, (err) => {
resultArray[index] = err
if (resultArray.length === arrayLength) {
resolve(resultArray)
}
})
})
}
})
}
Promise.any()
返回第一个兑现的值,当所有都被拒绝时,会以一个包含拒绝原因数组的 AggregateError 拒绝
function myPromiseAny(array) {
return new Promise((resolve, reject) => {
let arrayLength = array.length
let errList = []
if (array && typeof array[Symbol.iterator] === 'function') {
array.forEach((value, index) => {
Promise.resolve(value).then((res) => {
resolve(res)
}, (err) => {
errList[index] = new Error(err)
if (errList.length === arrayLength) {
reject(new AggregateError(errList))
}
})
})
}
})
}
Promise.race()
接受一个iterable
,返回一个随着第一个promise敲定的promise,当传入的iterable
为空时,返回的promise会一直保持在待定状态Promise.resolve()
将给定的值转换为一个Promise。如果值本身就是一个Promise,那么该Promise将被返回;如果该值是一个thenable 对象,Promise.resolve()
将调用其then()
方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现。
原型链相关问题
垃圾回收
分代收集
垃圾回收将堆结构分成了新生代和旧生代
新生代
新生代主要用于存放存活时间较短的对象。新生代内存是由两个空间组成,在新生代的垃圾回收过程中主要采用了Scavenge
算法。这是一种典型的牺牲空间换时间的算法。
在
Scavenge
算法的具体实现中,主要采用了Cheney
算法,它将新生代内存一分为二,每一个部分的空间称为semispace
,也就是我们在上图中看见的new_space中划分的两个区域,其中处于激活状态的区域我们称为From
空间,未激活(inactive new space)的区域我们称为
To
空间。这两个空间中,始终只有一个处于使用状态,另一个处于闲置状态。我们的程序中声明的对象首先会被分配到From
空间,当进行垃圾回收时,如果
From
空间中尚有存活对象,则会被复制到To
空间进行保存,非存活的对象会被自动回收。当复制完成后,From
空间和To
空间完成一次角色互换,To
空间会变为新的From
空间,原来的From
空间则变为To
空间。
当一个对象在经过多次复制之后依旧存活,那么它会被认为是一个生命周期较长的对象,在下一次进行垃圾回收时,该对象会被直接转移到老生代中,这种对象从新生代转移到老生代代过程为 晋升
- 对象是否经历过一次Scavenge算法
- To空间的内存占比是否已经超过25%
旧生代
可达性
JavaScript中主要的内存管理概念是可达性,简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。
- 固有可达值的基本集合
- 当前执行的函数,他的局部变量和参数
- 当前嵌套调用链上的其他函数,他们的局部变量和参数(例如闭包)
- 全局变量
这些值被称为根
- 如果一个值可以通过引用链从根访问任何其他值,则认为该值是可达的
- 全局变量中有一个对象,并且该对象有一个属性引用了另一个对象,则该对象被认为是可达的,而且他引用的内容也是可达的
// user具有对这个对象的引用
let user = {
name: 'John'
}
如果user
的值被重写了,那么这个引用就没了
现在John变成不可达的了,因为没有引用,也无法访问到他,因此垃圾回收器会将其标记为垃圾数据并进行回收,然后释放内存
循环引用
function marry(man, woman) {
woman.husban = man
man.wife = woman
return {
father: man,
mother: woman
}
}
let family = marry({
name: 'John'
}, {
name: 'Ann'
})
倘若我们现在移除外部引用
family = null
则内存状态会变为
因为可达性的存在,虽然John和Ann依然在互相引用,但是没有任何外部对其的引用,因此这些循环引用也可以被垃圾回收程序成功标记清除
内部算法
这套算法被称为mark-and-swap
,其包含以下步骤
- 垃圾收集器找到所有的根,并标记他们
- 然后垃圾收集器遍历并标记来自根的所有引用
- 如此循环便利,直到所有从根部可达的引用都被访问到
- 所有没有被标记的对象都会被删除
WeakMap和WeakSet(弱映射和弱集合)
通常,当对象,数组之类的数据结构在内存中时,**他的子元素,如对象的属性,数组的元素都被认为是可达的。 **
let john = {name: 'John'}
let array = [john]
john = null
// 即使用null覆盖了john 的引用,因为array的存在,john也不会被垃圾回收程序回收
WeakMap
和WeakSet
在这方面有根本的不同,它不会阻止垃圾回收机制对作为键的对象的回收
let john = {
name: 'john'
}
let weakMap = new WeakMap()
weakMap.set(john, '...')
john = null
// john被从内存中删除了
因为WeakMap对键是弱引用,因此当键原有的引用不存在时,将会被垃圾回收工具回收。在上述代码中,删除了john的引用后,该对象只有weakMap
对其的弱引用,垃圾回收工具会忽略这个弱引用,当检查无其他引用时,会将其删除
可以将若引用看作,weakMap本身只是保留对对象的索引,但在垃圾回收工具中并不会当作对该对象的 引用
需要注意的是:WeakMap
不支持迭代以及key()
,values()
,entries()
方法,因此没有办法获取WeakMap
的所有键或值
TypeScript相关
never,unknown,any,void区别
any:JavaScript转TypeScript的银弹
当我们将某个变量定义为any后,TypeScript将会跳过对这个变量的类型检查
let something: any = 'Hello World!'
something.notExistMethod() // ok!
something.notExistProperty.name() // ok!
something = false //ok
使用场景:
- 代码迁移:在JS向TS迁移的过程中,可以采用any来快速的推进重构,但这只是一种**临时方案 **,千万不能写成AnyScript
- 类型确缺失或者补全困难,一般发生在使用了第三方JS编写库的时候,因为没有很好的TS适配,导致我们无法准确定义某个类型,这是可以使用any去暂时规避这类问题
unknown:any的安全替代品
any会跳过所有的TS类型检查,这会为后续代码的维护埋下巨大的安全隐患,为了解决any的问题,TS在3.0版本引入了unknown类型,可以简单理解为类型安全的any
和any一样,任何类型都可以赋值给unknown类型,但不同的是,unknown类型不可以直接赋值给其他非unknown或any类型的对象,并且不可以访问上面的任何属性
let vAny: any = 'Hello World!'
let vUnknown: unknown = 'Hello World!'
let vNumberForAny: number = vAny // ok! any可以直接赋值给其它任意类型
let vNumberForUnknown: number = vUnknown // error! unknown不可以直接赋值给其它非any和unknown类型的对象
vAny.toLocaleLowerCase() // ok! any可以访问所有的属性
vUnknown.toLocaleLowerCase() // error! unknown对象不可以直接访问上面的属性
如果想要使用unknown,那就必须先推导出unknown的类型,比如typeof
let vUnknown: unknown = 'abc'
// 使用typeof推断出vUnknown的类型是string
if (typeof vUnknown === 'string') {
vUnknown.toLocaleUpperCase() // ok! 因为能进入这个if条件体就证明了vUnknown是字符串类型!
}
let vNumberForUnknown: number = vUnknown as number // unknown类型一定要使用as关键字转换为number才可以赋值给number类型
unknown基本可以替代any,所以在使用any的场景,都应该优先使用unknown。使用了unknown后,我们既允许某个对象储存任意类型的变量,同时也要求别人在使用这个对象的时候一定要先进行类型推断。
never
never是TypeScript的底部类型,是一个不存在,不可能发生的类型
never类型只接受never类型,any都不可以赋值给never
let vAny: any = 1
let vNever: never = vAny // error! never除了自己谁都不接受!
function test(a: number | string) {
if (typeof a === 'number') {
console.log(a)
} else if (typeof a === 'string') {
console.log(a)
} else {
let check: never = a //永远无法到达,因此a的类型为never
}
}
never类型可以很好的帮助我们在未来添加某一个类型时能够检查到代码的逻辑漏洞
function test(a: number | string | boolean) {
if (typeof a === 'number') {
console.log(a)
} else if (typeof a === 'string') {
console.log(a)
} else {
let check: never = a // error! boolean无法赋值给never
}
}
void
void可以理解为null和undefined的联合类型,它表示空值,一般不用于声明值的类型,更常见的场景是表示某个函数没有返回值
注意与never的区别
function noReturn(): void {
console.log('hello') //函数可以正常结束,无返回值
}
function Never(): never {
while (true) {
} //函数永远无法结束
}
function error(): never {
throw new Error('this function will crash')
}
转载自:https://juejin.cn/post/7272779801425150012