1. 防抖

防抖:在事件被触发的 n 秒后再执行,如果这 n 秒内又被触发,则重新计时。
要求写一个可以实现首次立即执行的防抖函数,缺少返回值

  1. /**
  2. * 防抖函数,可立即执行,可传参,但无返回值
  3. * @param {Function} fn 需要防抖的函数
  4. * @param {Number} delay 防抖的时间
  5. * @param {Boolean} immediate 是否立即执行
  6. * @returns
  7. */
  8. const debounce = (fn, delay = 800, immediate = false) => {
  9. let timer = null;
  10. return function(...args) {
  11. if(timer !== null) clearTimeout(timer);
  12. if(immediate && timer === null) {
  13. fn.apply(this, args);
  14. timer = setTimeout(()=> timer = null, delay);
  15. return;
  16. }
  17. timer = setTimeout(()=>{
  18. fn.apply(this, args);
  19. clearTimeout(timer);
  20. timer = null;
  21. }, delay)
  22. }
  23. }

debounce 内部通过 apply 或 call 方式来调用原函数,可以使原函数 this 指向不变,前提是新函数必须挂载到原函数的对象上,比如 o.b = debounce(o.a) 使用场景:

  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
  • 输入场景:搜索联想词功能类似

2. 节流

节流:在规定单位时间内,只能触发一次函数。如果多次触发则只生效一次。

  1. /**
  2. * 节流函数
  3. * @param {Function} fn 需要节流的函数
  4. * @param {Number} delay 节流的时间
  5. * @returns
  6. */
  7. const throttle = (fn, delay) => {
  8. let previous = 0;
  9. return function (...args) {
  10. const now = Date.now();
  11. if (now - previous > delay) {
  12. previous = now;
  13. return fn.apply(this, args);
  14. }
  15. }
  16. }

3. 深拷贝

简单版

  1. const cloneDeep = (source) => JSON.parse(JSON.stringify(source));

局限性:

  • 时间对象会变成字符串
  • 函数或者 undefined 会丢失
  • RegExp、NodeList 等特殊对象会变成空对象
  • NaN、 Infinity、 -Infinity 会变成 null
  • 会抛弃对象的 constructor,所有的构造函数会指向 Object
  • 对象有循环引用,会报错

    1. const a = {}
    2. a.next = a;
    3. cloneDeep(a);

    image.png

    面试版

    支持 Date、RegExp、Function、Symbol、NaN、无穷值、循环引用的深拷贝 ```javascript /**

    • 数据类型:
      1. 可以直接返回的:
    • Number、String、Boolean、undefined、Symbol、BigInt、Function
    • null(需要特殊判断) *
      1. 不可以直接返回的:
    • Object、Array
    • Object 的特殊细分类型:Date、RegExp *
      1. 避免循环引用 */ const deepClone = (obj, hash = new WeakMap()) => { if (obj === null) return obj if (obj instanceof Date) return new Date(obj) if (obj instanceof RegExp) return new RegExp(obj) if (typeof obj !== ‘object’) return obj

    if (hash.has(obj)) return hash.get(obj)

    const target = Array.isArray(obj) ? [] : {} hash.set(obj, target)

    Reflect.ownKeys(obj).forEach(key => { target[key] = deepClone(obj[key], hash) })

    return target }

// 测试用例 const a = {}

const obj = { name: ‘foo’, reg: /456/, date: new Date(), add: () => { }, goods: [1, 2, 3, 4, 5], circleReference: a, symbol: Symbol(), NaN: NaN, Null: null, Undefined: undefined }

a.circleReference = obj

const newObj = deepClone(obj) console.log(newObj) console.log(obj.symbol === newObj.symbol)

  1. <a name="kDUEb"></a>
  2. # 4. Promise
  3. <a name="Wd7XK"></a>
  4. ## 简单版
  5. ```javascript
  6. function MyPromise(executor) {
  7. this.status = 'pending';
  8. const resolve = (result) => {
  9. if (this.status === 'pending') {
  10. this.status = 'fulfilled';
  11. this.result = result;
  12. }
  13. }
  14. const reject = (err) => {
  15. if (this.status === 'pending') {
  16. this.status = 'rejected';
  17. this.result = err;
  18. }
  19. }
  20. try {
  21. executor(resolve, reject);
  22. } catch(err) {
  23. reject(err);
  24. }
  25. }
  26. Mypromise.prototype.then = function (onFulfilled, onRejected) {
  27. if(this.status === 'fulfilled') onFulfilled && onFulfilled(this.result);
  28. if(this.status === 'rejected') onRejected && onRejected(this.result);
  29. }

