深拷贝

function deepClone(obj, cache = new WeakMap()) {
  // 检查是否已经克隆过该对象,避免循环引用
  if (cache.has(obj)) {
    return cache.get(obj);
  }
 
  if (obj === null || typeof obj !== "object" || typeof value === "function") {
    return obj;
  }
 
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
 
  const result = Array.isArray(obj)
    ? []
    : Object.create(Object.getPrototypeOf(obj));
 
  cache.set(obj, result);
 
  for (const key of Object.keys(obj)) {
    const value = obj[key];
    result[key] = deepClone(value, cache);
  }
 
  return result;
}

计算嵌套对象里各个 value 值的总和

function sum(obj) {
  let total = 0
  
  function f(value) {
    if (typeof value === 'object') {
      for (const key of Object.keys(value)) {
        f(value[key])
      }
    } else {
        total += value
    }
  }
 
  f(obj)
 
  return total
}

数组去重

// 1.indexOf 或 includes
function unique(arr) {
  let res = []
  for (const ele of arr) {
    if (res.indexOf(ele) === -1) res.push(ele)
    // if (!res.includes(ele)) res.push(ele)
  }
  return res
}
 
// 2.Set
function unique(arr) {
  return [...new Set(arr)]
}
 
// 3.filter
function unique(arr) {
  return arr.filter((ele, index, arr) => arr.indexOf(ele) === index)
}

数组扁平化

// 1.递归
function flat(arr, depth = 1) {
  let res = []
  for (const ele of arr) {
    if (Array.isArray(ele) && depth > 0) {
      res = res.concat(flat(ele, depth - 1))
    } else {
      res.push(ele)
    }
  }
  return res
}
 
// 2.reduce
function flat(arr, depth = 1) {
  return depth > 0
    ? arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur), [])
    : arr
}

实现 call/apply

const obj = {a: 1}
const func = function (...args) {
  console.log(this, args)
}
 
// 本质就是要构造一个这样的对象:
// obj = {
//   a: 1,
//   Symbol(): func
// }
 
Function.prototype.call = function (context) {
  context = Object(context) // 原始值变成包装类型
  
  const fnSymbol = Symbol() // 避免属性冲突
  context[fnSymbol] = this // this 此时是调用 call 的原函数
  
  const args = [...arguments].slice(1) // 获取调用 call 时传入的参数
 
  const res = context[fnSymbol](...args) // 执行对象下的原函数,此时原函数的 this 执行的就是这个对象
 
  delete context[fnSymbol]
 
  return res
}
 
// apply 同上,只是参数有点区别
Function.prototype.apply = function (context, arr = []) {
  context = Object(context) // 原始值变成包装类型
 
  const fnSymbol = Symbol() // 避免属性冲突
  context[fnSymbol] = this // this 此时是调用 call 的原函数
 
  const res = context[fnSymbol](...arr) // 执行对象下的原函数,此时原函数的 this 执行的就是这个对象
 
  delete context[fnSymbol]
 
  return res
}

实现 bind

Function.prototype.bind = function (context) {
  const fn = this // this 就是调用 bind 的源函数
  const args = [...arguments].slice(1) // 获取调用 bind 时传入的参数
  
  return function(...moreArgs) {
    return fn.apply(context, args.concat(...moreArgs))
  }
}

Promise.all

全部成功则成功,有一个失败则失败

function promiseAll(promises) {
  // 判断输入是否为 Iterable
  const isIterable = typeof promises[Symbol.iterator] === "function"
 
  // 不是的话就回传错误讯息
  if (!isIterable) {
    return new TypeError("Arguments must be iterable")
  }
 
  // 把 Iterable 转成 Array
  promises = Array.from(promises)
 
  const outputs = []; // 先定义一个最终要 resolve 的 outputs,之后每个 promise 被 fulfilled 时,就放到 outputs 里面
  let resolveCounter = 0; // 记录有多少个 promise 已经 fulfilled
 
  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          outputs[index] = value
          resolveCounter += 1
          if (resolveCounter === promises.length) {
            resolve(outputs)
          }
        })
        .catch((err) => {
          reject(err)
        })
    })
  })
}

Promise.any

取第一个成功的

function promiseAny(promises) {
  // 省略参数校验...
  
  const errors = []        // 存储所有 Promise 的拒绝原因
  let rejectedCount = 0    // 记录已拒绝的 Promise 数量
  
  return new Promise((resolve, reject) => {
    // 迭代过 promises
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          // 只要有 fulfill 的,就马上 resolve
          resolve(val)
        })
        .catch((err) => {
          errors[index] = err // 记录 err 信息
          rejectedCount++
          if (rejectedCount === promises.length) reject(new AggregateError(errors, 'All promises were rejected')) // 全部 rejected 则返回错误
        })
    })
  })
}

