2023精选50道JavaScript高频手写题,助力金三银四

781 阅读15分钟

前言

2022的冬天已经过去, 2023就业形势和招聘数量有所缓和(希望是),但是企业的招聘要求依然很高, 近年来很多企业都会要求面试选手在线Code,慢慢的手写一些笔试题会变为常态。在金三银四来临之前,我为大家准备了50道精选JavaScript手写题,希望可以帮助到一些面试者。刷题不单单是为了面试做准备,更多的是希望通过手写题提高个人编程思想,培养思维逻辑,能够深入了解到一些函数的内部原理。

祝大家都能拿到理想的Offer,2023冲冲冲。

目录导航

  1. 实现call方法
  2. 实现apply方法
  3. 实现bind方法
  4. 实现new操作符
  5. 实现instanceof操作符
  6. 实现typeof操作符
  7. 实现find方法
  8. 实现findIndex方法
  9. 实现filter方法
  10. 实现map方法
  11. 实现reduce方法
  12. 数组扁平化
  13. 数组去重
  14. 实现深拷贝
  15. 实现对象可迭代
  16. 实现防抖函数
  17. 实现节流函数
  18. 冒泡排序
  19. 选择排序
  20. 插入排序
  21. 快速排序
  22. 大数相加
  23. 洗牌算法
  24. 根据路径合并对象
  25. 数组转为嵌套对象
  26. 实现a==1&&a==2&&a==3
  27. url参数解析
  28. 版本号排序
  29. LRU缓存
  30. LRU缓存-双向链表优化
  31. 实现require函数
  32. 基于A+规范实现Promise
  33. 实现Promise.all
  34. 实现Promise.allSelled
  35. 实现Promise.race
  36. 实现Promise并发控制
  37. 实现Promise.retry
  38. 实现链表的基本操作LinkedList
  39. 实现EventEmitter
  40. DFS和BFS
  41. 获取对象的最大深度
  42. 转树结构数据
  43. 整数千分位加逗号
  44. 青蛙跳台阶
  45. 字符串公共子串
  46. 实现Object.create

01.实现call方法

MDN: # call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

Function.prototype.aCall = function (context, ...args) {
  // 上下文对象为undefined和null时指向window,否者强制转为Object的包装类型
  context = context === null || context === undefined ? window : Object(context);
  // 创建一个symbol保存调用call的函数
  const fn = Symbol();
  context[fn] = this;
  // 将上下文context作为this调用该函数
  const res = context[fn](...args);
  // 调用完毕删除引用并返回结果
  delete context[fn];
  return res;
};

02.实现apply方法

MDN: # apply 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。

Function.prototype.aApply = function (context, args) {
  // 上下文对象为undefined和null时指向window,否者强制转为Object的包装类型
  context = context === null || context === undefined ? window : Object(context);
  // 创建一个symbol保存调用call的函数
  const fn = Symbol();
  context[fn] = this;
  // 将上下文context作为this调用该函数
  const res = context[fn](...args);
  // 调用完毕删除引用并返回结果
  delete context[fn];
  return res;
};

03.实现bind方法

MDN: # bind 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

Function.prototype.aBind = function (context, ...args) {
  // 上下文对象为undefined和null时指向window,否者强制转为Object的包装类型
  context = context === null || context === undefined ? window : Object(context);
  // 创建一个symbol保存调用call的函数
  const fn = Symbol();
  context[fn] = this;
  // 创建一个新的函数并返回这个新函数
  const _bind = function(..._args){
    // 将上下文context作为this调用该函数
    const res = context[fn](...args,..._args);
    // 调用完毕删除引用并返回结果
    delete context[fn];
    return res;
  }
  return _bind
};

04.实现new运算符

MDN: # new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

function _new(fn, ...args) {
  // 基于构造函数的原型对象创建一个新对象
  const newObj = Object.create(fn.prototype);
  // 使用新对象作为this调用构造函数
  const res = fn.apply(newObj, args);
  return res instanceof Object ? res : newObj;
}

05.实现instanceof运算符

MDN: # instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

function _instanceof(A, B) {
  let a = A.__proto__;
  let b = B.prototype;

  while (a !== b) {
    if (a === null) return false;
    a = a.__proto__;
  }
  return true;
}

06.实现typeof运算符

MDN: # typeof 运算符返回一个字符串,表示操作数的类型。

function _typeof(value) {
  let types = [Date, RegExp, Map, Set, WeakMap];
  if (types.some(type => value instanceof type) || value === null) return 'object';
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

07.实现find函数

MDN: # find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

Array.prototype.myFind = function (fn) {
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) return this[i];
  }
};

