前端常见手写 JS 题(巩固你的前端基础)

在这里插入图片描述

数组扁平化

const arr = [1, [2, [3, [4, 5]]], 6]; // => [1, 2, 3, 4, 5, 6]
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;
}
flat()
const res1 = arr.flat(Infinity)
正则
const res2 = JSON.stringify(arr).replace(/[\[|\]]/g, '').split(',').map(e => parseInt(e))
// 版本2
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)
  }, [])
}
const res4 = flatten(arr)

那如果我希望可以控制展开的层级呢?ok,满足你

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

数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
Set
const res1 = Array.from(new Set(arr));
两层for循环+splice
const unique1 = 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);
        // 每删除一个数,j--
        // 保证j的值经过自加后不变。同时,len--,减少循环次数提升性能
        len--;
        j--;
      }
    }
  }
  return arr;
}
indexOf
const unique2 = 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 unique3 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
}
filter
const unique4 = arr => {
  return arr.filter((item, index) => {
    return arr.indexOf(item) === index;
  });
}
Map
const unique5 = 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;
}

初始化二维数组

function initializeArr(row, column) {
  let arr = [];
  for (let i = 0; i < row; i++) {
    arr[i] = [];
    for (let j = 0; j < column; j++) {
      arr[i][j] = 0;
    }
  }
  return arr
}
const arr = new Array(row).fill(0).map(v => new Array(column).fill(0));

类数组转化为数组

Array.from
Array.from(document.querySelectorAll('div'))
Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
扩展运算符
[...document.querySelectorAll('div')]
concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));

求数组最大值

Math.max(...arr)

Math.max.apply(null, arr)

Math.max.call(null, ...arr)

let arr1 = arr.reduce(function (prev, next) {
  return Math.max(prev, next)
})

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
}

Array.prototype.filter()

Array.prototype.filter = function(callbackFn, thisArg) {
  console.log('this is my filter function')
  // 处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError("Cannot read property 'filter' of null or undefined")
  }
  // 处理回调类型异常
  if (Object.prototype.toString.call(callbackFn) !== '[object Function]') {
    throw new TypeError(callbackFn + 'is not a function')
  }
  let O = Object(this)
  let len = O.length >>> 0
  let resLen = 0
  let res = []
  for (let i = 0; i < len; i++) {
    if (i in O) {
      let element = O[i]
      // 是否满足回调函数,如果满足,则放入数组中
      if (callbackFn.call(thisArg, O[i], i, O)) {
        res[resLen++] = element
      }
    }
  }
  return res
}

const tempArr = [10, 23, 443, 23, 8, 6, 34, 67]
let res = tempArr.filter((e) => {
  if (e > 100) {
    return e
  }
})
console.log(res)

Array.prototype.map()

Array.prototype.map = function(callbackFn, thisArg) {
  // 处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError("Cannot read property 'map' of null or undefined");
  }
  // 处理回调类型异常
  if (Object.prototype.toString.call(callbackFn) != "[object Function]") {
    throw new TypeError(callbackFn + ' is not a function')
  }
  let O = Object(this);
  let T = thisArg;

  let len = O.length >>> 0;
  let A = new Array(len);
  for(let k = 0; k < len; k++) {
    // 如果用 hasOwnProperty 是有问题的,它只能找私有属性
    if (k in O) {
      let kValue = O[k];
      // 依次传入this, 当前项,当前索引,整个数组
      let mappedValue = callbackFn.call(T, kValue, k, O);
      A[k] = mappedValue;
    }
  }
  return A;
}

const tempArr = [10, 23, 443, 23, 8, 6, 34, 67]
let res = tempArr.map((e) => e * 2)
console.log(res)

处理数组类型异常,先转换为对象,获取参数的长度,声明数组。对生成的 O 对象进行遍历,获取每一个 key 对应的值 O[key],传入到当前的回调函数中,进行计算,将值重新放入对应 key 的位置。

Array.prototype.forEach()

Array.prototype.forEach = function(callback, thisArg) {
  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 len = o.length >>> 0
  for (let i = 0; i < len; i++) {
    if (i in o) {
      callback.call(thisArg, o[i], i)
    }
  }
}

Array.prototype.reduce()

