55道JS 手写题--巩固你的基础⛽️

735 阅读5分钟

1. 实现一个简易的 JSON.stringify() 和 JSON.parse()

JSON.stringify() 方法将 JavaScript 对象转换为字符串。

本质上是将其他类型的数据转换为字符串类型

  • undefined、任意的函数以及 symbolJSON.stringify() 作为单独的值进行序列化时都会返回 undefined

  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。

function stringfy(value) {
  // 获取数据类型
  let type = typeof value

  function getValues(value) {
    if (type === 'undefined' || type === 'symbol' || type === 'function') {
      return undefined
    }
    if (type === 'number' || type === 'boolean') {
      return `${value}`
    }
    if (type === 'string') {
      return `"${value}"`
    }
  }

  return getValues(value)
}
  • JSON.stringify() 将会正常序列化 Date 的值。实际上 Date 对象自己部署了 toJSON() 方法(同Date.toISOString()),因此 Date 对象会被当做字符串处理。
function stringfy(value) {
  // 获取数据类型
  let type = typeof value

  function getValues(value) {
    if (type === 'undefined' || type === 'symbol' || type === 'function') {
      return undefined
    }
    if (type === 'number' || type === 'boolean') {
      return `${value}`
    }
    if (type === 'string') {
      return `"${value}"`
    }
  }

  if (type === 'object') {
    if (!value) {
      return `${value}`
    }
    if (value instanceof Date) {
      return `"${new Date(value).toISOString()}"`
    }
    // 数据类型
    if (value instanceof Array) {
      return `[${value.map(stringfy)}]`
    } else {
      // 对象类型
      return (
        '{' + Object.keys(value).map(key => {
          let result = stringfy(value[key])
          if (result === undefined) {
            return undefined
          }
          return `"${key}":${result}`
        }).filter(item => item !== undefined) + '}'
      )
    }
  }

  return getValues(value)
}

JSON.parse()

function parse(value) {
  return eval('(' + value + ')')
}

function parse(value) {
  return new Function('return' + value)()
}

2. 封装原生的 Ajax 为 Promise 形式

function ajax(method, url) {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();
    request.open(method, url);
    request.onreadystatechange = () => {
      if (request.readyState === 4) {
        if (request.status === 200 || request.status === 304) {
          resolve(request.response)
        } else {
          reject(request)
        }
      }
    }
    request.send()
  })
}

测试

ajax('get', 'https://netease-cloud-music-api-chi-sooty.vercel.app/comment/music?id=186016&limit=1').then(res => {
  console.log(res)
})

3. 字符串模板

  1. 利用正则表达式,判断模板里是否有模板字符串
  2. 查找当前模板里第一个模板字符串的字段
  3. 将第一个模板字符串渲染
  4. 递归的渲染并返回渲染后的结构
  5. 知道没有匹配到模板字符串返回
function render(template, data) {
  const reg = /\{\{(\w+)\}\}/;
  if (reg.test(template)) {
    const name = reg.exec(template)[1];
    template = template.replace(reg, data[name])
    return render(template, data)
  }
  return template;
}

测试

let template = '今天是{{year}}年{{month}}月{{day}}日';
let person = {
  year: 2022,
  month: 02,
  day: 22
}

console.log(render(template, person))

4. 解析 URL 参数为对象

  1. 将 ? 后面的字符串取出来
  2. 将字符串以 & 分割后存到数组中
  3. 遍历分割之后的数组,将 params 存到对象中
function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]
  const paramsArr = paramsStr.split('&');
  let paramsObj = {}
  paramsArr.forEach(param => {
    let [key, value] = param.split('=')
    paramsObj[key] = value
  })
  return paramsObj
}

以上都是正常情况,但我们需要处理一些非正常情况。

  1. 处理没有 value 的参数
paramsArr.forEach(param => {
  if (/=/.test(param)) {
    let [key, value] = param.split('=')
    paramsObj[key] = value
  } else {
    paramsObj[param] = ''
  }
})
  1. 如果 key 有多个 value 值
paramsArr.forEach(param => {
  if (/=/.test(param)) {
    let [key, value] = param.split('=')

    if (paramsObj.hasOwnProperty(key)) {
      paramsObj[key] = [].concat(paramsObj[key], value)
    } else {
      paramsObj[key] = value
    }
  } else {
    paramsObj[param] = ''
  }
})
  1. 解码,判断是否转为数字
function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]
  const paramsArr = paramsStr.split('&');
  let paramsObj = {}
  paramsArr.forEach(param => {
    if (/=/.test(param)) {
      let [key, value] = param.split('=')
      value = decodeURIComponent(value)
      value = /^\d+$/.test(value) ? parseFloat(value) : value

      if (paramsObj.hasOwnProperty(key)) {
        paramsObj[key] = [].concat(paramsObj[key], value)
      } else {
        paramsObj[key] = value
      }
    } else {
      paramsObj[param] = ''
    }
  })
  return paramsObj
}

5. 正则实现 trim()