08.实现findIndex函数

MDN: # findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 -1。

Array.prototype.myFind = function (fn) {
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) return i;
  }
  return -1;
};

09.实现filter函数

MDN: # filter() 方法创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。

Array.prototype.myFilter = function (fn) {
  const res = [];
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) res.push(this[i]);
  }
  return res;
};

10.实现map函数

MDN: # map() 方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。

Array.prototype.myMap = function (fn) {
  const res = [];
  for (let i = 0; i < this.length; i++) {
    res.push(fn(this[i], i, this));
  }
  return res;
};

11.实现reduce函数

MDN: # reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

Array.prototype.myMap = function (fn) {
  const res = [];
  for (let i = 0; i < this.length; i++) {
    res.push(fn(this[i], i, this));
  }
  return res;
};

12.数组扁平化

MDN: # 深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

// 1. 使用原声flat方法
array.flat(infinity);

// 2. 手动递归扁平化
Array.prototype.myFlat = function () {
    return this.reduce((prev,curr)=>{
        return prev.concat(Array.isArray(curr) ? curr.myFlat() : curr );
    })
};

13.数组去重

数组去重是将数组中重复的元素进行过滤的过程

const array = [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5];

// 1. 通过Es6的 Set方法
Array.prototype.repeatSet = function(){
    return [...new Set(this)];
}

// 2. 通过循环判断是否已存在
Array.prototype.repeatReduce = function(){
    return this.reduce((prev,curr)=>( prev.indexOf(curr) === -1 && prev.push(curr),prev),[])
}

14.深拷贝

MDN: 对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本

const obj = {
    a:1,
    b:false,
    c:[1,2,3],
    d:[{ k1:1,k2:2 }],
    e: new Set([1,2,3]);
    f: new Map([['k1','v1'],['k2','v2']]),
    g: new Date(),
    h: Symbol('symbol'),
    i: new RegExp(/\.?*/),
    j: function(){}
}
obj.k = obj; // 添加循环引用 

function deepClone(record,weakMap = new WeakMap){
    // 处理Symbol对象
    if(typeof record === 'symbol') return Symbol(record.description);
    // 非对象类型的值 直接返回
    if(!(record instanceof Object)) return record;
    // 函数无需拷贝 直接使用原函数
    if(typeof record === 'function') return record;
    // 使用weakMap防止循环引用重复拷贝
    if(weakMap.has(record)) return weakMap.get(record);
    // 处理几种特殊的引用类型数据
    const wClass = [Set,Map,WeakMap,Date,RegExp];
    for(const Cls of wClass){
        if(record instanceof Cls) return new Cls(record);
    }
    // 当原对象是普通对象或数组时
    const newObject = Array.isArray(record) ? [] :{};
    for(const key in record){
        if(Object.hasOwnProperty.call(record,key)){
            newObject[key] = deepClone(record[key],weakMap);
        }
    }
    weakMap.set(record,newObject);
    return newObject;
}

15.实现对象可迭代

MDN: #实现对象可迭代是指,能够通过for of 进行迭代对象,将对象的value值进行循环迭代。 本质是实现对象的(Symbol.iterator接口)


const info = {
    name:"rd",
    age:20,
    address:"上海",
    phone:'177xxxxxxxx'
}
// 实现Symbol.iterator即可
info[Symbol.iterator] = function(){
    const values = Object.values(this);
    let index = 0;
    return {
        next(){
            return { value: values[index] , done: ++index === values.length };
        }
    }
}

for(const val of info){
    console.log(val);
}


16.防抖

ludash: #防抖,就是指触发事件后,在 n 秒后只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数的执行时间