5. 柯里化 (curry)

把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术

  1. const curry = function (fn, ...firstArgs) {
  2. return function (...args) {
  3. // 首次柯里化时,若未提供firstArgs,则不拼接进args
  4. if (firstArgs.length) args = firstArgs.concat(args);
  5. // 递归调用,若 args 参数长度不满足函数 fn 的参数要求,则将参数传入,并柯里化并返回
  6. if (args.length < fn.length) return curry(fn, ...args);
  7. // 递归出口,执行函数
  8. return fn.apply(null, args)
  9. }
  10. }
  1. // 应用
  2. function multiFn(a, b, c) {
  3. return a * b * c;
  4. }
  5. var multi = curry(multiFn);
  6. multi(2)(3)(4);
  7. multi(2,3,4);
  8. multi(2)(3,4);
  9. multi(2,3)(4)

6. 函数组合(compose)

  1. function fn1(x) {
  2. return x + 1;
  3. }
  4. function fn2(x) {
  5. return x - 2;
  6. }
  7. function fn3(x) {
  8. return x * 3;
  9. }
  10. function fn4(x) {
  11. return x + 4;
  12. }
  13. // 相当于 fn1(fn2(fn3(fn4(x))))
  14. const a = compose(fn1, fn2, fn3, fn4);
  15. console.log(a(1)); // 14
  16. // 经过 fn4 = 1 + 4 = 5
  17. // 经过 fn3 = 5 * 3 = 15
  18. // 经过 fn2 = 15 - 2 = 13
  19. // 经过 fn1 = 13 + 1 = 14
  1. const compose = (...fn) => {
  2. if (fn.length === 0) return a => a
  3. if (fn.length === 1) return fn[0]
  4. return fn.reduce((pre, cur) => (...args) => pre(cur(...args)))
  5. }

7. 并发的调度器

JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个。完善代码中Scheduler类,使得以下程序能正确输出

  1. // 实现这个类
  2. class Scheduler {
  3. }
  4. const timeout = (time) => new Promise(resolve => {
  5. setTimeout(resolve, time)
  6. })
  7. const scheduler = new Scheduler();
  8. const addTask = (time, order) => {
  9. scheduler.add(() => timeout(time))
  10. .then(() => console.log(order))
  11. }
  12. addTask(1000, '1')
  13. addTask(500, '2')
  14. addTask(300, '3')
  15. addTask(400, '4')
  16. // output: 2 3 1 4
  17. // 一开始,1、2两个任务进入队列
  18. // 500ms时,2完成,输出2,任务3进队
  19. // 800ms时,3完成,输出3,任务4进队
  20. // 1000ms时,1完成,输出1
  21. // 1200ms时,4完成,输出4
  1. class Scheduler {
  2. constructor(max) {
  3. this.max = max || 2;
  4. this.queue = []; // 等待执行的任务队列
  5. this.tasks = []; // 正在执行的任务
  6. }
  7. add(task) {
  8. return new Promise((resolve, reject) => {
  9. try {
  10. task.resolve = resolve; // 存放 resolve 函数,当 task 执行后可以使用
  11. if (this.tasks.length < this.max) this.run(task);
  12. else this.queue.push(task);
  13. } catch (error) {
  14. reject(error);
  15. }
  16. })
  17. }
  18. run(task) {
  19. this.tasks.push(task);
  20. task().then((res) => {
  21. // 异步任务执行完,resolve 执行完的值
  22. task.resolve(res);
  23. this.clearTask(task);
  24. })
  25. }
  26. // 清除执行完的任务,并把缓存的任务加入任务队列
  27. clearTask(task) {
  28. const index = this.tasks.indexOf(task);
  29. this.tasks.splice(index, 1);
  30. if (this.queue.length) this.run(this.queue.shift())
  31. }
  32. }

8. 订阅者模式(EventEmitter)

