# 常用的工具函数

# 周期循环

function backCycle(num: number, cycle: number): number {
  let index = num % cycle;
  if (index < 0) {
    index += cycle;
  }
  return index;
}

num 表示要计算的数值,cycle 表示周期长度. 例如:时钟从 0 点开始,经过 15 小时后,即 backCycle(15,12),指针会指向刻度的"3". 例如:现在是 0 点,计算 28 小时之前,即 backCycle(-28,12),是"8"点.

# 数值越界

/**
 * @name boundary
 * @desc 数值边界
 * @param {Number} num 需要处理的数值
 * @param {Number} min 最小边界值
 * @param {Number} max 最大边界值
 */
function boundary(num, min = -Infinity, max = Infinity) {
  return Math.max(min, Math.min(max, num));
}

# search 参数转化

/**
 * @desc 获取url中的search参数,并转化为对象
 * @return {Object} search
 */
function getSearchObject(): object {
  let result = {};
  const url = window.location.href;
  const hasSearch = url.split("?").length > 1;
  if (hasSearch) {
    const searchStr = url.split("?")[1];
    const searchKeyValueArr = searchStr.split("&");
    for (let i = 0; i < searchKeyValueArr.length; i++) {
      const [key, value] = searchKeyValueArr[i].split("=");
      result[key] = decodeURIComponent(value);
    }
  }

  return result;
}

/**
 * @desc 将对象转化为search字符串
 * @param {String} window.location.search
 */
function objectToSearch(obj: object): string {
  let searchStr = "";
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = encodeURIComponent(obj[key]);
      searchStr += `${key}=${value}&`;
    }
  }

  return searchStr ? "?" + searchStr.slice(0, -1) : "";
}

# 小数点保留

function toFixed(num = 0, count = 2) {
  if (typeof num !== "number") {
    throw new Error("argument 'num' must be a Number");
  }
  if (typeof count !== "number") {
    throw new Error("argument 'count' must be a Number");
  }

  const _c = Math.max(1, Math.min(10, count));
  return parseFloat(num.toFixed(_c));
}

使用

toFixed(0.1 + 0.2);
toFixed(12 * 0.6);
toFixed(0.3 + 0.6);
toFixed(0.4 * 0.7);
toFixed(0.9 * 0.2);

# 颜色转化 hsl to rgb

/**
 * @desc hsla 转为 rgba
 * @param {Number} h 色相 [0-360]
 * @param {Number} s 饱和度 [0-1]
 * @param {Number} l 亮度 [0-1] 0.5时为正常
 * @param {Number} a 透明度 [0-1]
 */
function hsla2rgba(h = 0, s = 1, l = 0.5, a = 1) {
  let r, g, b;
  let deg = h % 360;
  // 将色相转化为[0-1]的数值
  deg = deg < 0 ? 360 + deg : deg;
  const num = Math.floor((1000 * deg) / 360) / 1000;

  if (s === 0) {
    r = g = b = l;
  } else {
    const hue2rgb = (p, q, t) => {
      if (t < 0) t += 1;
      if (t > 1) t -= 1;
      if (t < 1 / 6) return p + (q - p) * 6 * t;
      if (t < 1 / 2) return q;
      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
      return p;
    };

    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    const p = 2 * l - q;
    r = hue2rgb(p, q, num + 1 / 3);
    g = hue2rgb(p, q, num);
    b = hue2rgb(p, q, num - 1 / 3);
  }

  return `rgba(${Math.floor(r * 255)},${Math.floor(g * 255)},${Math.floor(
    b * 255
  )},${a})`;
}

# 随机字符串

function randomStr(len = 16) {
  const l = Math.max(1, Math.min(100, len));
  let str = "_";

  while (str.length < l) {
    str += Math.random()
      .toString(36)
      .slice(2);
  }

  return str.slice(0, l);
}

# 函数节流

/**
 * @desc 将高频触发的事件,降低频率
 * @param {Function} fn 回调函数
 * @param {Number} timeout 函数执行的周期时间
 */
function throttle(fn, timeout = 1000) {
  if (!fn) {
    return;
  }
  let startTime = new Date();
  return (event) => {
    const now = new Date();
    if (now - startTime > timeout) {
      fn.call(fn, event);
      startTime = now;
    }
  };
}

# 函数防抖

/**
 * @desc 高频触发的事件,当该事件停止触发再执行回调
 * @param {Function} fn 回调函数
 * @param {Number} delay 防抖周期
 */