挂载在字符串的原型链上

String.prototype.trim = function() {
  return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}

使用单独的函数写法

function trim(string) {
  return string.replace(/^\s+|\s+$/g, '')
}

6. 驼峰命名

使用正则匹配 - ,切割 -,获取后半部分, 转化为大写开头

function humpNamed(s) {
  return s.replace(/-\w/g, function (x) {
    return x.slice(1).toUpperCase()
  })
}

7. 千位分隔符

  1. 保留小数点后三位
  2. 获取整数部分
  3. 使用正则,每隔三位使用“,”进行分割
function parseToMoney(num) {
  num = parseFloat(num.toFixed(3))
  let [integer, decimal] = String.prototype.split.call(num, '.')
  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,')
  return integer + (decimal ? `.${decimal}` : '')
}

8. 重复执行函数 repeatFunc()

使用 for 循环重复执行,使用 setTimeout 间隔执行

function repeatFunc(func, times, wait) {
  return function () {
    let args = arguments
    let handle = function (i) {
      setTimeout(() => {
        func.apply(null, args)
      }, wait * i)
    }
    for (let i = 0; i < times; i++) {
      handle(i)
    }
  }
}

测试

repeatFunc(console.log, 4, 1000)('hello world')

9. sleep 函数

通过 Promise + async + await 的形式

function sleepFunc(func, times) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(func)
    }, times);
  })
}

测试

let sayHello = () => {
  console.log('say hello')
}

async function autoPlay() {
  await sleepFunc(sayHello(), 1000)
  await sleepFunc(sayHello(), 2000)
}

autoPlay()

10. 深拷贝

  1. 非对象返回自身
  2. 使用 Map 进行循环检测
  3. 每次都添加未有的对象
  4. 循环遍历拷贝
function isObject(x) {
  return Object.prototype.toString.call(x) === '[object Object]';
}

function cloneDeep(obj, hash = new Map()) {
  if (!isObject(obj)) return obj;
  if (hash.has(obj)) return hash.get(obj);
  let target = Array.isArray(obj) ? [] : {};
  hash.set(obj, target);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (isObject(obj[key])) {
        target[key] = cloneDeep(obj[key], hash);
      } else {
        target[key] = obj[key];
      }
    }
  }
  return target;
}

11. Object.assign()

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象(这个操作是浅拷贝)

Object.assign2 = function (target, ...source) {
  if (target === null) {
    throw new TypeError('Cannot convert undefined or null to object')
  }
  let ret = Object(target)
  source.forEach(function (obj) {
    if (obj !== null) {
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          ret[key] = obj[key]
        }
      }
    }
  })
  return ret
}

12. 模拟Object.create

Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__

  1. 新声明一个函数
  2. 将函数的原型指向obj
  3. 返回这个函数的实例化对象
function create(proto) {
  function F() { }
  F.prototype = proto
  return new F()
}

13. 实现 new

  1. 创建一个新对象
  2. 这个新对象的 __proto__ 属性指向原函数的 prototype 属性。(即继承原函数的原型)
  3. 将这个新对象绑定到此函数的 this 上
  4. 如果这个函数没有返回其他对象,返回新对象
function create(Con, ...args) {
  let obj = {}
  obj.__proto__ = Con.prototype
  let result = Con.apply(obj, args)
  return result instanceof Object ? result : obj
}

14. Instanceof

判断该对象的原型链中是否可以找到该构造类型的 prototype 类型