EventEmiiter 既是 node 中各个模块的基石,又是前端组件通信的依赖手段之一,同时涉及了订阅-发布设计模式,是非常重要的基础。
题目描述:实现一个发布订阅模式拥有 on emit once off 方法

  1. // 实现的使用如下
  2. const event = new EventEmitter();
  3. const handle = (...rest) => {
  4. console.log(rest);
  5. };
  6. event.on("click", handle);
  7. event.emit("click", 1, 2, 3, 4);
  8. event.off("click", handle);
  9. event.emit("click", 1, 2);
  10. event.once("dbClick", () => {
  11. console.log(123456);
  12. });
  13. event.emit("dbClick");
  14. event.emit("dbClick");
  1. class EventEmitter {
  2. construtor() {
  3. this.events = {}
  4. }
  5. // 订阅类型为 type 的事件,callback 是回调函数
  6. on(type, callback) {
  7. if (this.events[type]) this.events[type].push(callback)
  8. else this.events[type] = [callback]
  9. }
  10. // 取消订阅类型为 type 的事件,原本的回调函数是 callback
  11. off(type, callback) {
  12. if (this.events[type]) return new Error('不存在此类型');
  13. this.events[type] = this.events[type].filter(f => f !== callback);
  14. }
  15. // 发布订阅,触发事件
  16. emit(type, ...args) {
  17. if (this.events[type]) return new Error('不存在此类型');
  18. this.events[type].forEach(f => f.apply(this, args))
  19. }
  20. // 添加只执行一次的订阅事件
  21. once(type, callback) {
  22. function onceFn(...args) {
  23. callback.apply(this, args);
  24. this.off(type, onceFn);
  25. }
  26. this.on(type, onceFn);
  27. }
  28. }

9. Function.prototype.call

  1. Function.prototype.Call = function (context, ...args) {
  2. // 函数使用该方法时,this 指向该函数
  3. context = context || window;
  4. // 利用隐式绑定的规则,谁调用,函数上下文就属于谁
  5. const fnSym = Symbol();
  6. context[fnSym] = this // 把函数添加到该对象上
  7. const res = context[fnSym](...args);
  8. delete context[fnSym]; // 把添加到对象上的函数删除
  9. return res;
  10. }

10. Function.prototype.apply

  1. Function.prototype.Apply = function (context, args) {
  2. if (args !== undefined && !Array.isArray(args)) return new Error('Apply 第二个参数应该是数组');
  3. args = args || [];
  4. const fnSym = Symbol();
  5. context[fnSym] = this;
  6. const res = context[fnSym](...args);
  7. delete context[fnSym];
  8. return res;
  9. }

11. Function.prototype.bind

对于直接调用来说,因为 bind 可以实现类似于 fn.bind(obj, 1)(2),所以需要将两边的参数拼起来。 对于通过 new 调用来说,函数不会被任何方式改变 this,所以要忽略传入的 this。

  1. // 测试用例
  2. function Person(name, age) {
  3. console.log(name); //'我是参数传进来的name'
  4. console.log(age); //'我是参数传进来的age'
  5. console.log(this); // 构造函数 this 指向实例对象
  6. }
  7. // 构造函数原型的方法
  8. Person.prototype.say = function() {
  9. console.log(123);
  10. }
  11. let obj = {
  12. objName: '我是obj传进来的name',
  13. objAge: '我是obj传进来的age'
  14. }
  15. // 普通函数
  16. function normalFun(name, age) {
  17. console.log(name); //'我是参数传进来的name'
  18. console.log(age); //'我是参数传进来的age'
  19. console.log(this); //普通函数this指向绑定bind的第一个参数 也就是例子中的obj
  20. console.log(this.objName); //'我是obj传进来的name'
  21. console.log(this.objAge); //'我是obj传进来的age'
  22. }
  23. // 先测试作为构造函数调用
  24. let bindFun = Person.Bind(obj, '我是参数传进来的name')
  25. let a = new bindFun('我是参数传进来的age')
  26. a.say() //123
  27. // 再测试作为普通函数调用
  28. let bindFun = normalFun.Bind(obj, '我是参数传进来的name')
  29. bindFun('我是参数传进来的age')
  1. Function.prototype.Bind = function (context, ...firstrgs) {
  2. // 把函数暂存起来
  3. const fn = this;
  4. const fnSym = Symbol();
  5. context[fnSym] = fn;
  6. return function F(...args) {
  7. // 判断是否是 new 操作
  8. if (this instanceof F) {
  9. const res = new context[fnSym](...firstrgs, ...args);
  10. delete context[fnSym]
  11. return res;
  12. } else {
  13. const res = context[fnSym](...firstrgs, ...args);
  14. delete context[fnSym]
  15. return res;
  16. }
  17. }
  18. }