Array.prototype.reduce  = function(callbackfn, initialValue) {
  // 处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError("Cannot read property 'reduce' of null or undefined");
  }
  // 处理回调类型异常
  if (Object.prototype.toString.call(callbackfn) != "[object Function]") {
    throw new TypeError(callbackfn + ' is not a function')
  }
  let O = Object(this);
  // console.log(this)
  // O = (4) [1, 2, 3, 4]
  let len = O.length >>> 0;
  let k = 0;
  let accumulator = initialValue;
  if (accumulator === undefined) {
    for(; k < len ; k++) {
      // 查找原型链
      if (k in O) {
        accumulator = O[k];
        k++;
        break;
      }
    }
  }
  // 表示数组全为空
  if(k === len && accumulator === undefined) 
    throw new Error('Each element of the array is empty');
  for(;k < len; k++) {
    if (k in O) {
      // 注意,核心!
      accumulator = callbackfn.call(undefined, accumulator, O[k], k, O);
    }
  }
  return accumulator;
}

let tempArr = [1, 2, 3, 4]
let res = tempArr.reduce((a, b) => a - b)
console.log(res)

处理数组类型异常,处理回调类型异常,获取传入参数列表的长度,如果初始值为 undefined,则取参数列表的第一个数做为初始值,将前一个数字和后一个数字传入回调函数中,进行计算。把两个计算出来的值,重新赋值给前一个。

Array.prototype.push()

Array.prototype.push = function(...items) {
  let O = Object(this);
  let len = this.length >>> 0;
  let argCount = items.length >>> 0;
  // 2 ** 53 - 1 为JS能表示的最大正整数
  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];
  }
  let newLength = len + argCount;
  O.length = newLength;
  return newLength;
}

Array.prototype.pop()

Array.prototype.pop = function () {
  console.log('this is my pop function')
  let O = Object(this)
  let len = this.length >>> 0
  if (len === 0) {
    O.length = 0
    return undefined
  }
  len--
  let value = O[len]
  delete O[len]
  O.length = len
  return value
}

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
}

function print(age) {
  console.log(this.name + ' ' +age)
}

let obj = {
  name: '奶油桃子'
}

print.call(obj, 1, 2, 3)

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
}

function print(age, age2, age3) {
  console.log(`${this.name} ${age} ${age2} ${age3}`)
}

let obj = {
  name: '奶油桃子'
}

print.myApply(obj, [1, 2, 3])

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
}

function print(age) {
  console.log(`${this.name} ${age}`)
}

let obj = {
  name: '奶油桃子'
}

let bound = print.myBind(obj, 1)
bound()

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)
  }
}

throttle(节流)

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

函数柯里化

function add() {
  // 第一次执行时,定义一个数组专门用来存储所有的参数
  let _args = Array.prototype.slice.call(arguments)

  // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
  let _adder = function () {
    _args.push(...arguments)
    return _adder
  };

  // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
  _adder.toString = function () {
    return _args.reduce(function (a, b) {
      return a + b
    });
  }
  return _adder
}

alert(add(1)(2)(3))                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

compose

function compose() {
  let fns = [].slice.call(arguments)
  return function(initalArg) {
    let res = initalArg
    for (let i = fns.length - 1; i > -1; i--) {
      res = fns[i](res)
    }
    return res
  }
}

let greet = function (name) { return 'hi:' + name }
let exclaim = function (statement) { return statement.toUpperCase() + '!' }
let transform = function (str) { return str.replace(/[dD]/, 'DDDDD') }

let result = compose(greet, exclaim, transform)
console.log(result('dot'))

实现 new

function create(Con, ...args) {
  let obj = {}
  obj.__proto__ = Con.prototype
  let result = Con.apply(obj, args)
  return result instanceof Object ? result : obj
}

instanceof

function Foo() {}
var f1 = new Foo()
// 根据 instanceof 原理
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)
  }
}

console.log(myInstanceof(f1, Foo))

ES5 继承

function Father() {}

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

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

Object.is

bject.is解决的主要是这两个问题:

+0 === -0  // true
NaN === NaN // false
const is = (x, y) => {
  if (x === y) {
    return x !== 0 || y !== 0 || 1/x === 1/y
  } else {
    return x !== x && y !== y
  }
}

模拟Object.create

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

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

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
}

let a = {
  b11: 1
}
let b = Object.assign2({}, a)
a.b11 = 111111
console.log(a, b)

深拷贝

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);
  // 判断数组和是对象
  var 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;
}

JSON.stringify()

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)()
}

JSONP