function debounce(fn, delay, options = { leading: false }) {
  // 初始化定时器
  let timer = null;
  // 控制第一次是否需要执行
  let { leading } = options;
  let invoker = false;

  const _debounce = function(...args) {
    // 判断第一次是否需要执行
    if (leading && !invoker) {
      fn.apply(this, args);
      invoker = true;
      return;
    }
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
  return _debounce;
}

17.节流

ludash: #节流是限制一个函数在一定时间内只能执行一次

function throttle(fn, delay, options = { leading: false }) {
  // 判断第一次是否需要执行
  const { leading } = options;
  // 上一次执行的时间 初始是否需要执行
  let prevTime = leading ? null : new Date();
  const _throttle = function(...args) {
    let nowTime = new Date();
    if (nowTime - prevTime >= delay) {
      fn.apply(this, args);
      prevTime = nowTime;
    }
  };

  return _throttle;
}

18.冒泡排序

# 排序算法之冒泡排序(图解

const array = new Array(50000).fill().map((_,i)=> i+1).sort(()=> Math.random() - .5);
function bobbleSort(array){
    for(let i = 0;i<array.length;i++){
        for(let j= 0;j<array.length- i- 1;j++){
            if(array[j] > array[j+1]){
                [array[j],array[j+1]] = [array[j+1],array[j]];
            }
        }
    }
    return array;
}

console.time();
console.log(bobbleSort(array));
console.timeEnd();

19.选择排序

# 排序算法之选择排序 (图解

const array = new Array(50000).fill().map((_,i)=> i+1).sort(()=> Math.random() - .5);

function selectionSort(array){
    for(let i = 0;i<array.length - 1;i++){
        let min = i; // 从起点开始 找更小的值的下标 找到则赋值给min
        for(let j= i+1;j<array.length;j++){
           if(array[j] < array[min]){
               min = j;
           }
        }
        // 一轮循环结束 找到最小值的小标 进行位置交换
        [array[i],array[min]] = [array[min],array[i]];
    }
    return array;
}

console.time();
console.log(selectionSort(array));
console.timeEnd();

20.插入排序

# 排序算法之插入排序(图解)

const array = new Array(50000).fill().map((_,i)=> i+1).sort(()=> Math.random() - .5);

function insertionSort(array){
    for(let i = 1;i<array.length;i++){
        let j = i;
        let temp = array[i];
        // 初始从下标为1的元素开始向前查找更大的元素,找到则直接交换位置
        while(array[j-1] > temp && j > 0){
            array[j] = array[--j]
        }
        // 一轮循环结束,将j位置开始下标对应的值
        array[j] = temp;
    }
    return array;
}

console.time();
console.log(insertionSort(array));
console.timeEnd();

21.快速排序

# 快速排序(Quicksort)的Javascript实现阮一峰

  1. 分组快排
const array = new Array(50000).fill().map((_,i)=> i+1).sort(()=> Math.random() - .5);

function quickSortGroup(array){
    if(array.length <=1) return array;
    // 设置一个基点用于比较进行分组 (选择数组第一位元素作为基点)
    const middleItem = array[0];
    
    const leftArray = [];
    const rightArray = [];
    
    for(let i = 1;i<array.length;i++){
        array[i] >= middleItem ? rightArray.push(array[i]) : leftArray.push(array[i]);
    }
    
    // 递归调用
    return quickSortGroup(leftArray).concat(middleItem,quickSortGroup(rightArray));
}
  1. 原地排序(优化了时间复杂度)
let array = new Array(100000)
  .fill()
  .map((_, i) => i + 1)
  .sort(() => Math.random() - 0.5);

function quickSort(array, l = 0, r = array.length - 1) {
  if (l >= r) return;
  // 左指针和右指针
  let left = l;
  let right = r;
  // 基点和基点元素
  let basic = left;
  let basicItem = array[basic];

  while (left < right) {
    // 尝试从右边找比基点小的元素
    while (left < right && array[right] > basicItem) {
      right--;
    }
    // 从右边找到了比基点小的元素 则交换和基点的位置
    if (left < right) {
      [array[basic], array[right], basic] = [array[right], basicItem, right];
      left++;
    }

    // 尝试从左边找比基点大的元素
    while (left < right && array[left] <= basicItem) {
      left++;
    }
    // 从左边找到了比基点大的元素
    if (left < right) {
      [array[basic], array[left], basic] = [array[left], basicItem, left];
      right--;
    }
  }

  quickSort(array, l, basic - 1);
  quickSort(array, basic + 1, r);
  return array;
}

console.time('快排');
quickSort(array);
console.timeEnd('快排');

22.实现大数相加

let a = '88888888888888888888888888';
let b = '333333333333388888888888888888888888888';

function computeMaxNumber(a, b) {
  let maxLength = Math.max(a.length, b.length);
  // 从前补位 将长度保持一致
  a = a.padStart(maxLength, '0');
  b = b.padStart(maxLength, '0');
  let res = '';
  let c = 0; // 进制
  // 从最后一位进行相加
  while (--maxLength >= 0) {
    const s = Number(a[maxLength]) + Number(b[maxLength]) + c;
    c = Math.floor(s / 10);
    res = (s % 10) + res;
  }
  if (c === 1) res = c + res;
  return res;
}

23.洗牌算法

  • 洗牌算法-将一副扑克牌尽可能的洗乱
// 一副扑克牌 尽可能的洗乱

let arr = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'jokers', 'Jokers'];

// 1. 随机排序
function shuffle1(arr) {
  return arr.sort(_ => Math.random() - 0.5);
}