function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left)
  while (true) {
    if (proto === null) return false
    if (proto === right.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
}

15. ES5 寄生组合继承

function Father() {}

function Son() {
  Father.call(this)
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

16. compose

在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x)。

function compose() {
  let fns = [].slice.call(arguments)
  return function (initalArg) {
    let res = initalArg
    for (let i = 0; i < fns.length; i++) {
      res = fns[i](res)
    }
    return res
  }
}

测试

let add = (x) => x + 1
let mul = (x) => x * 3
let div = (x) => x / 2

let result = compose(add, mul, div)
console.log(result(0))

17. EventEmitter(发布订阅-观察者)模式实现

function EventEmitter() {
  this.events = new Map();
};

// once 参数表示是否只是触发一次
const wrapCallback = (fn, once = false) => ({ callback: fn, once });

EventEmitter.prototype.addListener = function (type, fn, once = false) {
  let handler = this.events.get(type);
  if (!handler) {
    // 为 type 事件绑定回调
    this.events.set(type, wrapCallback(fn, once));
  } else if (handler && typeof handler.callback === 'function') {
    // 目前 type 事件只有一个回调
    this.events.set(type, [handler, wrapCallback(fn, once)]);
  } else {
    // 目前 type 事件回调数 >= 2
    handler.push(wrapCallback(fn, once));
  }
};

EventEmitter.prototype.off = function (type, listener) {
  let handler = this.events.get(type);
  if (!handler) return;
  if (!Array.isArray(handler)) {
    if (handler.callback === listener.callback) this.events.delete(type);
    else return;
  }
  for (let i = 0; i < handler.length; i++) {
    let item = handler[i];
    if (item.callback === listener) {
      // 删除该回调,注意数组塌陷的问题,即后面的元素会往前挪一位。i 要 -- 
      handler.splice(i, 1);
      i--;
      if (handler.length === 1) {
        // 长度为 1 就不用数组存了
        this.events.set(type, handler[0]);
      }
    }
  }
};

EventEmitter.prototype.once = function (type, fn) {
  this.addListener(type, fn, true);
}

EventEmitter.prototype.emit = function (type, ...args) {
  let handler = this.events.get(type);
  if (!handler) return;
  if (Array.isArray(handler)) {
    // 遍历列表,执行回调
    handler.map(item => {
      item.callback.apply(this, args);
      // 标记的 once: true 的项直接移除
      if (item.once) this.off(type, item);
    })
  } else {
    // 只有一个回调则直接执行
    handler.callback.apply(this, args);
  }
  return true;
};

EventEmitter.prototype.removeAllListener = function (type) {
  let handler = this.events.get(type);
  if (!handler) return;
  else this.events.delete(type);
};

18. debounce(防抖)

function debounce(fn, delay) {
  let timer = null
  return function (...args) {
    let context = this
    if (timer) clearTimeout(timer)
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

19. throttle(节流)

function throttle(fn, delay) {
  let flag = true
  let timer = null
  return function (...args) {
    let context = this
    if (!flag) return
    flag = false
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(context, args)
      flag = true
    }, delay);
  }
}

20. 数组扁平化

测试用例

const arr = [1, [2, [3, [4, 5]]], 6]; // => [1, 2, 3, 4, 5, 6]

递归 + for

function flatten(arr) {
  let result = []

  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]))
    } else {
      result = result.concat(arr[i])
    }
  }
  return result
}

控制层数

function flatten(arr, k) {
  if (k === 0) {
    return arr;
  }
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i], k - 1));
    } else {
      result = result.concat(arr[i]);
    }
  }
  return result;
}

flat()

const res1 = arr.flat(Infinity)

正则

const res2 = JSON.stringify(arr).replace(/[\[|\]]/g, '').split(',').map(e => parseInt(e))

const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/[\[|\]]/g, '') + ']')

reduce

const flatten = arr => {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
  }, [])
}

21. 数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];

Set

const unique1 = Array.from(new Set(arr))

两层for循环 + splice

const unique2 = arr => {
  let len = arr.length;
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1);
        len--;
        j--;
      }
    }
  }
  return arr;
}

indexOf

const unique3 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) {
      res.push(arr[i]);
    }
  }
  return res;
}

include

const unique4 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) {
      res.push(arr[i]);
    }
  }
  return res;
}

filter

const unique5 = arr => {
  return arr.filter((item, index) => {
    return arr.indexOf(item) === index;
  });
}

Map

const unique6 = arr => {
  const map = new Map();
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!map.has(arr[i])) {
      map.set(arr[i], true)
      res.push(arr[i]);
    }
  }
  return res;
}

22. Array.prototype.filter()

Array.prototype.filter = function (callback) {
  console.log('this is my filter')
  if (this === undefined) {
    throw new TypeError('this is null or not undefined')
  }
  if (typeof callback !== "function") {
    throw new TypeError(callback + 'is not a function')
  }
  const o = Object(this);
  const A = [];
  const len = o.length >>> 0;
  let ALen = 0;

  for (let i = 0; i < len; i++) {
    if (i in o) {
      if (callback.call(null, o[i], i, o)) {
        A[ALen++] = o[i];
      }
    }
  }
  return A;
};

23. Array.prototype.map()

Array.prototype.map = function (callback) {
  console.log('this is my map')
  if (this === undefined) {
    throw new TypeError('this is null or not undefined')
  }
  if (typeof callback !== "function") {
    throw new TypeError(callback + 'is not a function')
  }
  const o = Object(this);
  const A = new Array(len);
  const len = o.length >>> 0;

  for (let i = 0; i < len; i++) {
    if (i in o) {
      let mappedValue = callback.call(null, o[i], i, o);
      A[i] = mappedValue;
    }
  }
  return A;
}

24. Array.prototype.forEach()

Array.prototype.forEach = function (callback) {
  console.log('this is my forEach')
  if (this === undefined) {
    throw new TypeError('this is null or not undefined')
  }
  if (typeof callback !== "function") {
    throw new TypeError(callback + 'is not a function')
  }
  const o = Object(this);
  const len = o.length >>> 0;

  for (let i = 0; i < len; i++) {
    if (i in o) {
      callback.call(null, o[i], i);
    }
  }
}

25. Array.prototype.reduce()