const jsonp = function (url, data) {
  return new Promise((resolve, reject) => {
    // 初始化url
    let dataString = url.indexOf('?') === -1 ? '?' : ''
    let callbackName = `jsonpCB_${Date.now()}`
    url += `${dataString}callback=${callbackName}`
    if (data) {
      // 有请求参数,依次添加到url
      for (let k in data) {
        url += `${k}=${data[k]}`
      }
    }
    let jsNode = document.createElement('script')
    jsNode.src = url
    // 触发callback,触发后删除js标签和绑定在window上的callback
    window[callbackName] = result => {
      delete window[callbackName]
      document.body.removeChild(jsNode)
      if (result) {
        resolve(result)
      } else {
        reject('没有返回数据')
      }
    }
    // js加载异常的情况
    jsNode.addEventListener('error', () => {
      delete window[callbackName]
      document.body.removeChild(jsNode)
      reject('JavaScript资源加载失败')
    }, false)
    // 添加js节点到document上时,开始请求
    document.body.appendChild(jsNode)
  })
}
jsonp('http://139.224.66.67:3000/comment/music?id=186016&limit=1').then(result => {
  console.log(result)
}).catch(err => {
  console.error(err)
})

AJAX

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", "http://139.224.66.67:3000/comment/music?id=186016&limit=1").then(res => {
  console.log(res);
});

字符串模板

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 = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
  name: '淘淘',
  age: 12
}

console.log(render(template, person))

解析 URL 参数为对象

function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
  const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
  let paramsObj = {};
  // 将 params 存到对象中
  paramsArr.forEach(param => {
    if (/=/.test(param)) { // 处理有 value 的参数
      let [key, val] = param.split('='); // 分割 key 和 value
      val = decodeURIComponent(val); // 解码
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字

      if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else { // 如果对象没有这个 key,创建 key 并设置值
        paramsObj[key] = val;
      }
    } else { // 处理没有 value 的参数
      paramsObj[param] = true;
    }
  })
  return paramsObj;
}

用正则实现 trim()

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

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

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

手写观察者模式

class Subject {
  constructor(name) {
    this.name = name
    this.observers = []
    this.state = ''
  }
  // 被观察者要提供一个接受观察者的方式
  attach(observer) {
    this.observers.push(observer)
  }
  // 改变被观察者的状态
  setState(newState) {
    this.state = newState
    this.observers.forEach(o => {
      o.update(newState)
    })
  }
}
class Observer {
  constructor(name) {
    this.name = name
  }
  update(newState) {
    console.log(`${this.name} say: ${newState}`)
  }
}

// 被观察者   灯
let sub = new Subject('灯')
let mm = new Observer('小明')
let jj = new Observer('小健')
// 订阅 观察者
sub.attach(mm)
sub.attach(jj)

sub.setState('灯亮了来电了')

转化为驼峰命名

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

实现千位分隔符

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 : '')
}

repeat

function repeat(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)
    }
  }
}

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

sleep

function sleep(fn, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(fn)
    }, time)
  })
}

let saySomrthing = (name) => console.log(`hello, ${name}`)

async function autoPlay() {
  let demo = await sleep(saySomrthing('lily'), 1000)
  let demo1 = await sleep(saySomrthing('mike'), 2000)
}

autoPlay()

将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;
}

图片懒加载

可以给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);

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

渲染大数据时,合理使用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)

EventEmitter 实现

// 手写发布订阅模式 EventEmitter
class EventEmitter {
  constructor() {
    this.events = {};
  }
  // 实现订阅
  on(type, callBack) {
    if (!this.events) this.events = Object.create(null);

    if (!this.events[type]) {
      this.events[type] = [callBack];
    } else {
      this.events[type].push(callBack);
    }
  }
  // 删除订阅
  off(type, callBack) {
    if (!this.events[type]) return;
    this.events[type] = this.events[type].filter(item => {
      return item !== callBack;
    });
  }
  // 只执行一次订阅事件
  once(type, callBack) {
    function fn() {
      callBack();
      this.off(type, fn);
    }
    this.on(type, fn);
  }
  // 触发事件
  emit(type, ...rest) {
    this.events[type] &&
      this.events[type].forEach(fn => fn.apply(this, rest));
  }
}
// 使用如下
const event = new EventEmitter();

const handle = (...rest) => {
  console.log(rest);
};

event.on("click", handle);

event.emit("click", 1, 2, 3, 4);

event.off("click", handle);

event.emit("click", 1, 2);

event.once("dbClick", () => {
  console.log(123456);
});
event.emit("dbClick");
event.emit("dbClick");

事件委托

ul.addEventListener('click', function (e) {
  if (e.target.tagName.toLowerCase() === 'span') {
  }
})

格式化时间

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
}

实现一个同时允许任务数量最大为 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()
  })
}

10进制转换

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
}

是否为素数

function isPrime(num) {
  let temp = Math.sqrt(num)
  for (let i = 2; i <= temp; i++) {
    if (num % i === 0) {
      return false
    }
  }
  return true
}

大数运算

let a = "9007199254740991";
let b = "1234567899999999999";