// 2. 随机数
function shuffle2(arr) {
  let result = [];
  for (let i = arr.length - 1; i >= 0; i--) {
    // 生成0到 数组长度随机数
    const random = Math.round(Math.random() * i);
    result.push(arr.splice(random, 1)[0]);
  }
  return result;
}

24.根据路径合并对象

  • 对象中的key为路径,相同路径的值合并到一个对象内
const d1 = { 'a.b.c': 1, 'a.b.d': 2,'a.c':5 };
const d2 = { 'a.b.e': 3, 'a.b.f': 4 };
// 合并后的结果 { a: { b: { c: 1, d: 2, e: 3, f: 4 }, c: 5 } }

function mergeRecord(...args) {
  return args.reduce((prev, curr) => {
    Object.keys(curr).forEach(path => {
      const paths = path.split('.');
      let data = prev;
      paths.forEach((item, index) => {
        data = data[item] || (data[item] = index === paths.length - 1 ? curr[path] : {});
      });
    });
    return prev;
  }, {});
}

25.数组转为嵌套对象

  • 将数组中的值转为嵌套的对象
let arr = ['a', 'b', 'c', 'd'];
// 结果 { a:{ b:{ c:{ d:null } } } }

function arrayToObject(arr) {
  let data = null;
  return arr.reduce((prev, curr, index) => {
    data = data ? (data[curr] = index === arr.length - 1 ? null : {}) : (prev[curr] = {});
    return prev;
  }, {});
}

26.实现a==1&&a==2&&a==3

// 1. a是对象 实现valueOf toString方法即可(隐式转换)
let a = {
  value: 1,
  valueOf() {
    return a.value++;
  },
};

// 2. 是数组,隐式转化时,除了调用valueOf toString还会调用join方法
let b = [1, 2, 3];
b.join = Array.prototype.shift;
console.log(b == 1 && b == 2 && b == 3);

27.url参数解析

let search = '?id=856&name=%22%E7%87%83%E7%82%B9%22';
// 结果 { id: 856, name: }

// 使用URLSearchParams
function searchParams(search) {
  const params = new URLSearchParams(search);
  const result = {};
  for(let [key,value] of params.entries()){
      result[key] = value;
  }
  return result;
}

28.版本号排序

let arr = ['1.5.1', '1.04.9', '1.45.0', '6.0.0', '5.2'];
// 结果 ['1.04.9', '1.5.1', '1.45.0', '5.2', '6.0.0']

function versionSort(arr) {
  return arr.sort((a, b) => {
    let _a = a.split('.');
    let _b = b.split('.');
    let index = 0;
    while (index >= 0) {
      let s1 = _a[index];
      let s2 = _b[index];
      index++;
      // 前面的版本一致 跳过本次循环 比较后面的版本
      if (s1 === s2) continue;
      // 更短的版本在前面
      if (s1 === undefined || s2 === undefined) {
        return (s1.length = s2.length);
      }
      // 小版本在前面
      return s1 - s2;
    }
  });
}

29.LRU缓存

# LRU缓存机制,你想知道的这里都有

# KeepAlive 中的数据结构 LRU 缓存

class LRUCache {
  constructor(max) {
    this.map = new Map();
    this.max = max;
  }
  put(key, value) {
    // 判断是否已存在 先移除
    if (this.map.has(key)) {
      this.map.delete(key);
    }
    if (this.map.size >= this.max) {
      // 删除最后一个元素
      this.map.delete(this.map.keys().next().value);
    }
    return this.map.set(key, value);
  }
  get(key) {
    if (this.map.has(key)) {
      const value = this.map.get(key);
      this.map.delete(key);
      this.map.set(key, value);
      return value;
    } else {
      return -1;
    }
  }
}

30.LRU缓存优化(双向链表实现)