Array.prototype.reduce = function (callback, initialValue) {
  console.log('this is my reduce')
  if (this === undefined) {
    throw new TypeError('this is null or not undefined')
  }
  if (typeof callback !== "function") {
    throw new TypeError(callback + 'is not a function')
  }
  const o = Object(this);
  const len = o.length >>> 0;
  const A = new Array(len);
  let k = 0;
  let accumulator = initialValue;

  // 外部没有提供初始值,将数组的第一个赋值为初始值
  if (accumulator === undefined) {
    for (; k < len; k++) {
      if (k in o) {
        accumulator = o[k];
        k++;
        break
      }
    }
  }

  for (; k < len; k++) {
    if (k in o) {
      accumulator = callback.call(null, accumulator, o[k], k, o);
    }
  }
  return accumulator;
};

26. Array.prototype.push()

Array.prototype.push = function (...items) {
  console.log('this is my push')
  if (this === undefined) {
    throw new TypeError('this is null or not undefined')
  }
  const o = Object(this);
  const len = o.length >>> 0;
  let argCount = items.length >>> 0;

  if (len + argCount > 2 ** 53 - 1) {
    throw new TypeError('The number of array is over the max value restricted!')
  }

  for (let i = 0; i < argCount; i++) {
    o[len + i] = items[i]
  }
  const newLength = len + argCount;
  o.length = newLength;
  return newLength;
};

27. Array.prototype.pop()

  1. 获取要删除的数组长度,如果长度为 0,返回 undefined
  2. len--,获取数组的最后一项
  3. 删除该值
  4. 将减去的数组长度作为新的数组长度
  5. 返回删除的值
Array.prototype.pop = function () {
  console.log('this is my pop')
  if (this === undefined) {
    throw new TypeError('this is null or not undefined')
  }
  const o = Object(this);
  let len = o.length >>> 0;

  if (len === 0) {
    o.length = 0
    return undefined;
  }
  len--
  let value = o[len]
  delete o[len]
  o.length = len
  return value
};

28. Function.prototype.call()

Function.prototype.call = function(context) {
  console.log('this is my call function')
  if (typeof this !== 'function') {
    throw new Error('Error')
  }
  context = context || window
  context.fn = this
  const args = Array.from(arguments).slice(1)
  const result = context.fn(...args)
  delete context.fn
  return result
}

29. Function.prototype.apply()

