常用函数工具集

56 阅读3分钟

柯里化通用模板

const curry = (fn) => {
  // 实现一个curry工具方法
  // TODO
  const curryTemp = (...args1) => {
    if (args1.length < fn.length) {
      return (...args2) => {
        return curryTemp(...args1, ...args2);
      };
    }
    return fn(...args1);
  };
  return curryTemp;
};

组合函数通用模板

const compose = (...funcs) => {
  // 组合函数工具方法
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
};

解析 url

const httpUrl = string =>
  string
    .split('?')
    .slice(-1)[0]
    .split('&')
    .reduce((acc, cur) => {
      cur = cur.split('=')
      acc[cur[0]] = cur[1]
      return acc
    }, {})

小驼峰转下划线

const humpToUnderLine = srt => srt.replace(/([A-Z])/g, '_$1').toLowerCase()

下划线转小驼峰

const underLineToHump = str => str.replace(/_(\w)/g, (all, latter) => latter.toUpperCase())

对象拍平

// 输入
const entryObj = {
  a: {
    b: {
      c: {
        dd: 'abcdd',
      },
    },
    d: {
      xx: 'adxx',
    },
    e: 'ae',
  },
};

const objectFlat = (data) => {
  const outputTemp = {};
  const objectMap = (obj, parentKey = '') => {
    for (let i in obj) {
      if (Object.prototype.toString.call(obj[i]) === '[object Object]') {
        objectMap(obj[i], `${parentKey}.${i}`);
      } else {
        outputTemp[`${parentKey}.${i}`] = obj[i];
      }
    }
  };
  for (let i in data) {
    if (Object.prototype.toString.call(data[i]) === '[object Object]') {
      objectMap(data[i], i);
    } else {
      outputTemp[i] = data[i];
    }
  }
  return outputTemp;
};

objectFlat(entryObj);

// 输出
const outputObj = {
  'a.b.c.dd': 'abcdd',
  'a.d.xx': 'adxx',
  'a.e': 'ae',
};

数组拍平

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

const arrayFlat = (array) =>
  array.reduce((acc, cur) => {
    if (Object.prototype.toString.call(cur) === '[object Array]') {
      cur = arrayFlat(cur);
    }
    return acc.concat(cur);
  }, []);

arrayFlat(array); // [1, 2, 3, 4, 5, 6, 7]

手写实现 bind

Function.prototype.bind(ctx) = {
  var that = this
  return function fn() {
    that.apply(ctx,arguments)
  }
}

手写实现 call

Function.prototype.ownCall = function(context, ...args) {
  context = (typeof context === 'object' ? context : window)
  // 防止覆盖掉原有属性
  const key = Symbol()
  // 这里的this为需要执行的方法
  context[key] = this
  // 方法执行
  const result = context[key](...args)
  delete context[key]
  return result
}

手写实现 apply

Function.prototype.ownApply = function(context, args) {
  context = (typeof context === 'object' ? context : window)
  // 防止覆盖掉原有属性
  const key = Symbol()
  // 这里的this为需要执行的方法
  context[key] = this
  // 方法执行
  const result = context[key](...args)
  delete context[key]
  return result
}

排列组合后的所有可能性

const Permutations = (arr) => {
  const getNumber = (val) => {
    return val ? getNumber(val - 1) * val : 1;
  };
  let k = 0;
  let max = getNumber(arr.length);
  let res = [];
  while (max--) {
    res.push(arr.join(''));
    [arr[k], arr[k + 1]] = [arr[k + 1], arr[k]];
    k = ++k % (arr.length - 1);
  }
  return res;
};

合并连贯数字

const mergeNumber = (arr) => {
  const len = arr.length;
  const empty = [];
  if (len > 0) empty.push(arr[0]);
  let k = 0;
  let j = 0;
  for (let i = 1; i < len; i++, j++) {
    if (arr[j] !== arr[i] - 1) {
      empty.push(arr[i]);
      k = i;
    }
    if (i - k > 0) {
      empty[empty.length - 1] = `${
        empty[empty.length - 1].toString().split('-')[0]
      }-${arr[i]}`;
    }
  }
  return empty;
};

防抖和节流

防抖 - 触发高频事件后 n 秒后函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间

function debounce(fn, time) {
  let timer = null;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(...this.arguments);
    }, time);
  };
}

节流 - 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。

function throttle(fn, time) {
  let canRun = true;
  return function () {
    if (!canRun) return;
    canRun = false;
    const timer = setTimeout(() => {
      fn.apply(...this.arguments);
      canRun = true;
      clearTimeout(timer);
    }, time);
  };
}

继承

父类

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

原型链继承

核心: 将父类的实例作为子类的原型

function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

优点

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点

  1. 要想为子类新增属性和方法,必须要在 new Animal()这样的语句之后执行,不能放到构造器中
  2. 无法实现多继承
  3. 来自原型对象的引用属性是所有实例共享的(详细请看附录代码: 示例 1
  4. 创建子类实例时,无法向父类构造函数传参

构造函数继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

特点

  1. 解决了 1 中,子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call 多个父类对象)

缺点

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

组合式继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

特点

  1. 弥补了方式 2 的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  5. 函数可复用

缺点

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

寄生式继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

数组排序

哈希排序

function hashSort(arr) {
  const hashTable = {};
  const sortedArray = [];

  // 遍历数组,并在哈希表中记录每个元素的出现次数
  arr.forEach(num => {
    if (hashTable[num]) {
      hashTable[num]++;
    } else {
      hashTable[num] = 1;
    }
  });
  console.log(hashTable)

  // 遍历哈希表,将元素按照出现次数添加到排序后的数组中
  for (let key in hashTable) {
    let freq = hashTable[key];
    while (freq > 0) {
      sortedArray.push(parseInt(key));
      freq--;
    }
  }

  return sortedArray;
}

每隔一秒输出一个数字

const func5 = async () => {
    for (let i = 1; i < 6; i++) {
       await new Promise((reslove) => {setTimeout(() => { reslove(i) }, 1000, i) }).then(res =>{ console.log(res)})
    }
}