function debounce(fn, delay = 500) {
  if (!fn) {
    return;
  }
  let timer = null;
  return (event) => {
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      fn.call(fn, event);
    }, delay);
  };
}

# aes 加解密

安装插件

npm install crypto-js --save
/**
 * @desc aes 加/解密工具
 * @author darcrand
 * @version 2019-09-17
 */
import CryptoJS from "crypto-js";

const DEFAULT_KEY = "your-key";
const DEFAULT_IV = "your-iv";

/**
 * @desc aes加密
 * @param {any} value 需要加密的数据
 * @return {String} 加密的字符串
 */
function encrypt(value = "", { key = DEFAULT_KEY, iv = DEFAULT_IV } = {}) {
  if (!value) {
    console.error("aes encrypt error: param:'value' is required");
    return "";
  }

  try {
    const str = JSON.stringify(value);
    const ciphertext = CryptoJS.AES.encrypt(str, key, { iv });
    const encrypted = ciphertext.toString();
    return encrypted;
  } catch (err) {
    console.error("aes encrypt error: fail");
    return "";
  }
}

/**
 * @desc aes解密
 * @param {String} str 被加密的内容
 * @return {any} 解密的明文
 */
function decrypt(str = "", { key = DEFAULT_KEY, iv = DEFAULT_IV } = {}) {
  if (!str) {
    console.error("aes decrypt error: param:'str' is required");
    return "";
  }

  try {
    var bytes = CryptoJS.AES.decrypt(str, key, { iv });
    var plaintext = bytes.toString(CryptoJS.enc.Utf8);
    const decrypted = JSON.parse(plaintext);
    return decrypted;
  } catch (err) {
    console.error("aes encrypt error: fail");
    return "";
  }
}

export { encrypt, decrypt };

# 数组去重

/**
 * @description 数组去重
 * @param {Array<any>} arr
 * @param {String} key 用于对象数组时,过滤的key
 * @returns {Array<any>}
 */
export function unduplicate(arr = [], key = null) {
  const result = [];
  const obj = {};

  if (key) {
    for (const item of arr) {
      if (!obj[item[key]]) {
        result.push(item);
        obj[item[key]] = true;
      }
    }
  } else {
    for (let i of arr) {
      if (!obj[i]) {
        result.push(i);
        obj[i] = true;
      }
    }
  }

  return result;
}

# 数组分割

/**
 * @description 一维数组转为'col'列的二维数组
 * @param {Array} arr
 * @param {Number} col 列数,不能小于2
 * @returns {Array|null}
 */
function splitArray(arr = [], col = 5) {
  if (!Array.isArray(arr) || !arr.length || col < 2) {
    return [];
  }

  return arr.reduce((prev, curr, index) => {
    const shouldCreateArr = index % col === 0;
    if (shouldCreateArr) {
      prev.push([curr]);
      return prev;
    } else {
      const lastArr = prev[prev.length - 1];
      lastArr.push(curr);
      return prev;
    }
  }, []);
}

# 数组深度查询

/**
 * @desc 对象数组深度查询
 * @param {Array} arr 原始数组,子孙数组结构需相同
 * @param {Function} fnFilter 用于判断是否匹配成功的函数,接受当前遍历的元素,返回布尔值
 * @param {String} childrenKey 指向子数组的key
 *
 * @returns {Object|undefined} target 匹配到的元素,该元素保留在原始数组的路径
 */
function findDeep(
  arr = [],
  fnFilter = (item, index) => {},
  childrenKey = "children"
) {
  const stack = [];

  // 把所有节点推入栈,铺平数组
  const putInStack = (_arr = []) => {
    if (!Array.isArray(_arr)) {
      return;
    }
    for (let i = 0; i < _arr.length; i++) {
      stack.push(_arr[i]);
      putInStack(_arr[i][childrenKey]);
    }
  };

  putInStack(arr);
  return stack.find(fnFilter);
}

另外一种深度优先算法

function findDeep2(
  arr = [],
  fnFilter = (item, index) => {},
  childrenKey = "children"
) {
  let matchItem = null;
  let currIndex = 0;
  const stack = [];

  const putInStack = (_arr = []) => {
    if (!Array.isArray(_arr)) {
      return;
    }
    for (let i = 0; i < _arr.length; i++) {
      const item = _arr[i];
      stack.push(item);

      const isFinded = search();
      if (isFinded) {
        break;
      } else {
        putInStack(item[childrenKey]);
      }
    }
  };

  const search = () => {
    for (; currIndex < stack.length; currIndex++) {
      matchItem = fnFilter(stack[currIndex]) ? stack[currIndex] : null;
      if (matchItem) {
        return true;
      }
    }
    return false;
  };

  putInStack(arr);
  return matchItem;
}