Function.prototype.myApply = function(context) {
  console.log('this is my apply')
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  let result;
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

30. Function.prototype.bind()

Function.prototype.myBind = function(context) {
  console.log('this is my bind')
  if (typeof this !== 'function') {
    throw new Error('Error')
  }

  let self = this
  let args = [].slice.call(arguments, 1)
  let bound = function() {
    let boundArgs = [].slice.call(arguments)
    return self.apply(context, args.concat(boundArgs))
  }
  return bound
}

30. 图片懒加载

为 img 标签统一自定义属性data-src='default.png',当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。

function lazyload() {
  const imgs = document.getElementsByTagName('img');
  const len = imgs.length;
  // 视口的高度
  const viewHeight = document.documentElement.clientHeight;
  // 滚动条高度
  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
  for (let i = 0; i < len; i++) {
    const offsetHeight = imgs[i].offsetTop;
    if (offsetHeight < viewHeight + scrollHeight) {
      const src = imgs[i].dataset.src;
      imgs[i].src = src;
    }
  }
}

// 可以使用节流优化一下
window.addEventListener('scroll', lazyload);

31. 渲染几万条数据不卡住页面

渲染大数据时,合理使用 createDocumentFragmentrequestAnimationFrame,将操作切分为一小段一小段执行。

setTimeout(() => {
  const total = 100000;
  const once = 20;
  const loopCount = Math.ceil(total / once);
  let countOfRender = 0;
  const ul = document.querySelector('ul');
 
  function add() {
    const fragment = document.createDocumentFragment();
    for(let i = 0; i < once; i++) {
      const li = document.createElement('li');
      li.innerText = Math.floor(Math.random() * total);
      fragment.appendChild(li);
    }
    ul.appendChild(fragment);
    countOfRender += 1;
    loop();
  }
  function loop() {
    if(countOfRender < loopCount) {
      window.requestAnimationFrame(add);
    }
  }
  loop();
}, 0)

32. 大数运算

  1. 取两个数字的最大长度
  2. 用0去补齐长度
  3. 使用 for 循环,如果有余数,则记录下来
function add(a, b) {
  let maxLength = Math.max(a.length, b.length);
  a = a.padStart(maxLength, 0);
  b = b.padStart(maxLength, 0);

  let curSum = 0;
  let remainder = 0;
  let sum = "";
  for (let i = maxLength - 1; i >= 0; i--) {
    curSum = parseInt(a[i]) + parseInt(b[i]) + remainder;
    remainder = Math.floor(curSum / 10);
    sum = curSum % 10 + sum;
  }
  if (remainder == 1) {
    sum = "1" + sum;
  }
  return sum;
}

33. 二分查找

二分查找的前提为:数组有序

function binarySearch(target, arr, start, end) {
  if (start > end) { return -1 }
  start = start || 0;
  end = end || arr.length - 1;

  let mid = parseInt(start + (end - start) / 2);
  if (target === arr[mid]) {
    return mid;
  } else if (target > arr[mid]) {
    return binarySearch(target, arr, mid + 1, end);
  } else {
    return binarySearch(target, arr, start, mid - 1);
  }
}
function binarySearch(target, arr) {
  let start = 0;
  let end = arr.length - 1;

  while (start <= end) {
    let mid = parseInt(start + (end - start) / 2);
    if (target === arr[mid]) {
      return mid;
    } else if (target > arr[mid]) {
      start = mid + 1;
    } else {
      end = mid - 1;
    }
  }
  return -1;
}

无序,使用快排分组,分好组再二分

function binarySearch(target, arr) {
  while (arr.length > 0) {
    let left = [];
    let right = [];
    let pivot = arr[0];
    for (let i = 1; i < arr.length; i++) {
      let item = arr[i];
      item > pivot ? right.push(item) : left.push(item);
    }

    if (target === pivot) {
      return true;
    } else if (target > pivot) {
      arr = right;
    } else {
      arr = left;
    }
  }
  return false;
}

34. 版本号排序

  1. 获取
const versionSort = (arr) => {
  const p = 100
  const maxLen = Math.max(
    ...arr.map((item) => item.split('.').length)
  );

  const gen = (arr) => {
    return arr.split('.').reduce((acc, value, index) => {
      return acc + (+value) * Math.pow(p, maxLen - index - 1);
    }, 0)
  };

  return arr.sort((a, b) => gen(a) > gen(b) ? 1 : -1)
};

35. 可拖拽的 DIV

let draggle = false;
let position = null;
let smallBox = document.getElementById('small');

smallBox.addEventListener('mousedown', function (e) {
  draggle = true
  position = [e.clientX, e.clientY]
});

document.addEventListener('mousemove', function (e) {
  if (draggle === false) return null
  const x = e.clientX
  const y = e.clientY
  const deltaX = x - position[0]
  const deltaY = y - position[1]
  const left = parseInt(smallBox.style.left || e.clientX)
  const top = parseInt(smallBox.style.top || e.clientY)
  smallBox.style.left = left + deltaX + 'px'
  smallBox.style.top = top + deltaY + 'px'
  position = [x, y]
});

document.addEventListener('mouseup', function (e) {
  draggle = false
});

36. 合并有序数组

不需要额外申请空间

let merge = function (nums1, nums2) {
  let len1 = nums1.length - 1;
  let len2 = nums2.length - 1;
  let len = nums1.length + nums2.length - 1;

  while (len2 >= 0) {
    if (len1 < 0) {
      nums1[len--] = nums2[len--];
      continue
    }
    nums1[len--] = nums1[len1] >= nums2[len2] ? nums1[len1--] : nums2[len2--];
  }
}
function mergeArray(arr1, arr2) {
  let index1 = 0;
  let index2 = 0;
  let arr = [];
  while (index1 < arr1.length && index2 < arr2.length) {
    if (arr1[index1] <= arr2[index2]) {
      arr.push(arr1.slice(index1)[0]);
      index1++;
    } else {
      arr.push(arr2.slice(index2)[0]);
      index2++;
    }
  }
  return arr.concat((index1 < arr1.length) ? arr1.slice(index1) : arr2.slice(index2));
}

37. 对 localStorage 的操作

对 localStorage 的添加,删除,获取 localStorage 的大小

let storageOperations = {
  storageSet: new Set(),
  setStorage: function (key, value, expires) {
    try {
      let obj = {
        time: +new Date(),
        value,
        expires
      }
      localStorage.setItem(key, JSON.stringify(obj));

      if (this.storageSet.has(key)) {
        this.storageSet.delete(key);
      }

    } catch (e) {
      console.log(e)
    }
  },
  removeStorage: function (key) {
    this.sortStorage();
    for (let oldkey of this.storageSet) {
      if (key === oldkey) {
        localStorage.removeItem(oldkey)
        this.storageSet.delete(oldkey)
        break;
      }
    }
  },
  getLocalStorageSize: function () {
    let sizeStore = 0;
    if (window.localStorage) {
      for (let item in window.localStorage) {
        if (window.localStorage.hasOwnProperty(item)) {
          sizeStore += window.localStorage.getItem(item).length
        }
      }
    }
    return sizeStore + 'KB'
  },
  getExpire: function () {
    let val = localStorage.getItem(key)
    if (!val) {
      return val;
    }
    val = JSON.parse(val);
    if (Date.now() - val.time > val.expires) {
      localStorage.removeItem(key)
      return null
    }
  },
  sortStorage: function () {
    let temp = []
    for (let key in localStorage) {
      if (localStorage.hasOwnProperty(key)) {
        temp.push({
          key,
          value: localStorage[key] ? JSON.parse(localStorage[key]) : ''
        })
      }
    }
    for (let outIndex = 0; outIndex < temp.length - 1; outIndex++) {
      let min = outIndex;
      for (let innerIndex = outIndex + 1; innerIndex < temp.length; innerIndex++) {
        if (temp[innerIndex].value.time < temp[min].value.time) {
          min = innerIndex
        }
      }
      if (min !== outIndex) {
        const swapTemp = temp[min]
        temp[min] = temp[outIndex]
        temp[outIndex] = swapTemp
      }
    }
    for (let i = 0, len = temp.length; i < len; i++) {
      this.storageSet.add(temp[i].key)
    }
  }
}

38. 数组随机排序

arr.sort(function () {
  return Math.random() > 0.5 ? -1 : 1
})

39. 生成随机数

function getRandom(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

40. groupBy

function groupBy(array, name) {
  let groups = {};
  array.forEach(function (item) {
    let group = JSON.stringify(item[name])
    groups[group] = groups[group] || []
    groups[group].push(item)
  })
  return Object.keys(groups).map(function (group) {
    return groups[group]
  })
}

测试

let list = [
  { "name": "John", "Average": 15, "High": 10, "DtmStamp": 1358226000000 },
  { "name": "Jane", "Average": 16, "High": 92, "DtmStamp": 1358226000000 },
  { "name": "Jane", "Average": 17, "High": 45, "DtmStamp": 1358226000000 },
  { "name": "John", "Average": 18, "High": 87, "DtmStamp": 1358226000000 },
  { "name": "Jane", "Average": 15, "High": 10, "DtmStamp": 1358226060000 },
  { "name": "John", "Average": 16, "High": 87, "DtmStamp": 1358226060000 },
  { "name": "John", "Average": 17, "High": 45, "DtmStamp": 1358226060000 },
  { "name": "Jane", "Average": 18, "High": 92, "DtmStamp": 1358226060000 }
];

console.log(groupBy(list, 'name'));

41. 根据 key 进行排序

const ObjectSort = function (array) {
  let newKey = Object.keys(array).sort()
  let newObj = {};
  for (let i = 0; i < newKey.length; i++) {
    newObj[newKey[i]] = array[newKey[i]];
  }
  return newObj;
}

42. 格式化时间

let format = (date) => {
  let fmt = 'yyyy-MM-dd hh:mm:ss'
  const o = {
    'M+': date.getMonth() + 1,
    'd+': date.getDate(),
    'h+': date.getHours(),
    'm+': date.getMinutes(),
    's+': date.getSeconds()
  }

  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, date.getFullYear())
  }
  for (let k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) {
      fmt = fmt.replace(RegExp.$1, o[k].toString().length === 1 ? '0' + o[k] : o[k])
    }
  }
  return fmt
}