12. JSON.stringify

  • JSON.stringify(value[, replacer [, space]])
    • value:将要序列化成一个 JSON 字符串的值
    • replacer
      • 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性,都会经过该函数的转换和处理;
      • 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
      • 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化
    • space:指定缩进用的空白字符串,用于美化输出(pretty-print);
      • 如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;
      • 如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;
      • 如果该参数没有提供(或者为 null),将没有空格
    • 返回值:一个表示给定值的 JSON 字符串

      几个注意的点:

      • 要转换的对象中存在 BigInt 格式时无法转换,需抛出 TypeError
      • 如果对象的值为 undefined,则该对象会被忽略
      • 数组中所有无法转换的值都会被转换为 null
const jsonStringify = source => {
  const sourceType = Object.prototype.toString.call(source).slice(8, -1);
  // 无法转换 BigInt 类型
  if (sourceType === 'BigInt') return new TypeError('Invalid JSON');
  const targetResult = {
    Null: 'null',
    Undefined: undefined,
    Function: undefined,
    Symbol: undefined,
    // 下面需要在前面加一层判断是为了防止:
    // 1. 不是 Date 的对象执行 toISOString
    // 2. 数组中的 Symbol 被转为字符串(会报错)
    Date: `"${sourceType === 'Date' && source.toISOString()}"`,
    String: `"${sourceType === 'String' && source}"`,
    Boolean: `${sourceType === 'Boolean' && source}`,
    Number: `${sourceType === 'Number' && source}`,
  }
  if (Object.keys(targetResult).includes(sourceType)) return targetResult[sourceType];
  let target = ''
  // 剩下数组 和 对象的情况
  if (sourceType === 'Object') {
    Object.keys(source).forEach(key => {
      const res = jsonStringify(source[key]);
      // 如果 value 为 undefined 就不存入字符串
      if (source[key] !== undefined && res !== undefined) {
        target += `"${key}":res,`
      }
    })
    return `{${target.substring(0, target.length - 1)}}`
  } else {
    // 不用 forEach 因为会忽略 undefined 的值
    // JSON.stringify 会将数组 undefined 的值转为 null
    for (let item of source) target += `${jsonStringify(item) || 'null'},`
    return `[${target.substring(0, target.length - 1)}]`
  }
}

13. JSON.parse

JSON.parse(text[, reviver])

  • text:要被解析成 JavaScript 值的字符串
  • reviver:转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值,调用时机在 parse 函数返回之前
  • 返回值:object 类型, 对应给定 JSON 文本的对象/值

    使用 eval

    JSON.Parse = source => eval(`(${source})`);
    

    eval() 执行的代码拥有着执行者的权利。如果其运行的字符串代码被恶意方操控修改,最终可能会在用户计算机上运行恶意代码。它会执行 JS 代码,有 XSS 漏洞

如果想用 eval,必须对参数进行 json 校验

JSON.Parse = source => {
  const rx_one = /^[\],:{}\s]*$/
  const rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g
  const rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g
  const rx_four = /(?:^|:|,)(?:\s*\[)+/g
  if (
    rx_one.test(
      source
      .replace(rx_two, "@")
      .replace(rx_three, "]")
      .replace(rx_four, "")
    )
  ) {
    return eval("(" + source + ")")
  }
}

使用 Function

JSON.Parse = source => new Function(`return ${source}`)()

eval 与 Function 都有着动态编译 js 代码的作用,但是在实际的编程中并不推荐使用

14. 列表转树结构

// 题目描述
[
    {
        id: 1,
        text: '节点1',
        parentId: 0 //这里用0表示为顶级节点
    },
    {
        id: 2,
        text: '节点1_1',
        parentId: 1 //通过这个字段来确定子父级
    }
    ...
]

转成
[
    {
        id: 1,
        text: '节点1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点1_1',
                parentId:1
            }
        ]
    }
]
const listToTree = (lists) => {
    if(!Array.isArray(lists)) return new Error('输入不是数组');
  const map = {}, tree = [];
  lists = JSON.parse(JSON.stringify(lists))
  for (let list of lists) {
    list.children = [];
      map[list.id] = list;
  }
  for (let list of lists) {
      if (list.parentId === 0) tree.push(list);
    else map[list.parentId].children.push(list);
  }
  return tree;
}

15. 树结构转列表

const treeToList = (tree) => {
    const res = [];
  const dfs = (tree) => {
      tree.forEach(item => {
        if (item.children && item.children.length) dfs(tree.children);
      const tmp = JSON.parse(JSON.stringify(item));
      delete tmp.children;
      res.push(tmp);
    })
  }
  dfs(tree);
    return res;
}