# 数值缩写

/**
 * @description 数值缩写
 * @param number 数值
 * @param count 保留小数点位数(大于等于0)
 */
function numberAbbreviation(number = 0, count = 0): string {
  const suffixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y"];
  const step = 1000;
  const power = Math.pow(10, count < 0 ? 0 : count);
  let num = number;
  let index = 0;

  while (num > step) {
    index++;
    num /= step;
  }

  num = Math.floor(power * num) / power;
  const suffix = suffixes[index];

  return num + suffix;
}

使用

const s = numberAbbreviation(123456789, 1); //123.4M

# 从 url 中保存和获取对象类型参数

const URLQuery = {
  toSearch(obj = {}, searchKey = "query") {
    if (typeof obj !== "object") {
      return "";
    }

    const str = JSON.stringify(obj);
    const encodeStr = window.btoa(unescape(encodeURIComponent(str)));
    return `?${searchKey}=${encodeStr}`;
  },

  toObject(searchKey = "query") {
    const url = window.location.href;
    const hasSearch = url.split("?").length > 1;

    if (!hasSearch) {
      return {};
    }

    const searchStr = url.split("?")[1];
    const searchKeyValueArr = searchStr.split("&");
    let encodeQueryStr = "";

    for (let i = 0; i < searchKeyValueArr.length; i++) {
      const [key, value] = searchKeyValueArr[i].split("=");
      if (key === searchKey) {
        encodeQueryStr = value;
        break;
      }
    }

    const str = decodeURIComponent(escape(window.atob(encodeQueryStr)));
    try {
      return JSON.parse(str);
    } catch (err) {
      console.error(err);
      return {};
    }
  },
};

使用:

在 url 上添加 search 字符串

const params = { a: 1, b: "", c: [] };
const search = URLQuery.toSearch(params);

window.location.href += search;

从 url 上获取 search 转化的对象参数

const objectFromSearch = URLQuery.toObject();

# 限制数值大小

/**
 * @description 限制数值大小
 * @param {number} num 目标数值
 * @param {number?} min 最小值
 * @param {number?} max 最大值
 * @returns
 */
function limit(num = 0, { min = -Infinity, max = Infinity } = {}) {
  return Math.min(max, Math.max(min, num));
}

# 判断浏览器环境

原始地址: JowayYoung - 详细判断浏览器运行环境 (opens new window)