43. 进制转换

function Conver(number, base = 2) {
  let rem, res = '', digits = '0123456789ABCDEF', stack = [];
  while (number) {
    rem = number % base;
    stack.push(rem);
    number = Math.floor(number / base);
  }

  while (stack.length) {
    res += digits[stack.pop()].toString()
  }

  return res;
}

44. 缓存函数memozition

function memoize(func, hashFunc) {
  let memoize = function(key) {
    let cache = memoize.cache
    let address = '' + (hashFunc ? hashFunc.apply(this, arguments) : key)
    if (Object.getOwnPropertyNames(cache).indexOf(address) === -1) {
      cache[address] = func.apply(this, arguments)
    }
    return cache[address]
  }
  memoize.cache = {}
  return memoize
}

45. 字符串中出现最多的字符

function getMaxStr(str) {
  let o = {};
  let maxNumber = 0;
  for (let i = 0; i < str.length; i++) {
    let char = str.charAt(i);
    if (o[char]) {
      o[char]++;
    } else {
      o[char] = 1;
    }
  }

  for (let key in o) {
    if (maxNumber < o[key]) {
      maxNumber = o[key];
    }
  }

  for (let key in o) {
    if (o[key] === maxNumber) {
      return [key, maxNumber]
    }
  }
}

46. 函数柯里化

function add() {
  let _args = Array.prototype.slice.call(arguments);
  let _adder = function () {
    _args.push(...arguments)
    return _adder
  }
  _adder.toString = function () {
    return _args.reduce(function (a, b) {
      return a + b
    })
  }
  return _adder
}

测试

console.log(add(1, 2, 3)(3).toString())
alert(add(1)(2)(3))
add(1)(2)(3)(4)(5)
function curry(func, args) {
  var len = func.length;
  var args = args || [];

  return function () {
    var _args = [].slice.call(arguments);
    [].push.apply(_args, args);
    // 如果参数个数小于最初的func.length,则递归调用,继续收集参数
    if (_args.length < len) {
      return curry.call(this, func, _args);
    }
    // 参数收集完毕,则执行func
    return func.apply(this, _args);
  }
}

function add(a, b) {
  return a + b;
}

const curriedAdd = curry(add);
alert(curriedAdd(1)(2));

47. String.prototype.indexOf()