Promise.race

取第一个完成的

function promiseRace(promises) {
  // 省略参数校验...
 
  return new Promise((resolve, reject) => {
    // 迭代过 promises
    for (const p of promises) {
      p.then((val) => {
        // 只要有 fulfill 的,就马上 resolve
        resolve(val)
      }).catch((e) => {
        // 或是只要有 reject 的,就马上 reject
        reject(e)
      })
    }
  })
}

Promise.allSettled

全部完成后返回对应的结果

function promiseAllSettled(promises) {
  // 省略参数校验...
 
  const outputs = []      // 记录结果
  let settledCount = 0    // 记录已完成的 Promise 数量
  
  return new Promise((resolve, reject) => {
    // 迭代过 promises
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          // fulfill 的放入结果中
          outputs[index] = { status: 'fulfilled', value: value }
          settledCount++
          if (settledCount === promises.length) resolve(outputs) // 全部完成了,返回结果
        })
        .catch((err) => {
          // rejected 的放入结果中
          outputs[index] = { status: 'rejected', reason: err }
          settledCount++
          if (settledCount === promises.length) resolve(outputs) // 全部完成了,返回结果
        })
    })
  })
}

Promise 限制并发数量

请实现 PLimit 类,该类可以控制异步函数的并发数量

const limit = new PLimit(2); // 设置并发数为 2
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
 
const task = async () => {
  console.log('Start task');
  await delay(1000); // 模拟一个耗时的任务
  console.log('End task');
};
 
Promise.all([
  limit.run(task),
  limit.run(task),
  limit.run(task),
  limit.run(task),
  limit.run(task),
  limit.run(task),
  limit.run(task),
  limit.run(task),
  limit.run(task),
  limit.run(task),
]).then(() => console.log("done"));
 
// 请补充下面的代码片段
class PLimit {
  constructor(limit) {
    this.limit = limit;
    // 请补充
  }
 
  run(fn) {
    // 请补充
  }
}

答案如下:

class PLimit {
  constructor(limit) {
    this.limit = limit;
    this.queue = []; // 任务队列
    this.running = 0; // 当前运行任务数
  }
 
  run(fn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject }); // 每个任务要跟它自身的 resolve 和 reject 相对应
      this.execute();
    });
  }
 
  execute() {
    // 当前运行任务数量超过限制 || 任务队列为空
    if (this.running >= this.limit || !this.queue.length) return;
 
    // 开始一个新任务
    this.running++;
    let { fn, resolve, reject } = this.queue.shift();
 
    // 执行它
    fn()
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        reject(err);
      })
      .finally(() => {
        // 任务结束,进行下一个任务
        this.running--;
        this.execute();
        // 如果要每次执行完 limit 个再执行下 limit 个,则把上面的 this.execute() 替换成:
        // if (this.running === 0) {
        //   for (let i = 0; i < this.limit; i++) {
        //     this.execute();
        //   }
        // }
      });
  }
}

实现一个 LazyMan,可以按照以下方式调用:

LazyMan("Hank")
输出:
Hi! This is Hank!
 
LazyMan("Hank").sleep(10).eat("dinner")
输出:
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner
 
LazyMan("Hank").eat("dinner").eat("supper")
输出:
Hi! This is Hank!
Eat dinner
Eat supper
 
LazyMan("Hank").sleepFirst(5).eat("supper")
输出:
//等待5秒
Wake up after 5
Hi! This is Hank!
Eat supper
function LazyMan(name) {
    const queue = [] // 任务队列
 
    // 执行函数,依次取任务队列中的任务进行执行
    function run() {
        if (!queue.length) return
        const task = queue.shift()
        task()
    }
 
    // 保证在执行完同步任务后再执行 run
    setTimeout(() => {
        run()
    })
 
    // 函数调用时的初始化任务
    const sayHi = () => {
        console.log(`Hi! This is ${name}!`)
        run()
    }
    queue.push(sayHi)
 
    // 要实现链式调用,需要定义一个操作映射对象,并返回这个 obj
    const obj = {
        sleep: (time) => {
            queue.push(() => {
                setTimeout(() => {
                    console.log(`Wake up after ${time}`)
                    run()
                }, time * 1000)
            })
 
            return obj
        },
        eat: (something) => {
            queue.push(() => {
                console.log(`Eat ${something}`)
                run()
            })
 
 
            return obj
        },
        sleepFirst: (time) => {
            queue.unshift(() => {
                setTimeout(() => {
                    console.log(`Wake up after ${time}`)
                    run()
                }, time * 1000)
            })
 
            return obj
        },
    }
 
    return obj
}