JS 复习

96 阅读16分钟

对象以及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 重命名
  • 静态分析:importexport 语法在编译时就能静态分析,使得 ESM 支持 Tree Shaking(移除未使用的代码),有助于减少打包体积。
  • 异步:有动态 import,是异步加载,会被视为一个 Promise,并且可以和 await 配合使用。
  • 默认开启严格模式
  • 加载:编译时静态分析

箭头函数特点

  • 箭头函数没有独立的 thisarguments 绑定
  • 箭头函数不能用作构造函数。使用 new调用它们会引发 TypeError
  • this 在定义时决定,而不是调用时,不会创建一个新的 this 绑定。
  • new.target 是从周围的作用域继承的。如果箭头函数不是在另一个具有 new.target 绑定的类或函数中定义的,则会抛出语法错误。

ES6 特性

  1. 块级作用域:let、const
  2. 函数默认参数
  3. 箭头函数
  4. 模板字符串
  5. 解构赋值
  6. 新的对象:Map 与 Set,新的基本类型:Symbol
  7. promise 和 proxy
  8. 拓展运算符与可选链

浮点数精度丢失

0.1 + 0.2 不打印 0.3,而是打印 0.30000000000000004,因为JavaScript 中的数字都是 64 位双精度浮点数(IEEE 754),0.10.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

  1. 创建一个空对象
  2. 改原型链
  3. 改this
  4. 如果构造函数返回非原始值,那么返回这个值;无返回值或返回空值,则返回创建的对象。
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。它会:

    1. 执行 func
    2. 如果 func 返回一个值,就用这个值 fulfilled
    3. 如果 func 返回的是一个 Promise,就 adopt 那个 Promise 的状态
    4. 如果 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
}