对象以及Map、Set、WeakMap、WeakSet
Map
插入键值对,能保持插入顺序,任何类型都可以作为键或值;键是唯一的,for...of 按插入顺序返回键值对。零值相等算法,+0 与 -0相等,NaN 与 NaN相等。
方法:set、get、has、delete、clear等
set
存储任何类型的值,能保持插入顺序,值是唯一的,也是零值相等算法
方法:add、has、delete、clear等
WeakMap
存储可被垃圾回收的键值对,键只能是对象和非全局 Symbol,值可以是任意类型
WeakMap 中的弱引用:在普通的 Map 中,只要 key 存在,相关对象就不会被垃圾回收,但在 WeakMap 中,如果 key 被垃圾回收了,那么这个 key 和它对应的值也会被清理掉。
不可枚举。
WeakSet
存储可被垃圾回收的值的集合,对象和非全局 Symbol,原生类型不能被垃圾回收所以不能存。值是唯一的。
WeakSet 中对象的引用为弱引用:如果没有其他的对 WeakSet 中对象的引用存在,那么这些对象会被垃圾回收。
不可枚举。
Map 与 对象:
- Map 默认不包含任何键,但 Object 有默认键
- Map 键可以是任意类型,但 Object 键只能是 原始类型 或 symbol
- Map 是可迭代对象,可以直接 for...of 枚举,但 Object 不行,它必须通过 Object.keys 等迭代
- Map 频繁删除、添加键值对时性能更好,Object 没做优化
- Map 不能直接被 JSON.stringify、JSON.parse 序列化,Object 可以
对象枚举方法
- for...in 包含可枚举字符串键
- Object.keys 包含自有可枚举字符串键
- Object.getOwnPropertyNames 包含自有字符串键,即使不可枚举
- Object.getOwnPropertySymbols 包含 Symbol 键
for...in 与 for...of
- for...in:迭代对象的可枚举字符串属性(不含 Symbol),包括继承的可枚举属性。所有非负整数键将首先按值升序遍历,然后是其他字符串键按属性创建的先后顺序升序遍历
- for...of:只能遍历可迭代对象的值序列,可迭代对象有 Array、String、Map、Set、arguments 对象等
类型判断
typeof
typeof 123 // "number"
typeof 'hi' // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 10n // "bigint"
typeof function(){} // "function"
typeof null // "object" ← 历史遗留 bug
typeof [] // "object"
typeof {} // "object"
通过将值转换为二进制后,前三位都是 0 则为object,反之则为原始类型,null 是一串长 0,所以显示 object。
可以准确判断除null之外的所有原始类型,null会被判定成object。function类型可以被准确判断为function,而其他所有引用类型都会被判定为object
instanceof
[] instanceof Array // true
[] instanceof Object // true
new Date() instanceof Date // true
1 instanceof Number // false
依赖原型链查找
准确判断引用类型,不像 type 只能知道是 object 而不是 Map 之类,不能判断原始类型
Object.prototype.toString.call()
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(new Date()) // "[object Date]"
Object.prototype.toString.call(/abc/) // "[object RegExp]"
原始类型会被包装,可以判断任何数据类型,继续 .slice(8, -1) 可以获得类型
借用 Object.prototype 原型上的 toString,是因为有些类型的 toString 已经被重写,比如 Array 等
Array.isArray()
Array.isArray([]) // true
Array.isArray({}) // false
一般用来判断是否为数组
constructor
(123).constructor === Number // true
'hi'.constructor === String // true
[].constructor === Array // true
({}).constructor === Object // true
指向创建它的构造函数
原始类型会被包装
对于 null 和 undefined 会报错
Object.getPrototypeOf
const arr = []
Object.getPrototypeOf(arr) === Array.prototype // true
Object.getPrototypeOf(arr) === Object.prototype // false
Object.getPrototypeOf(1) === Number.prototype // true
返回指定对象的原型
原始类型会被包装
Object.prototype.isPrototypeOf
Array.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf([]) // true
Number.prototype.isPrototypeOf(1) // false
Number.prototype.isPrototypeOf(new Number(1)) // true
Number.prototype.isPrototypeOf(Number(1)) // false
检查一个对象是否存在于另一个对象的原型链中
原始类型直接 false
总结
| 方法 | 能判断基本类型 | 能判断对象类型 | 跨 iframe 有效 | 说明 |
|---|---|---|---|---|
| typeof | 可 | 不精确(对象一律 "object") | 是 | 用于基本类型 |
| instanceof | 不能 | 精确 | 否 | 判断原型链 |
| Object.prototype.toString.call | 可 | 精确 | 是 | 最准确的通用方法 |
| Array.isArray | - | 只能判数组 | 是 | 判断数组最推荐 |
| constructor | 可 | 精确 | 否 | 容易被改写 |
| Object.getPrototypeOf | 可 | 精确 | 是 | 获取原型对象 |
| Object.prototype.isPrototypeOf | 不能 | 精确 | 是 | 判断是否在原型链上 |
笔者注:JS 为了让基本类型也能像对象一样调用属性和方法, 在访问属性时会自动创建一个临时包装对象,用完即销毁。null 和 undefined 没有对应的包装对象。
相等比较
宽松相等(==):会执行类型转换,NaN != NaN,+0 == -0,+Infinity != -Infinity。如果其中一个操作数是对象,另一个是原始值,则对象转原始值;一个布尔一个数字,布尔转数字;一个字符一个数字,字符转数字;一个symbol一个不是,返回false。相同的类型值也相等的情况,只有 NaN != NaN。
严格相等(===):不发生类型转换,类型不同为 false,做的比较与 == 相同,认为 NaN 与其他任何值(包括自己)都不全等。也就是说,当类型相等值也相等的情况,只有 NaN !== NaN。indexOf、lastIndexOf、case 都使用严格相等。
Object.is():不进行类型转换,Object.is() 和 === 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。Object.is(NaN, NaN) 为 true,+0、-0不相等。
对于结构相同,但不是同一个的对象,都返回 false;对于 ==,有 null == undefined。
ESM 与 CommonJS
CommonJS
- 导出:单值导出 exports.xxx = yyy,多值导出 module.exports = {...},导出值的拷贝
- 导入:require
- 同步:
require()是 同步加载 的,会在运行时加载模块。因为它是同步的,所以适合 Node.js 中的文件系统操作。在 CommonJS 中,require()会立即执行并返回结果,不支持await。 - 底层:其实就是被包裹了普通函数
- 加载:运行时动态加载
ESM
- 导出: export xxx,导出值的引用,所以更新会同步
- 导入:import xxx,同时可以使用 as 重命名
- 静态分析:
import和export语法在编译时就能静态分析,使得 ESM 支持 Tree Shaking(移除未使用的代码),有助于减少打包体积。 - 异步:有动态
import,是异步加载,会被视为一个 Promise,并且可以和await配合使用。 - 默认开启严格模式
- 加载:编译时静态分析
箭头函数特点
- 箭头函数没有独立的
this、arguments绑定 - 箭头函数不能用作构造函数。使用
new调用它们会引发TypeError。 - this 在定义时决定,而不是调用时,不会创建一个新的
this绑定。 - new.target 是从周围的作用域继承的。如果箭头函数不是在另一个具有
new.target绑定的类或函数中定义的,则会抛出语法错误。
ES6 特性
- 块级作用域:let、const
- 函数默认参数
- 箭头函数
- 模板字符串
- 解构赋值
- 新的对象:Map 与 Set,新的基本类型:Symbol
- promise 和 proxy
- 拓展运算符与可选链
浮点数精度丢失
0.1 + 0.2 不打印 0.3,而是打印 0.30000000000000004,因为JavaScript 中的数字都是 64 位双精度浮点数(IEEE 754),0.1 和 0.2 都无法用二进制精确的表示,比如 0.1 转二进制是 0.00011001100110011…
在比较的时候需要 toFixed 之类的控制精度,或者直接两数相减与精度进行比较
const sum = 0.1 + 0.2;
console.log(Number(sum.toFixed(10))); // 0.3
const isEqual = Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;
console.log(isEqual); // true
防抖
停止一段时间才会执行,常用于搜索框、窗口大小变化
function debounce(func, delay) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func(...args)
}, delay)
}
}
节流
一段时间内只执行一次,常用于接口请求、scroll 监听
function throttle(func, delay) {
let lastTime = 0 // Date.now() 第一次不执行,改成 0 第一次就执行了
return function(...args){
let now = Date.now()
if (now - lastTime >= delay) {
func(...args)
lastTime = now
}
}
}
new
- 创建一个空对象
- 改原型链
- 改this
- 如果构造函数返回非原始值,那么返回这个值;无返回值或返回空值,则返回创建的对象。
function myNew(constructor, ...args){
if (typeof target !== 'function' || !target.prototype) throw TypeError(`${constructor} is not a constructor!`)
const newInstance = {}
newInstance.__proto__ = constructor.prototype // 这两步可以使用 newInstance = Object.create(constructor.prototype) 替代
const obj = constructor.call(newInstance, ...args) // 执行构造函数,this 绑为 newInstance
return typeof obj === 'object' ? obj || newInstance : newInstance
}
instanceof
instanceof 是利用原型链进行比较的
function myInstanceof(obj, target){
if (Object(obj) !== obj) return false // 原始对象不能用
if (typeof target !== 'function' || !target.prototype) throw new TypeError('right side of instanceof needs object!') // 右边必须有 prototype,排除箭头函数
let proto = obj.__proto__ // __proto__ 可以使用 Object.getPrototypeOf 替代
while (proto !== null) { // 查找原型链直到尽头
if (proto === target.prototype) return true
proto = proto.__proto__
}
return false
}
冒泡排序
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
选择排序
function selectionSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) minIndex = j;
}
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
快速排序
function quickSort (nums, k) {
const target = nums.length - k
const sort = (l, r) => {
if (l === r) return nums[l]
let i = l - 1, j = r + 1
const mid = nums[Math.floor((r - l) / 2 + l)] // 注意不能是下标,可能会被换
while (i < j) {
do i++; while (nums[i] < mid)
do j--; while (nums[j] > mid)
if (i < j) [nums[i], nums[j]] = [nums[j], nums[i]]
}
sort(l, j) // 如果要求前 k 大的数,此处可以与 nums[target] 作比较,只排序一侧
sort(j + 1, r)
}
sort(0, nums.length - 1)
return nums[target]
};
更简单但空间复杂度更高的写法:
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) left.push(arr[i]);
else right.push(arr[i]);
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
并发控制 promise
function promisePool(tasks, concurrency = 2) {
let i = 0; // 当前任务索引
const results = [];
const run = async () => {
while (i < tasks.length) {
const currentIndex = i++;
try {
results[currentIndex] = await tasks[currentIndex]();
} catch (err) {
results[currentIndex] = err;
}
}
};
const workers = Array(concurrency).fill(run).map(fn => fn());
return Promise.all(workers).then(() => results);
}
深拷贝
对象的名存在栈,值存在堆,浅拷贝只复制了指向值的地址,没有复制值,但深拷贝还会复制值。
function deepClone(obj, hash = new WeakMap()){
if (typeof obj !== 'object' || obj === null) return obj
if (hash.has(obj)) return hash.get(obj) // 避免循环引用
if (obj instanceof Date) return new Date(obj) // 兼容日期
if (obj instanceof RegExp) return new RegExp(obj) // 兼容正则
if (obj instanceof Map){ // 兼容 Map
const map = new Map()
hash.set(obj, map)
obj.forEach((value, key) => {
map.set(deepClone(key, hash), deepClone(val, hash))
})
return map
}
if (obj instanceof Set){ // 兼容 Set
const set = new Set()
hash.set(obj, set)
obj.forEach(child => {
set.add(deepClone(child, hash))
})
return set
}
const res = Array.isArray(obj) ? [] : {} // 对象或数组
hash.set(obj, res)
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 防止继承的属性被复制,换成 Object.keys 之类的就不用写了
res[key] = deepClone(obj[key], hash)
}
}
for (let sym in Object.getOwnPropertySymbols(obj)) {// 兼容 Symbol 键
res[sym] = deepClone(obj[sym], hash)
}
return res
}
函数柯里化
参数够了,返回执行值,否则返回累积参数的函数
function curry(fn){
let len = fn.length
return function curried(...args) {
if (args.length >= len) return fn.apply(this, args) // 保持原函数 this
else return function(...next) {
return curried.apply(this, [...next, ...args])
}
}
}
bind、apply、call
外面是被借用的,里面是要去借的函数
bind
参数以列表形式传入,永久改变 this 指向,不会立即执行,参数柯里化
被 new 调用时,this 会改为新实例
Function.prototype.myBind(context. ...args){
let self = this
function boundFunc(...args2) {
const finalThis = this instanceof boundFunc ? this : context || globalThis // 作为构造函数调用,this 继承自自己
return self.apply(finalThis, [...args, ...args2])
}
boundFunc.prototype = Object.create(self.prototype)
return boundFunc
}
apply
参数以数组或类数组对象(形如 NodeList、arguments 等类似数组但不包含所有数组方法的对象)形式传入,暂时改变 this 指向,立即执行
第一个参数为 null 或 undefined,this 指向全局;为基本类型,则包装为对象。
原理与 call 相同,只是参数为列表
Function.prototype.myApply = function(context, arr = []) {
// context = context === null || context === undefined ? globalThis : Object(context)
if (typeof this !== 'function') throw new TypeError('must be func!')
if (!Array.isArray(arr)) throw new TypeError('must be arrayLike List!') // 我不知道怎么判断类数组
context = context || gloablThis
const fn = Symbol('fn')
context[fn] = this
let res
res = context[fn](...args)
delete context[fn]
return res
}
call
参数以列表形式传入,暂时改变 this 指向,立即执行
非严格模式下,第一个参数为 null 或 undefined,this 指向全局;为基本类型,则包装为对象。
原理是把函数暂时变成上下文的属性,执行完毕再删除
Function.prototype.myCall = function(context, ...args){
// 非严格模式:第一个参数 null / undefined,指向全局
// context = context ?? globalThis : Object(context) !== context ? Object(context) : context
if (typeof this !== 'funtion') throw new TypeError('must be func!')
context = context || globalThis
const fn = Symbol('fn')
context[fn] = this
const res = context[fn](...args)
delete context[fn]
return res
}
红绿灯
function light(color, time) {
return new Promise((resolve) => { // 核心就是到点以后再 resolve
setTimeout(() => {
console.log(color)
resolve()
}, time)
})
}
function start() {
return light('red', 1000)
.then(() => light('yellow', 1000))
.then(() => light('green', 1000))
.finally(() => start())
}
start()
async function start() {
while(1){
wait light('red', 1000)
await light('yellow', 1000)
await light('green', 1000)
}
}
// 要停止就加一个 stop 参数
promise
状态
只有三种:pending(待定)、fulfilled(兑现)、rejected(拒绝)
非待定状态,称为 Settled(落定)
已解决通常是落定,但也可能是待定(比如依靠另一个 Promise 解决的情况)
并发方法
它们的参数都是可迭代 Promise 对象,返回单个 Promise,并且这个 Promise:
- Promise.race:任一 Promise 落定时落定,状态相同
- Promise.any:有一个输入的 Promise 兑现即为兑现,值为第一个兑现的值,否则拒绝
- Promise.allSettled:全部落定时兑现,兑现值(有状态和值)有顺序
- Promise.all:全部兑现时兑现,兑现值有顺序
其他静态方法
-
Promise.resolve:
Promise.resolve()方法用于以给定值 解决 一个 Promise,如果该值本身就是一个 Promise,那么该 Promise 将被返回,否则,返回一个被解决的 Promise,解决值为该值 -
Promise.reject:返回一个被拒绝的
Promise对象,拒绝原因为给定的参数 -
Promise.try:受一个任意类型的回调函数(无论其是同步或异步,返回结果或抛出异常)及其参数,并将其结果封装成一个
Promise。它会:- 执行
func - 如果
func返回一个值,就用这个值 fulfilled - 如果
func返回的是一个 Promise,就 adopt 那个 Promise 的状态 - 如果
func里抛错了,就直接变成 rejected
- 执行
链式调用
因为上述和下述这些方法都返回 Promise,所以才能被链式调用
- catch:在内部调用
.then(),它实际上就是一个没有传递兑现处理器的.then() - then:
.then()方法最多接受两个参数;第一个参数是 Promise 兑现时的回调函数,第二个参数是 Promise 拒绝时的回调函数。如果onFulfilled不是一个函数,则内部会被替换为一个 恒等 函数((x) => x),它只是简单地将兑现值向前传递。如果onRejected不是一个函数,则内部会被替换为一个 抛出器 函数((x) => { throw x; }),它会抛出它收到的拒绝原因。 - finally:在内部调用
.then()
传递给 then 的两个回调分别称为 兑现处理器 和 拒绝处理器。初始 Promise 的敲定状态决定了要执行哪个处理器
- 如果初始 Promise 被兑现,则使用兑现值调用兑现处理器
- 如果初始 Promise 被拒绝,则使用拒绝原因调用拒绝处理器
处理器的完成情况决定了新 Promise 的敲定状态。
- 如果处理器返回一个 thenable 值,新 Promise 将以与返回值相同的状态落定
- 如果处理器返回一个非 thenable 值,新 Promise 将以返回值兑现;没有返回值就以 undefined 兑现
- 如果处理器抛出错误,新 Promise 将以抛出的错误拒绝
- 如果初始 Promise 没有附加相应的处理器,新 Promise 将落定为与初始 Promise 相同的状态,如没有拒绝处理器的情况下,被拒绝的 Promise 会保持拒绝状态和原因
如果初始 Promise 被拒绝了,那么调用拒绝处理器;如果拒绝处理器正常完成(不抛出错误或返回被拒绝的 Promise),下一个被调用的就是兑现处理器了。因此,如果必须立即处理错误,而且希望在链中保持错误状态,必须在拒绝处理器中抛出某种类型的错误。
中断
const controller = new AbortController();
const { signal } = controller;
fetch('/api/data', { signal })
.then(res => res.json())
.then(console.log)
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求被中止');
}
});
// 1秒后中止请求
setTimeout(() => controller.abort(), 1000);
手写 promise
/**
* 要点:
* 1. 三种状态
* 2. constructor:传入一个执行器,执行器有兑现和拒绝处理器
* 3. 基础的静态方法:resolve、rejected
* 4. 实例方法:then、catch、finally
* 5. 返回 Promise 以实现链式调用
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 储存状态的变量
status = PENDING
// 成功之后的值
value = null
// 失败之后的原因
reason = null
// 存储成功回调函数
onFulfilledCallbacks = []
// 存储失败回调函数
onRejectedCallbacks = []
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(value)
}
}
}
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
const realOnRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
// 链式调用
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})
return promise2
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(onFinally) {
return this.then(
(value) => MyPromise.resolve(onFinally()).then(() => value),
(reason) =>
MyPromise.resolve(onFinally()).then(() => {
throw reason
})
)
}
static resolve(parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter
}
// 转成常规方式
return new MyPromise((resolve) => {
resolve(parameter)
})
}
static reject(reason) {
return new MyPromise((_resolve, reject) => {
reject(reason)
})
}
// 返回待定的 promise,全兑现时兑现按顺序的结果数组,否则拒绝
static all(promises) {
return new MyPromise((resolve, reject) => {
const values = []
if (promises.length === 0) {
return resolve([])
}
let count = 0
promises.forEach((promise, i) => {
MyPromise.resolve(promise).then((res) => {
values[i] = res
count++
if (count === promises.length) resolve(values)
}, reject)
})
})
}
// 返回 promise,有一个落定则落定,状态不变
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(resolve, reject)
})
})
}
// 返回 promise,所有值拒绝才会拒绝
static any(promsies) {
return new MyPromise((resolve, reject) => {
const reasons: any[] = []
promsies.forEach((promise) => {
promise.then(resolve, (err) => {
reasons.push(err)
if (reasons.length === promsies.length)
// @ts-ignore
reject(new AggregateError(reasons))
})
})
})
}
// 返回promise,全部落定则兑现一个包含状态和结果的对象数组
static allSettled(promises) {
return new MyPromise((resolve) => {
const results = []
let count = 0
if (promises.length === 0) {
return resolve([])
}
promises.forEach((promise, i) => {
MyPromise.resolve(promise).then(
(res) => {
results[i] = { status: FULFILLED, value: res }
count++
if (count === promises.length) resolve(results)
},
(err) => {
results[i] = { status: REJECTED, reason: err }
count++
if (count === promises.length) resolve(results)
}
)
})
})
}
}
function resolvePromise(promise, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'))
}
if (typeof x === 'object' || typeof x === 'function') {
if (x === null) {
return resolve(x)
}
let then
try {
then = x.then
} catch (error) {
return reject(error)
}
if (typeof then === 'function') {
let called = false
try {
then.call(
x,
(y) => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} catch (error) {
if (called) return
reject(error)
}
} else {
resolve(x)
}
} else {
resolve(x)
}
}
手写 promise.all
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) return reject(new TypeError('must be array!'))
if (promises.length === 0) resolve([])
let results = []
let cnt = 0
promises.forEach((p, i) => {
Promise.resolve(p)
.then(res => {
results[i] = res
cnt++
if (cnt === promises.length) resolve(results)
})
.catch(err => reject(err))
})
})
}
const p1 = Promise.resolve(1)
const p2 = 42
const p3 = new Promise(res => setTimeout(() => res('done'), 500))
myPromiseAll([p1, p2, p3]).then(res => console.log(res)) // [1, 42, 'done']
发布-订阅
function EventBus() {
const event = new Map()
const on = (name, fn) => {
const targets = event.get(name)
if (!targets) event.set(name, [fn])
else targets.push(fn)
}
const emit = (name, ...args) => {
const fns = event.get(name)
if (fns) {
fns.forEach(fn => {
fn(...args)
})
}
}
const off = (name, fn) => {
const fns = event.get(name)
event.set(name, fns.filter(func => func !== fn))
}
const once = (name, fn) => {
const wrapper = (...args) => {// 包装成执行完以后马上删掉
fn(...args)
off(name, wrapper)
}
on(name, warpper)
}
return {
on,
emit,
off,
once
}
}
数组拍平
function flattern(arr, depth = Infinity) {
const ans = []
if (depth === 0 || !Array.isArray(arr)) return [arr]
arr.forEach(item => {
ans.push(...flattern(item, depth - 1))
})
return ans
}
数组转树
function toTree(arr) {
const map = new Map()
const res = []
// 初始化 map
arr.map(item => map.set(item.id, {...item, children: []}))
arr.forEach(item => {
const parentId = item.parentId
const node = map.get(item.id)
if (parentId === 0) {// 根节点
res.push(node)
}else {
const parent = map.get(parentId)
parent?.children.push(node) // 应该塞入处理好的节点
}
})
return res
}
function toTree(arr, parentId = 0) {
const res = []
arr.forEach(item => {
if (item.parentId === parentId) {
const children = toTree(item, item.id)
res.push({...item, children})
}
})
return res
}