String.prototype.myIndexOf = function (str) {
  let sourceArr = this.split('');
  let num = -1;
  for (let i in sourceArr) {
    if (sourceArr[i] === str.slice(0, 1)) {
      if (str === this.slice(i, Number(i) + str.length)) {
        num = i
      }
    }
  }
  return num
}

48. Object.is

+0 === -0: true NaN === NaN: false

主要是解决上诉两个判断错误的问题

  1. 运行到 1/x === 1/y 的时候 x 和 y 都为 0,但是 1/+0 = +Infinity1/-0 = -Infinity, 是不一样的
  2. NaN === NaNfalse,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理,两个都是 NaN 的时候返回 true
const is = (x, y) => {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
    return x !== x && y !== y
  }
}

49.将VirtualDom转化为真实DOM结构

function render(vnode, container) {
  container.appendChild(_render(vnode));
}
function _render(vnode) {
  // 如果是数字类型转化为字符串
  if (typeof vnode === 'number') {
    vnode = String(vnode);
  }
  // 字符串类型直接就是文本节点
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode);
  }
  // 普通DOM
  const dom = document.createElement(vnode.tag);
  if (vnode.attrs) {
    // 遍历属性
    Object.keys(vnode.attrs).forEach(key => {
      const value = vnode.attrs[key];
      dom.setAttribute(key, value);
    })
  }
  // 子数组进行递归操作
  vnode.children.forEach(child => render(child, dom));
  return dom;
}

50. 实现一个同时允许任务数量最大为 n 的函数

function limitRunTask(tasks, n) {
  return new Promise((resolve, reject) => {
    let index = 0, finish = 0, start = 0, res = []
    function run() {
      if (finish === tasks.length) {
        resolve(res)
        return;
      }
      while(start < n && index < tasks.length) {
        start++
        let cur = index
        tasks[index++]().then(v => {
          start--
          finish++
          res[cur] = v
          run()
        })
      }
    }
    run()
  })
}

51. isPromise

function isPromise(val) {
  return (
    val !== undefined && val !== null &&
    typeof val.then === "function" &&
    typeof val.catch === "function"
  )
}

52. Promise.all、race、allSettled

Promise.all 实际上是一个函数,它接受一个 promises 数组并返回一个 Promise。然后当所有的 promises 都完成时会得到 resolve 或者当其中一个被拒绝时会得到 rejected。

Promise.myall = function (arr) {
  return new Promise((resolve, reject) => {
    if (arr.length === 0) {
      return resolve([])
    } else {
      let res = [];
      let count = 0;
      for (let i = 0; i < arr.length; i++) {
        if (!arr[i] instanceof Promise) {
          res[i] = arr[i]
          if (++count === arr.length) {
            resolve(res)
          }
        } else {
          arr[i].then(data => {
            res[i] = data
            if (++count === arr.length) {
              resolve(res)
            }
          }, err => {
            reject(err)
          })
        }
      }
    }
  })
}

测试

const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 3000)
})

const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')

const p11 = Promise.all([p1, p4, p5])
  .then(console.log)
  .catch(console.log)

console.log(p11)

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

Promise.myrace = function (arr) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      if (!(arr[i] instanceof Promise)) {
        Promise.resolve(arr[i]).then(resolve, reject)
      } else {
        arr[i].then(resolve, reject)
      }
    }
  })
}

测试

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('1')
  }, 500);
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('2')
  }, 100);
})

Promise.myrace([p1, p2]).then((value) => {
  console.log(value) // 2
})

Promise.myrace([p1, p2, 3]).then((value) => {
  console.log(value) // 3
})

Promise.allSettled() 方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

Promise.myAllSettled = function (arr) {
  return new Promise((resolve, reject) => {
    const data = [];
    let count = arr.length;
    for (let i = 0; i < arr.length; i++) {
      const promise = arr[i];
      promise.then(res => {
        data[i] = {
          status: 'fulfilled',
          value: res
        }
      }, error => {
        data[i] = {
          status: 'rejected',
          reason: error
        }
      }).finally(() => {
        if (!--count) {
          resolve(data);
        }
      })
    }
  })
}

测试

const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
  setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => resolve(3), 3000)
})

const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')

const p12 = Promise.allSettled([p1, p2, p4, p5])

53. 构建Promise队列实现异步函数顺序执行

function queue(arr) {
  var sequence = Promise.resolve()
  arr.forEach(function (item) {
    sequence = sequence.then(item)
  })
  return sequence
}
async function queue(arr) {
  let res = null
  for (let promise of arr) {
    res = await promise(res)
  }
  return await res
}
queue([a, b, c])
  .then(data => {
    console.log(data)// abc
  })

54. 超时重连

function retry(fn, times) {
  return new Promise((resolve, reject) => {
    function run() {
      fn().then(resolve).catch(err => {
        if (times--) {
          console.log(`还有 ${times} 次尝试`)
          run()
        } else {
          reject(err)
        }
      })
    }
    run()
  })
}

测试