class Node {
  constructor(key, val) {
    this.key = key;
    this.val = val;
    this.prev = null; // 连接上一个节点
    this.next = null; // 连接下一个节点
  }
}
class LRUCache {
  constructor(max) {
    this.max = max;
    this.map = new Map(); // 初始化hash表
    this.head = new Node(); // 初始化头结点
    this.tail = new Node(); // 初始化尾节点
    this.head.next = this.tail; // 互相连接
    this.tail.prev = this.head;
  }
  /**
   * 添加元素
   */
  put(key, val) {
    // 创建节点
    let node = this.map.get(key);

    if (node === undefined) {
      // 创建Node节点
      node = new Node(key, val);
      if (this.map.size >= this.max) {
        this.removeNode();
      }
      // 加入hash表中
      this.map.set(key, node);
      // 添加到表头
      this.addToHead(node);
    } else {
      if (node.val !== val) node.val = val;
      // 移动到表头
      this.moveToHead(node);
    }
    return node.val;
  }
  /**
   * 获取节点内容
   */
  get(key) {
    let node = this.map.get(key);
    if (node === undefined) {
      return -1;
    } else {
      this.moveToHead(node);
      return node.val;
    }
  }
  /**
   * 删除后面的节点
   */
  removeNode() {
    this.map.delete(this.tail.prev.key); // 从hash表中移除
    // 断开连接
    this.tail.prev = this.tail.prev.prev;
    this.tail.prev.next = this.tail;
  }
  addToHead(node) {
    node.next = this.head.next;
    node.prev = this.head;
    node.next.prev = node;
    this.head.next = node;
  }
  moveToHead(node) {
    node.prev = node.next.prev;
    node.prev.next = node.next;
    this.addToHead(node);
  }
}

31.实现Node中require函数

const path = require('path');
const fs = require('fs');
const vm = require('vm');
class Module {
  constructor(id) {
    this.id = id; // 保存模块绝对路径
    this.exports = {};
  }
  load() {
    // 获取后缀名
    const ext = path.extname(this.id);
    Module._extensions[ext](this);
  }
}

Module._extensions = {
  '.js'(module) {
    // 读取文件内容
    const content = fs.readFileSync(module.id, 'utf-8');
    // 包装成函数
    const contentFn = Module._compiler[0] + content + Module._compiler[1];
    // 创建一个执行韩式
    const compilerFn = vm.runInThisContext(contentFn);
    const exports = module.exports;
    const _filename = module.id;
    const _dirname = path.dirname(module.id);
    compilerFn.call(exports, exports, module, myRequire, _filename, _dirname);
  },
  '.json'() {
    // 读取文件内容
    const content = fs.readFileSync(module.id, 'utf-8');
    module.exports = content;
  },
};

Module._compiler = ['(function(exports,module,require,_filename,_dirname){', '})'];

Module._getResolvePath = function(id) {
  // 补全路径
  const absPath = path.resolve(__dirname, id);
  // 判断文件是否存在
  const isExists = fs.existsSync(absPath);
  if (isExists) return absPath;
  // 尝试补全后缀信息
  let extensions = ['.js', '.json'];
  if (!isExists) {
    for (let ext of extensions) {
      if (fs.existsSync(absPath + ext)) {
        return absPath + ext;
      }
    }
  }
  throw new Error(`${id} is not defind`);
};

Module._cache = {};

function myRequire(id) {
  // 获取绝对路径
  const abs = Module._getResolvePath(id);
  // 读取缓存
  const cacheModule = Module._cache[abs];
  if (cacheModule) return cacheModule.exports;
  // 创建模块对象
  const module = new Module(abs);
  // 加载模块并进行缓存
  module.load();
  Module._cache[abs] = module;
  return module.exports;
}

32.基于A+规范实现Promise

A+规范官方

A+规范参考文章[中译]

/**
 * 基于Promise A+实现Promise
 * A+规范官方 https://promisesaplus.com/
 * A+规范参考文章[中译]: https://zhuanlan.zhihu.com/p/143204897
 */

const PROMISE_PENDING = 'pending';
const PROMISE_FULFILLED = 'fulfilled';
const PROMISE_REJECT = 'reject';

const isObject = x => Object.prototype.toString.call(x) === '[object Object]';
const isFunction = x => typeof x === 'function';