16. 数组去重

普通版

const unique = (arr) => {
    arr.sort();
  let last = null;
  const res = [];
  arr.forEach(item => {
      if (item !== last) {
        res.push(item);
      last = item;
    }
  })
  return res;
}

使用 Set

const unique = (arr) => Array.from(new Set(arr));

对象数组去重

根据每个对象的某一个具体属性来进行去重

const uniqBy = (arr, property) => {
  const propertyArr = [];

  const res = [];
  arr.forEach(item => {
    if (!propertyArr.includes(item[property])) {
      res.push(item);
      propertyArr.push(item[property]);
    }
  })
  return res;
}

17. Array 的方法

Array.prototype.map

Array<any>
  .map<U>(
    callbackfn: (value: any, index: number, array: any[]) => U, 
    thisArg?: any
): U[]
Array.prototyep.Map = function(callbackFn, thisArg) {
    const res = [];
  for(let i = 0;i < this.length;i++) {
      res.push(callbackFn.call(thisArg, this[i], i, this));
  }
    return res;
}

Array.prototype.reduce

Array<any>
  .reduce(
    callbackfn: (previousValue: any, currentValue: any,currentIndex: number,array: any[] => any
        initialValue?: any
): any
Array.prototype.Reduce = function (fn, initialVal) {
  if (this.length === 0) return new Error('数组不能为空')
  let res = initialVal || this[0]
  let i = initialVal ? 0 : 1
  for (; i < this.length; i++) {
    res = fn.call(this, res, this[i])
  }
  return res
}

Array.prototype.flat

使用 JSON 方法

无法控制扁平化的深度:depth不起作用

Array.prototype.Flat = function(depth) {
    const reg = /(\[)|(\])/g;
  const strArr = JSON.stringify(this)
  return `[${strArr.replace(reg, '')}]`;
}

使用 reduce + concat

Array.prototype.Flat = function(depth) {
    let res = this;
  // 判断 this 指向的数组中是否有元素 为 数组
  // 判断还未减少的层数 depth
  while(res.some(Array.isArray) && depth-- > 0) {
         // 第二个参数传初始值,既防止数组长度为 1 而出错,又可以用 concat 方法
      res = res.reduce((pre, cur) => pre.concat(cur) ,[])
  }
  return res;
}

18. 实现双向绑定

视图变化 <——> 数据变化

使用 defineProperty

const span = document.getElementById('span');
const input = document.getElementById('input');
const data = { val: 'default' };

Object.defineProperty(data, 'val', {
    set(value) {
    // 数据改变 ——> 视图改变
      span.innerHTML = value;
    input.value = value;
  }
})

input.addEventListener('change', (e) => {
    // 视图变化 ——> 数据变化
  data.val = e.target.value
})

使用 Proxy

const span = document.getElementById('span');
const input = document.getElementById('input');
const data = { val: 'default' };

const handler = {
    set(target, key, value) {
    target[key] = value; // 修改源对象的属性值
    // 数据改变 ——> 视图改变
    span.innerHTML = value;
    input.value = value;
      return value
  }
}

const proxy = new Proxy(data, handler);

input.addEventListener('change', (e) => {
    // 视图变化 ——> 数据变化
  proxy.val = e.target.value
})
// 存储被监听对象 与 其监听回调函数 的映射
const ObserveStore = new Map()

/**
 * 基于 proxy 代理 target,监听 target 的数据变化
 * @param {Object} target 监听对象
 * @param {Boolean} deepObservable 是否深度监听
 */
const makeObservable = (target, deepObservable = false) => {
  const handlerName = Symbol('observeHandler')
  ObserveStore.set(handlerName, [])

  // 给监听的对象 target 增加用于添加监听回调的方法
  target.observe = (handler) => ObserveStore.get(handlerName).push(handler)

  if (deepObservable) {
    const keys = Reflect.ownKeys(target)
    for (let key of keys) {
      if (typeof target[key] === 'object' && target[key] !== null) {
        target[key] = makeObservable(target[key], deepObservable)
      }
    }
  }

  const proxyHandler = {
    get(target, property, receiver) {
      const result = Reflect.get(target, property, receiver)
      if (result) {
        const handlers = ObserveStore.get(handlerName)
        handlers.forEach(handler => handler('get', property, target[property]))
      }
      return result
    },

    set(target, property, value, receiver) {
      const result = Reflect.set(target, property, value, receiver)
      if (result) {
        const handlers = ObserveStore.get(handlerName)
        handlers.forEach(handler => handler('set', property, value))
      }
      return result
    },

    deleteProperty(target, property) {
      const success = Reflect.delete(target, property)
      if (success) {
        const handlers = ObserveStore.get(handlerName)
        handlers.forEach(handler => handler('delete', property))
      }
      return success
    }
  }
  return new Proxy(target, proxyHandler)
}


// 使用方式
let user = {
  name: 55,
  house: {
    has: false,
  }
};
user = makeObservable(user, true)

user.observe((method, key, value) => {
  console.log('user', method, key, value)
})

user.house.observe((method, key, value) => {
  console.log('house', method, key, value)
})

user.house.has = true;

19. Object.create

Object.create(proto,[propertiesObject])

  • proto:新创建对象的原型对象
  • propertiesObject:将为新创建的对象添加指定的属性值和对应的属性描述
    Object.Create = function (proto, properties = {}) {
    if (/null|object/.test(typeof proto) === false) {
      throw new TypeError('Object prototype may only be an Object or null');
    }
    const res = { __proto__: proto };
    Object.defineProperties(res, properties);
    return res;
    }
    

    20. maxRequest

    实现 maxRequest,成功后 resolve 结果,失败后重试,尝试超过一定次数才真正的 reject
    function maxRequest(fn, maxNum = 1) {
    return new Promise((resolve, reject) => {
      if (maxNum === 0) {
        reject('maxRequest')
        return
      }
      Promise.resolve(fn())
        .then(res => {
          resolve(res);
        })
        .catch(err => {
          return maxRequest(fn, maxNum - 1);
        })
        .catch(err => reject(err))
    })
    }
    
    ```javascript // 测试用例 function getData() { return new Promise(async (resolve, reject) => {
      setTimeout(() => {
          reject('err')
      }, 2000);
    
    }) }

maxRequest(getData, 5).then(res => { console.log(‘maxRequest res = ‘, res); }).catch(err => { console.log(err); })

<a name="h4SND"></a>
# 21. 延迟任务队列
实现一个 Queue,task 方法可以添加任务,在一段延迟后执行下一个任务,start 方法开始这个任务队列<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1174243/1646147604585-b70f5167-6cf6-4b5e-a683-2b26f07bf6f3.png#clientId=u2cc8f693-7dc3-4&crop=0.0091&crop=0.007&crop=1&crop=1&from=paste&height=282&id=u0bbc26f5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=356&originWidth=820&originalType=binary&ratio=1&rotation=0&showTitle=false&size=77862&status=done&style=none&taskId=ue5df9f0b-4109-4c6e-ac17-89a441c2544&title=&width=650)
```javascript
class Queue {
  constructor() {
    this.queue = []
  }
  task(delay, fn) {
    this.queue.push({ delay, fn });
    return this;
  }
  circle() {
    const { queue } = this
    const node = queue.shift()
    if (node) {
      const { delay, fn } = node
      const timer = setTimeout(() => {
        fn();
        clearTimeout(timer)
        this.circle()
      }, delay)
    }
  }
  start() {
    this.circle()
  }
}

22. 模拟 instanceof

const InstanceOf = (left, right) => {
  if (typeof left !== 'object' || left === null) return false

  let proto = Object.getPrototypeOf(left);
  while (proto && proto !== right) {
    if (proto === right.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
  return false
}

23. 解析 URL

let url = "http://www.baidu.com?name=elephant&age=25&sex=male&num=100"

使用正则表达式

const queryURLParams = (url) => {
  const result = {}
  const pattern = /(\w+)=(\w+)/ig
  const queryArray = url.match(pattern)
  queryArray.forEach(query => {
    const [key, val] = query.split("=")
    result[key] = val
  })
  return result
}

使用 a 标签

node 环境不可用

const queryURLParams = (url) => {
  const result = {}
  const a = document.createElement("a")
  a.href = url
  const queryArray = a.search.replace('?', '').split('&')
  queryArray.forEach(query => {
    const [key,val] = query.split('=')
    result[key] = val
  })
  return result
}

使用 URLSearchParams

const queryURLParams = (url) => {
  const result = {}
  const queryUrl = url.split("?")[1]
  const searchParams = new URLSearchParams(queryUrl)
  for (let [key, val] of searchParams.entries()) {
    result[key] = val
  }
  return result
}