function BrowserType() {
  const ua = navigator.userAgent.toLowerCase();
  const testUa = (regexp) => regexp.test(ua);
  const testVs = (regexp) =>
    ua
      .match(regexp)
      .toString()
      .replace(/[^0-9|_.]/g, "")
      .replace(/_/g, ".");

  // 系统
  let system = "unknow";
  if (testUa(/windows|win32|win64|wow32|wow64/g)) {
    system = "windows"; // windows系统
  } else if (testUa(/macintosh|macintel/g)) {
    system = "macos"; // macos系统
  } else if (testUa(/x11/g)) {
    system = "linux"; // linux系统
  } else if (testUa(/android|adr/g)) {
    system = "android"; // android系统
  } else if (testUa(/ios|iphone|ipad|ipod|iwatch/g)) {
    system = "ios"; // ios系统
  }

  // 系统版本
  let systemVs = "unknow";
  if (system === "windows") {
    if (testUa(/windows nt 5.0|windows 2000/g)) {
      systemVs = "2000";
    } else if (testUa(/windows nt 5.1|windows xp/g)) {
      systemVs = "xp";
    } else if (testUa(/windows nt 5.2|windows 2003/g)) {
      systemVs = "2003";
    } else if (testUa(/windows nt 6.0|windows vista/g)) {
      systemVs = "vista";
    } else if (testUa(/windows nt 6.1|windows 7/g)) {
      systemVs = "7";
    } else if (testUa(/windows nt 6.2|windows 8/g)) {
      systemVs = "8";
    } else if (testUa(/windows nt 6.3|windows 8.1/g)) {
      systemVs = "8.1";
    } else if (testUa(/windows nt 10.0|windows 10/g)) {
      systemVs = "10";
    }
  } else if (system === "macos") {
    systemVs = testVs(/os x [\d._]+/g);
  } else if (system === "android") {
    systemVs = testVs(/android [\d._]+/g);
  } else if (system === "ios") {
    systemVs = testVs(/os [\d._]+/g);
  }

  let platform = "unknow";
  if (system === "windows" || system === "macos" || system === "linux") {
    platform = "desktop"; // 桌面端
  } else if (system === "android" || system === "ios" || testUa(/mobile/g)) {
    platform = "mobile"; // 移动端
  }

  let engine = "unknow";
  let supporter = "unknow";
  if (testUa(/applewebkit/g)) {
    engine = "webkit"; // webkit内核
    if (testUa(/edge/g)) {
      supporter = "edge"; // edge浏览器
    } else if (testUa(/opr/g)) {
      supporter = "opera"; // opera浏览器
    } else if (testUa(/chrome/g)) {
      supporter = "chrome"; // chrome浏览器
    } else if (testUa(/safari/g)) {
      supporter = "safari"; // safari浏览器
    }
  } else if (testUa(/gecko/g) && testUa(/firefox/g)) {
    engine = "gecko"; // gecko内核
    supporter = "firefox"; // firefox浏览器
  } else if (testUa(/presto/g)) {
    engine = "presto"; // presto内核
    supporter = "opera"; // opera浏览器
  } else if (testUa(/trident|compatible|msie/g)) {
    engine = "trident"; // trident内核
    supporter = "iexplore"; // iexplore浏览器
  }

  // 内核版本
  let engineVs = "unknow";
  if (engine === "webkit") {
    engineVs = testVs(/applewebkit\/[\d._]+/g);
  } else if (engine === "gecko") {
    engineVs = testVs(/gecko\/[\d._]+/g);
  } else if (engine === "presto") {
    engineVs = testVs(/presto\/[\d._]+/g);
  } else if (engine === "trident") {
    engineVs = testVs(/trident\/[\d._]+/g);
  }

  // 载体版本
  let supporterVs = "unknow";
  if (supporter === "chrome") {
    supporterVs = testVs(/chrome\/[\d._]+/g);
  } else if (supporter === "safari") {
    supporterVs = testVs(/version\/[\d._]+/g);
  } else if (supporter === "firefox") {
    supporterVs = testVs(/firefox\/[\d._]+/g);
  } else if (supporter === "opera") {
    supporterVs = testVs(/opr\/[\d._]+/g);
  } else if (supporter === "iexplore") {
    supporterVs = testVs(/(msie [\d._]+)|(rv:[\d._]+)/g);
  } else if (supporter === "edge") {
    supporterVs = testVs(/edge\/[\d._]+/g);
  }

  let shell = "none";
  let shellVs = "unknow";
  if (testUa(/micromessenger/g)) {
    shell = "wechat"; // 微信浏览器
    shellVs = testVs(/micromessenger\/[\d._]+/g);
  } else if (testUa(/qqbrowser/g)) {
    shell = "qq"; // QQ浏览器
    shellVs = testVs(/qqbrowser\/[\d._]+/g);
  } else if (testUa(/ucbrowser/g)) {
    shell = "uc"; // UC浏览器
    shellVs = testVs(/ucbrowser\/[\d._]+/g);
  } else if (testUa(/qihu 360se/g)) {
    shell = "360"; // 360浏览器(无版本)
  } else if (testUa(/2345explorer/g)) {
    shell = "2345"; // 2345浏览器
    shellVs = testVs(/2345explorer\/[\d._]+/g);
  } else if (testUa(/metasr/g)) {
    shell = "sougou"; // 搜狗浏览器(无版本)
  } else if (testUa(/lbbrowser/g)) {
    shell = "liebao"; // 猎豹浏览器(无版本)
  } else if (testUa(/maxthon/g)) {
    shell = "maxthon"; // 遨游浏览器
    shellVs = testVs(/maxthon\/[\d._]+/g);
  }

  return Object.assign(
    {
      engine, // webkit gecko presto trident
      engineVs,
      platform, // desktop mobile
      supporter, // chrome safari firefox opera iexplore edge
      supporterVs,
      system, // windows macos linux android ios
      systemVs,
    },
    shell === "none"
      ? {}
      : {
          shell, // wechat qq uc 360 2345 sougou liebao maxthon
          shellVs,
        }
  );
}
Last Updated: 4/12/2021, 4:41:52 PM