// 每隔一秒生成一个随机数,大于0.9才resolve
function retryDemo() {
  return new Promise((resolve, reject) => {
    let r = Math.random()
    setTimeout(() => {
      console.log(r)
      if (r > 0.9) {
        resolve(r)
      } else {
        reject('error:' + r)
      }
    }, 1000)
  })
}
// 使用重试函数
retry(retryDemo, 5).then(res => {
  console.log('成功:' + res)
}).catch(err => {
  console.log(err)
})

55. 简易版 Promise

class newPromise {
  queue1 = []
  queue2 = []
  constructor(fn) {
    const resolve = (data) => {
      setTimeout(() => {
        for (let i = 0; i < this.queue1.length; i++) {
          this.queue1[i](data)
        }
      })
    }
    const reject = (reason) => {
      setTimeout(() => {
        for (let i = 0; i < this.queue2.length; i++) {
          this.queue2[i](reason)
        }
      })
    }
    fn(resolve, reject)
  }
  then(s, e) {
    this.queue1.push(s)
    this.queue2.push(e)
    return this
  }
}

56. Promise

// 判断变量否为function
const isFunction = variable => typeof variable === 'function'
// 定义Promise的三种状态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
  constructor(handle) {
    if (!isFunction(handle)) {
      throw new Error('MyPromise must accept a function as a parameter')
    }
    // 添加状态
    this._status = PENDING
    // 添加状态
    this._value = undefined
    // 添加成功回调函数队列
    this._fulfilledQueues = []
    // 添加失败回调函数队列
    this._rejectedQueues = []
    // 执行handle
    try {
      handle(this._resolve.bind(this), this._reject.bind(this))
    } catch (err) {
      this._reject(err)
    }
  }
  // 添加resovle时执行的函数
  _resolve(val) {
    const run = () => {
      if (this._status !== PENDING) return
      // 依次执行成功队列中的函数,并清空队列
      const runFulfilled = (value) => {
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }
      // 依次执行失败队列中的函数,并清空队列
      const runRejected = (error) => {
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(error)
        }
      }
      /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
        当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
      */
      if (val instanceof MyPromise) {
        val.then(value => {
          this._value = value
          this._status = FULFILLED
          runFulfilled(value)
        }, err => {
          this._value = err
          this._status = REJECTED
          runRejected(err)
        })
      } else {
        this._value = val
        this._status = FULFILLED
        runFulfilled(val)
      }
    }
    // 为了支持同步的Promise,这里采用异步调用
    setTimeout(run, 0)
  }
  // 添加reject时执行的函数
  _reject(err) {
    if (this._status !== PENDING) return
    // 依次执行失败队列中的函数,并清空队列
    const run = () => {
      this._status = REJECTED
      this._value = err
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(err)
      }
    }
    // 为了支持同步的Promise,这里采用异步调用
    setTimeout(run, 0)
  }
  // 添加then方法
  then(onFulfilled, onRejected) {
    const { _value, _status } = this
    // 返回一个新的Promise对象
    return new MyPromise((onFulfilledNext, onRejectedNext) => {
      // 封装一个成功时执行的函数
      let fulfilled = value => {
        try {
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value)
          } else {
            let res = onFulfilled(value);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res)
            }
          }
        } catch (err) {
          // 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err)
        }
      }
      // 封装一个失败时执行的函数
      let rejected = error => {
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error)
          } else {
            let res = onRejected(error);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res)
            }
          }
        } catch (err) {
          // 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err)
        }
      }
      switch (_status) {
        // 当状态为pending时,将then方法回调函数加入执行队列等待执行
        case PENDING:
          this._fulfilledQueues.push(fulfilled)
          this._rejectedQueues.push(rejected)
          break
        // 当状态已经改变时,立即执行对应的回调函数
        case FULFILLED:
          fulfilled(_value)
          break
        case REJECTED:
          rejected(_value)
          break
      }
    })
  }
  // 添加catch方法
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
  // 添加静态resolve方法
  static resolve(value) {
    // 如果参数是MyPromise实例,直接返回这个实例
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }
  // 添加静态reject方法
  static reject(value) {
    return new MyPromise((resolve, reject) => reject(value))
  }
  // 添加静态all方法
  static all(list) {
    return new MyPromise((resolve, reject) => {
      /**
       * 返回值的集合
       */
      let values = []
      let count = 0
      for (let [i, p] of list.entries()) {
        // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
        this.resolve(p).then(res => {
          values[i] = res
          count++
          // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
          if (count === list.length) resolve(values)
        }, err => {
          // 有一个被rejected时返回的MyPromise状态就变成rejected
          reject(err)
        })
      }
    })
  }
  // 添加静态race方法
  static race(list) {
    return new MyPromise((resolve, reject) => {
      for (let p of list) {
        // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
        this.resolve(p).then(res => {
          resolve(res)
        }, err => {
          reject(err)
        })
      }
    })
  }
  finally(cb) {
    return this.then(
      value => MyPromise.resolve(cb()).then(() => value),
      reason => MyPromise.resolve(cb()).then(() => { throw reason })
    );
  }
}