class MyPromise {
  constructor(executor) {
    this.state = PROMISE_PENDING;
    this.value = undefined;
    this.reson = undefined;
    this.onFulfilledFns = [];
    this.onRejectFns = [];
    executor(this.resolve.bind(this), this.reject.bind(this));
  }
  resolve(value) {
    if (this.state === PROMISE_PENDING) {
      this.state = PROMISE_FULFILLED;
      this.value = value;
      this.onFulfilledFns.forEach(fn => fn(this.value));
    }
  }
  reject(reason) {
    if (this.state === PROMISE_PENDING) {
      this.state = PROMISE_REJECT;
      this.reason = reason;
      this.onRejectFns.forEach(fn => fn(this.reason));
    }
  }
  resolvePromise(promise, x, resolve, reject) {
    // 2.3.1. 如果promise和x引用同一个对象,用一个TypeError作为原因来拒绝promise
    if (promise === x) throw new TypeError('Chaining cycle detected for promise');

    // 2.3.2. 如果x是一个promise,采用它的状态:[3.4]
    if (x instanceof MyPromise) {
      x.then(
        y => {
          this.resolvePromise(promise, y, resolve, reject);
        },
        err => {
          reject(err);
        }
      );
    }

    // 2.3.3. 否则,如果x是一个对象或函数
    let isCalled = false;
    // 用于解决then函数的resolvePromise
    const resolvePromise = y => {
      if (isCalled) return;
      isCalled = true;
      this.resolvePromise(promise, y, resolve, reject);
    };
    // 用于拒绝then函数的rejectPromise
    const rejectPromise = err => {
      if (isCalled) return;
      isCalled = true;
      reject(err);
    };
    // 2.3.3. 否则,如果x是一个对象或函数
    if (isFunction(x) || isObject(x)) {
      try {
        // 2.3.3.1. 让then成为x.then。
        const then = x.then;
        // 2.3.3.3. 如果then是一个函数,用x作为this调用它。then方法的参数为俩个回调函数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise
        if (isFunction(then)) {
          then.call(x, resolvePromise, rejectPromise);
        } else {
          // 2.3.3.4. 如果then不是一个函数,用x解决promise
          resolve(x);
        }
      } catch (err) {
        // 2.3.3.3.4. 如果调用then抛出了一个异常e
        // 2.3.3.4.1. 如果resolvePromise或rejectPromise已经被调用,忽略它
        // 2.3.3.4.2. 否则,用e作为原因拒绝promise
        if (isCalled) return;
        isCalled = true;
        reject(err);
      }
    } else {
      // 2.3.4. 如果x不是一个对象或函数,用x解决promise
      resolve(x);
    }
  }
  then(onFulfilled, onReject) {
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
    onReject = isFunction(onReject)
      ? onReject
      : err => {
          throw err;
        };

    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === PROMISE_FULFILLED) {
        queueMicrotask(() => {
          try {
            const x = onFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }

      if (this.state === PROMISE_REJECT) {
        queueMicrotask(() => {
          try {
            const x = onReject(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }

      if (this.state === PROMISE_PENDING) {
        this.onFulfilledFns.push(() => {
          queueMicrotask(() => {
            try {
              const x = onFulfilled(this.value);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
        this.onRejectFns.push(() => {
          queueMicrotask(() => {
            try {
              const x = onReject(this.reason);
              this.resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });

    return promise2;
  }
  catch(onCatch) {
    return this.then(undefined, onCatch);
  }
  static resolve(value) {
    // 如果value是promise类型 则直接返回
    if (value instanceof MyPromise) return value;

    return new MyPromise((resolve, reject) => {
      // 可能是thenable对象
      if (value && isFunction(value.then)) {
        queueMicrotask(() => {
          try {
            value.then(resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else {
        resolve(value);
      }
    });
  }
  static reject(reason) {
    return new MyPromise((_, reject) => {
      reject(reason);
    });
  }
  finally(onFinally) {
    return this.then(
      value => {
        return MyPromise.resolve(onFinally()).then(() => value);
      },
      err => {
        return MyPromise.reject(onFinally()).then(() => {
          throw err;
        });
      }
    );
  }
}
/**
 * 测试Promise程序
 * yarn add promises-aplus-tests
 * npx promises-aplus-tests promise.js(当前文件名)
 */

MyPromise.defer = MyPromise.deferred = function() {
  let dfd = {};
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};
module.exports = MyPromise;

33.实现Promise.all函数

MDN: # Promise.all() 方法接收一个iterator类型,并且返回一个Promise实例,当输入的所有的结果都支持成功时,返回成功结果的数组,有一个执行失败则返回那个执行失败的结果。

Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    // 计数器用于判断promise使用全部执行完毕
    let index = 0;
    const result = [];
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(
        res => {
          // 将解决后的promise放入成功结果的集合
          result[i] = res;
          // 全部执行完毕则返回成功结果的集合
          if (++index === promises.length) {
            resolve(result);
          }
        },
        err => {
          // 有一个执行失败则返回失败
          reject(err);
        }
      );
    }
  });
};

34.实现Promise AllSettled函数

MDN: # Promise.allSettled()  方法以 promise 组成的可迭代对象作为输入,并且返回一个Promise实例。当输入的所有 promise 都已敲定时(包括传递空的可迭代类型),返回的 promise 将兑现,并带有描述每个 promsie 结果的对象数组。

Promise.myAllSelled = function(promises) {
  return new Promise((resolve, reject) => {
    // 计数器用于判断promise使用全部执行完毕
    let index = 0;
    const result = [];
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i])
        .then(
          res => {
            // 成功对应的status为fulfilled
            result[i] = { value: res, status: 'fulfilled' };
          },
          err => {
            // 失败对应的status为reject
            result[i] = { reason: err, status: 'reject' };
          }
        )
        .finally(() => {
          if (++index === promises.length) {
            // 全部执行完毕则返回包含成功和失败的结果集合
            resolve(result);
          }
        });
    }
  });
};

35.实现Promise race函数

MDN: # Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。

Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      // 返回第一个执行成功或失败的结果
      Promise.resolve(promises[i]).then(resolve, reject);
    }
  });
};

36.Promise显示并发数量(PromisePool)

参考文章: # JavaScript 中如何实现并发控制?

/**
 * @param {number} poolLimt 支持的最大并发数量
 * @param {[value,delay ][]} promises 待解决的promise列表
 * @param {Function} fn promise的解决程序
 */
async function asyncPool(poolLimt, promises, fn) {
  const result = []; // 存储所有的异步任务
  const delayPromises = []; // 存储正在执行的异步任务

  for (let [value, dealy] of promises) {
    // 创建一个新的异步任务
    const res = Promise.resolve().then(() => fn(value, dealy));
    result.push(res); // 保存异步任务

    // 当poolLimit值小于或等于总任务个数时,进行并发控制
    if (promises.length >= poolLimt) {
      // 当任务完成后,从正在执行的任务数组中移除已完成的任务
      const e = res.then(() => delayPromises.splice(delayPromises.indexOf(e), 1));
      delayPromises.push(e);
      // 从待执行的promise列表中执行
      if (delayPromises.length >= poolLimt) {
        await Promise.race(delayPromises); // 等待较快的任务执行完成
      }
    }
  }
  return Promise.all(result);
}

// fn是一个promise的解决程序,他返回一个delay毫秒成功解决的promise
function fn(result, delay) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(result);
      resolve(result);
    }, delay);
  });
}

asyncPool(
  2,
  [
    ['dalay 1000', 1000],
    ['dalay 5000', 5000],
    ['dalay 3000', 3000],
    ['dalay 2000', 2000],
  ],
  fn
).then(res => {
  console.log(res);
});

37.实现Promise.retry函数

  • 实现 retry函数 函数调用失败后可以自动重试,直到失败的次数耗完则抛出错误
// delayTask函数返回一个promise 他有90%的几率会执行失败
const delayTask = () => {
  return new Promise((resolve, reject) => {
    const random = Math.random();
    random > 0.9 ? resolve(`成功了:${random}`) : reject(`失败了:${random}`);
  });
};

// 实现 retry函数 函数调用失败后可以自动重试,直到失败的次数耗完则抛出错误

async function retry(task, count) {
  try {
    const res = await task();
    return res;
  } catch (err) {
    if (count > 0) {
      console.log('正常尝试,剩余次数:', count);
      return await retry(task, --count);
    } else {
      console.log('次数用完了', err);
      throw err;
    }
  }
}

retry(delayTask, 5)
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  });

38.实现LinkedList

  • 实现链表的基本操作
class Node {
  constructor(val) {
    this.val = val;
    this.next = null;
  }
}

class LinkedList {
  constructor() {
    this.head = new Node(); // 头结点
    this.last = null;
    this._size = 0; // 链表长度
  }
  // 添加节点
  append(val) {
    let head = this.head;
    while (head.next) {
      head = head.next;
    }
    const node = new Node(val);
    head.next = node;
    this._size += 1;
    return node;
  }
  // 添加到指定位置
  insert(index, val) {
    if (index < 0 || index >= this._size) return false; // 超过边界

    let head = this.head;
    while (index-- >= 0) {
      if (head.next) head = head.next;
      else break;
    }
    const node = new Node(val);
    node.next = head.next;
    head.next = node;
    this._size += 1;
  }
  // 返回链表长度
  size() {
    return this._size;
  }
  // 是否为空链表
  isEmpty() {
    return this._size === 0;
  }
  // 删除某个位置的节点
  removeAt(index) {
    if (index < 0 || index >= this._size) return false; // 超过边界
    let [prev, head] = [null, this.head];
    while (index-- >= 0) {
      if (head.next) {
        [prev, head] = [head, head.next];
      }
    }
    this._size -= 1;
    prev.next = head.next;
  }
}

39.实现EventEmitter

  • 实现发布订阅模式
/**
 * 实现EventEmitter
 * on方法添加一个订阅事件
 * emit触发该事件
 * off卸载对应事件
 * once添加一个订阅事件 只允许触发一次
 */
class EventEmitter {
  constructor() {
    this.events = {};
  }
  on(name, cb) {
    if (typeof cb !== 'function') {
      console.log(`${cb} is not a function`);
    }
    (this.events[name] || (this.events[name] = [])).push(cb);
  }
  emit(name, ...args) {
    const cbs = this.events[name] || [];
    cbs.forEach(cb => cb.apply(this, args));
  }
  off(name, cb) {
    let cbs = this.events[name] || [];
    this.events[name] = cbs.filter(fn => {
      return fn !== cb && fn !== cb.link;
    });
    if (this.events[name].length === 0) delete this.events[name];
  }
  once(name, cb) {
    const callback = (...args) => {
      this.off(name, callback);
      cb.apply(this, args);
    };
    callback.link = cb;
    this.on(name, callback);
  }
}

const mitt = new EventEmitter();

40.DFS和BFS

# 深度优先和广度优先的本质区别-图解

// 测试数据
let arr = [
  {
    name: 'a',
    children: [
      {
        name: 'aa',
        children: [ { name: 'aaa', children: [ { name: 'aaaa', } ] } ],
      },
      {
        name: 'ab',
        children: [ { name: 'aba' } ],
      },
    ],
  },
  {
    name: 'b',
    children: [ { name: 'ba' }, { name: 'bb' } ],
  },
];

// 深度优先遍历 (递归)
function DFS(arr) {
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    console.log(item.name);
    // 遍历的过程中发现子集合则进行递归遍历
    if (item?.children?.length) {
      DFS(item.children);
    }
  }
}
DFS(arr);

// 广度优先遍历 (栈)
function BFS(arr) {
  let array = [...arr];
  while (array.length) {
    // 取出栈顶的元素 并继续往栈中添加这个元素的子集合
    const item = array.shift();
    console.log(item.name);
    array.push(...(item?.children || []));
  }
}
BFS(arr);

41.获取对象的最大层级

function deep(obj) {
  let max = 0;
  for (let key in obj) {
    max = Math.max(max, deepCount(obj[key]));
  }
  return max;
}

function deepCount(obj, count = 0) {
  count += 1; // 每次进入一层则count+1
  let max = count;
  // 如果达到尽头 则返回max
  if (!isObject(obj)) return max;
  
  for (let key in obj) {
    max = Math.max(max, deepCount(obj[key], count));
  }
  return max;
}

42.转树结构数据

const array = [
  { id: 1, pid: 0, name: 'body' },
  { id: 2, pid: 1, name: 'body-children1' },
  { id: 3, pid: 1, name: 'body-children2' },
  { id: 4, pid: 2, name: 'div' },
];

// 1. 使用reduce
function renderTreeeduce(array) {
  return array.reduce((prev, curr) => {
    // 找出当前节点的父节点
    let parent = array.find(item => item.id === curr.pid);
    // 父节点存在则往父节点中进行添加当前的子节点
    if (parent) (parent.children || (parent.children = [])).push(curr);
    else prev.push(curr); // 不存在父节点则表示是最外层的节点
    return prev;
  }, []);
}


// 2. filter
function renderTreeFilter(array, id = 0) {
  return array.filter(item => {
    if (item.pid === id) {
      item.children = renderTreeFilter(array, item.id);
      return true;
    }
  });
}

43.整数千分位加逗号

将1000000 转为 1,000,000

// 1000000 => 1,000,000

function toThousands(num) {
  num = num.toString();
  let result = '';
  while (num.length > 3) {
    result = ',' + num.substring(num.length - 3) + result;
    num = num.substring(0, num.length - 3);
  }
  result = num + result;
  return result;
}

44.青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 `n` 级的台阶总共有多少种跳法。

var numWays = function(n) {
  // 0层和1层只有1种结果 2层有1和2两种结果
  if (n <= 2) return n === 0 ? 1 : n;
  let a1 = 1; // 上一层最多跳法
  let a2 = 2; // 当前层最多跳法
  for (let i = 3; i <= n; i++) {
    // 当前的层可能的跳法是上两层跳法的总和
    [a1, a2] = [a2, a1 + a2];
  }
  return a2;
};

45.字符串最长公共子串

var longestCommonPrefix = function(strs) {
  let item = strs[0]; // 取第一个元素作为对比项
  let res = ''; // 保存公共子串的结果
  // 将剩下的元素和第一个元素进行对比 
  for (let i = 0; i < item.length; i++) {
    for (let j = 1; j < strs.length; j++) {
      if (item[i] !== strs[j][i]) return res;
    }
    res += item[i];
  }
  return res;
};

46.实现Object.create

Object.prototype.myCreate = function(obj){
    // 创建一个构造函数
    function C(){};
    // 将函数的原型指向传入的对象obj
    C.prototype = obj;
    // 返回由构造函数生成的对象
    return new C();
}