function add(a, b) {
  //取两个数字的最大长度
  let maxLength = Math.max(a.length, b.length);
  //用0去补齐长度
  a = a.padStart(maxLength, 0);//"0009007199254740991"
  b = b.padStart(maxLength, 0);//"1234567899999999999"
  //定义加法过程中需要用到的变量
  let t = 0;
  let f = 0;   //"进位"
  let sum = "";
  for (let i = maxLength - 1; i >= 0; i--) {
    t = parseInt(a[i]) + parseInt(b[i]) + f;
    f = Math.floor(t / 10);
    sum = t % 10 + sum;
  }
  if (f == 1) {
    sum = "1" + sum;
  }
  return sum;
}

生成随机数

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

缓存函数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
}

数组随机排序

let arr = [2, 3, 454, 34, 324, 32]
arr.sort(function() {
  return Math.random() > 0.5 ? -1 : 1
})

合并有序数组

function mergeArray(arr1, arr2) {
  let ind1 = 0;
  let ind2 = 0;
  let arr = [];
  while (ind1 < arr1.length && ind2 < arr2.length) {
    if (arr1[ind1] <= arr2[ind2]) {
      arr.push(arr1.slice(ind1, ind1 + 1)[0]);
      ind1++;
    } else {
      arr.push(arr2.slice(ind2, ind2 + 1)[0]);
      ind2++;
    }
  }
  while (ind1 < arr1.length) {
    arr.push(arr1.slice(ind1, ind1 + 1)[0]);
    ind1++;
  }
  while (ind2 < arr2.length) {
    arr.push(arr2.slice(ind2, ind2 + 1)[0]);
    ind2++;
  }
  return arr;
}

二分查找

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

function binarySearch(target, arr, start, end) {
  if (start > end) { return -1 }
  let start = start || 0;
  let 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);
  }
  return -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) {
    var left = [];
    var right = [];
    var pivot = arr[0];
    for (var i = 1; i < arr.length; i++) {
      var 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;
}

已知excel表格的列命名方式(A.B.C…Z.AA.AB…AZ.AAA…AAZ)输出1-1000列的列名

function execlNumChange(num) {
  if (num <= 0) {
    alert("excel表格貌似没有负数吧");
    return 0;
  }
  if (num > 26) {
    let newnum1 = parseInt(num / 26);
    let newnum2 = num % 26;
    let newnum1str, newnum2str = null;
    if (!newnum2) {
      newnum1str = execlNumChange(newnum1 - 1);
      newnum2str = "Z";
    } else {
      newnum1str = execlNumChange(newnum1);
      newnum2str = execlNumChange(newnum2);
    }
    return newnum1str + newnum2str;
  }

  let az = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  let arr = az.split("");
  for (let i = 0; i <= arr.length; i++) {
    if (num === i) {
      return arr[i - 1];
    }
  }
}

版本号排序

const versionSort = (arr) => {
  const p = 100000
  const maxLen = Math.max(
    ...arr.map((item) => item.split('.').length)
  );

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

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

获取 localStorage 的大小

function getLocalStoreSize() {
  let sizeStore = 0;
  if (window.localStorage) {
    for (item in window.localStorage) {
      if (window.localStorage.hasOwnProperty(item)) {
        sizeStore += window.localStorage.getItem(item).length
      }
    }
  }
  return (sizeStore / 1024 / 1024).toFixed(2) + 'M'
}

可拖拽的 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
    });

简易版 Promise

// 未添加异步处理等其他边界情况
// ①自动执行函数,②三个状态,③then
class Promise {
  constructor(fn) {
    // 三个状态
    this.state = 'pending'
    this.value = undefined
    this.reason = undefined
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
      }
    }
    // 自动执行函数
    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // then
  then(onFulfilled, onRejected) {
    switch (this.state) {
      case 'fulfilled':
        onFulfilled()
        break
      case 'rejected':
        onRejected()
        break
      default:
    }
  }
}

实现Promise.all和race

Promise.myall = function (arr) {
  return new Promise((resolve, reject) => {
    if (arr.length === 0) {
      return resolve([])
    } else {
      let res = [],
        count = 0
      for (let i = 0; i < arr.length; i++) {
        // 同时也能处理arr数组中非Promise对象
        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)
          })
        }

      }
    }
  })
}

Promise.myrace = function (arr) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      // 同时也能处理arr数组中非Promise对象
      if (!(arr[i] instanceof Promise)) {
        Promise.resolve(arr[i]).then(resolve, reject)
      } else {
        arr[i].then(resolve, reject)
      }

    }
  })
}

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 })
    );
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值