常用工具函数助力开发

146 阅读2分钟

公共

获取数据类型

function getType(val) {
  const lowerCaseType = Function.prototype.call.bind(Object.prototype.toString);
  return lowerCaseType(val).replace(/\[object (\w+)\]/, "$1").toLowerCase()
}

移出事件监听

document.getElementById("button").addEventListener("click", () => {
  console.log("clicked!");
});

// 方法一 - removeEventListener
document.getElementById("button").removeEventListener("click", () => {
  console.log("clicked!");
});

// 方法一优化:
const cb = () => {
  console.log("clicked!");
};

document.getElementById("button").addEventListener("click", cb);
document.getElementById("button").removeEventListener("click", cb);

// 方法二 - 设置只会调用一次
document.getElementById("button").addEventListener(
  "click",
  () => {
    console.log("clicked!");
  },
  { once: true }
);

// 方法三 - AbortController
const button = document.getElementById("button");
const ct = new AbortController();
const { signal } = ct;

button.addEventListener("click", () => console.log("clicked!"), { signal });
window.addEventListener("resize", () => console.log("resized!"), { signal });
document.addEventListener("keyup", () => console.log("pressed!"), { signal });

// Remove all listeners at once:
controller.abort();

数字

金额格式化

// 数字格式化 3000 -> 3,000
function formatNumber(str) {
  return ("" + str).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  // number [number].toLocaleString('en-US')
}

// {number} number:要格式化的数字
// {number} decimals:保留几位小数
// {string} dec_point:小数点符号
// {string} thousands_sep:千分位符号

const moneyFormat = (number, decimals, dec_point, thousands_sep) => {
  number = (number + "").replace(/[^0-9+-Ee.]/g, "");
  const n = !isFinite(+number) ? 0 : +number;
  const pre = !isFinite(+decimals) ? 2 : Math.abs(decimals);
  const sep = typeof thousands_sep === "undefined" ? "," : thousands_sep;
  const dec = typeof dec_point === "undefined" ? "." : dec_point;
  let s = "";
  const toFixedFix = function (n, pre) {
    const k = Math.pow(10, pre);
    return "" + Math.ceil(n * k) / k;
  };
  s = (pre ? toFixedFix(n, pre) : "" + Math.round(n)).split(".");
  const re = /(-?\d+)(\d{3})/;
  while (re.test(s[0])) {
    s[0] = s[0].replace(re, "$1" + sep + "$2");
  }

  if ((s[1] || "").length < pre) {
    s[1] = s[1] || "";
    s[1] += new Array(pre - s[1].length + 1).join("0");
  }
  return s.join(dec);
};

对象

深拷贝

需要用到 utils.getType 函数。

function cloneDeep(obj) {
  // 维护两个储存循环引用的数组
  const parents = []
  const children = []
  const _clone = obj => {
    if (obj === null) return null;
    const parentType = getType(obj);
    if (parentType !== 'object') return obj

    let child, proto;

    if (parentType === 'array') {
      // 对数组做特殊处理
      child = []
    } else if (parentType === 'regexp') {
      // 对正则对象做特殊处理
      child = new RegExp(obj.source, getRegExp(obj))
      if (obj.lastIndex) child.lastIndex = obj.lastIndex
    } else if (parentType === 'date') {
      // 对Date对象做特殊处理
      child = new Date(obj.getTime())
    } else {
      // 处理对象原型
      proto = Object.getPrototypeOf(obj)
      // 利用Object.create切断原型链
      child = Object.create(proto)
    }

    // 处理循环引用
    const index = parents.indexOf(obj)

    if (index !== -1) {
      // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
      return children[index]
    }
    parents.push(obj)
    children.push(child)

    for (const i in obj) {
      // 递归
      child[i] = _clone(obj[i])
    }
    return child
  }
  return _clone(obj)
}

比较对象是否相同

通常我们可以直接使用 lodash.isEqual 方法。

function deepCompare(x, y) {
  let i, l, leftChain, rightChain;
  function compare2Objects(x, y) {
    let p;
    if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
      return true;
    }

    if (x === y) {
      return true;
    }

    if ((typeof x === 'function' && typeof y === 'function') ||
      (x instanceof Date && y instanceof Date) ||
      (x instanceof RegExp && y instanceof RegExp) ||
      (x instanceof String && y instanceof String) ||
      (x instanceof Number && y instanceof Number)) {
      return x.toString() === y.toString();
    }

    if (!(x instanceof Object && y instanceof Object)) {
      return false;
    }

    if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
      return false;
    }

    if (x.constructor !== y.constructor) {
      return false;
    }

    if (x.prototype !== y.prototype) {
      return false;
    }

    if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
      return false;
    }

    for (p in y) {
      if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
        return false;
      } else if (typeof y[p] !== typeof x[p]) {
        return false;
      }
    }

    for (p in x) {
      if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
        return false;
      } else if (typeof y[p] !== typeof x[p]) {
        return false;
      }

      switch (typeof (x[p])) {
        case 'object':
        case 'function':
          leftChain.push(x);
          rightChain.push(y);

          if (!compare2Objects(x[p], y[p])) {
            return false;
          }
          leftChain.pop();
          rightChain.pop();
          break;
        default:
          if (x[p] !== y[p]) {
            return false;
          }
          break;
      }
    }
    return true;
  }

  if (arguments.length < 1) {
    return true;
  }

  for (i = 1, l = arguments.length; i < l; i++) {
    leftChain = [];
    rightChain = [];
    if (!compare2Objects(arguments[0], arguments[i])) {
      return false;
    }
  }
  return true;
}

数组

乱序

function shuffle(arr) {
  let i = arr.length;
  while (i) {
    let j = Math.floor(Math.random() * i--);
    [arr[i], arr[j]] = [arr[j], arr[i]]
  }
  return arr
}

获取最后一项

function getLstMember(arr) {
//   return arr.slice(-1)[0]
  return arr.at(-1)
}

获取交集

需要对数组项进行比较,用到了 obj.deepCompare

// 基础类型
function getIntersection(left, right) {
  return left.filter(val => right.indexOf(val) > -1)
}
// 对象
function getIntersectionComplex(arr1, arr2) {
  return arr2.filter(v => arr1.some(n => deepCompare(n, v)))
}

去重

// 基础类型
function unique(arr) {
  const obj = {};
  return arr.filter(ele => {
    if (!obj[ele]) {
      obj[ele] = true;
      return true;
    }
  })
}

// 根据字段
function uniqueByKey(arr, key) {
  const res = new Map();
  return arr.filter((item) => !res.has(item[key]) && res.set(item[key], 1));
}

获取并集

// 基础类型
function getUnion(left, right) {
  return left.concat(right.filter(v => !left.includes(v)))
}

获取差集

// 数组差集 数组arr1相对于arr2所没有的
function getDiff(arr1, arr2) {
  return arr1.filter(item => !new Set(arr2).has(item))
}
// 数组差集 object[]
function getDiffObjectArray(arr1, arr2) {
  return arr1.filter(function (v) {
    return arr2.every(n => JSON.stringify(n) !== JSON.stringify(v))
  })
}

获取补集

// 基础类型
function getComplement(left, right) {
  return Array.from(new Set(left.concat(right).filter(v => !new Set(left).has(v) || !new Set(right).has(v))))
}

// 对象
function getComplementComplex(arr1, arr2) {
  let arr3 = arr1.concat(arr2);
  return arr3.filter(function (v) {
    return arr1.every(n => JSON.stringify(n) !== JSON.stringify(v)) || arr2.every(n => JSON.stringify(n) !== JSON.stringify(v))
  })
}

计算百分比

常用与饼状图等

// 计算合计
function calcObjArrTotal(arr, key = 'value') {
  return arr.reduce((pre, next) => {
    return pre + next[key]
  }, 0)
}
// 计算百分比
function fixObjArrRate(arr, key = 'value') {
  let max = 0, maxIdx = 0;

  const calcFloat = num => Math.floor(num * 100) / 100

  const total = calcObjArrTotal(arr, key = 'value');

  const scraps = 100 - arr.reduce((pre, next, index) => {
    if (arr[index].value > max) {
      max = arr[index].value;
      maxIdx = index
    }
    arr[index].rate = calcFloat(next[key] * 100 / total);
    return pre + arr[index].rate
  }, 0)
  arr[maxIdx].rate = + (scraps + arr[maxIdx].rate).toFixed(2)
  return arr
}

字符串

对象与字符串

// 对象转url
function stringifyUrl(search = {}) {
  return Object.entries(search)
    .reduce((t, v) => `${t}${v[0]}=${encodeURIComponent(v[1])}&`, Object.keys(search).length ? "?" : "")
    .replace(/&$/, "");
}

// 字符串转对象
function getUrlParams(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;
}

// 获取地址栏参数
function getParamByUrl(key){
  const url = new URL(location.href);
  return url.searchParams.get(key);
};

脱敏

// 手机号脱敏
function hideMobile(mobile) {
  return mobile.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2")
}

其他

下载文件

function download(url, fileName) {
    const x = new window.XMLHttpRequest()
    x.open('GET', url, true)
    x.responseType = 'blob'
    x.onload = () => {
      const url = window.URL.createObjectURL(x.response)
      const a = document.createElement('a')
      a.href = url
      a.download = fileName
      a.click()
    }
    x.send()
  }
  
// downloadFile('/api/download', {id}, '文件名')
const downloadFile = (api, params, fileName, type = "get") => {
  axios({
    method: type,
    url: api,
    responseType: "blob",
    params: params,
  })
    .then((res) => {
      let str = res.headers["content-disposition"];
      if (!res || !str) {
        return;
      }
      let suffix = "";
      // 截取文件名和文件类型
      if (str.lastIndexOf(".")) {
        fileName
          ? ""
          : (fileName = decodeURI(
              str.substring(str.indexOf("=") + 1, str.lastIndexOf("."))
            ));
        suffix = str.substring(str.lastIndexOf("."), str.length);
      }
      //  如果支持微软的文件下载方式(ie10+浏览器)
      if (window.navigator.msSaveBlob) {
        try {
          const blobObject = new Blob([res.data]);
          window.navigator.msSaveBlob(blobObject, fileName + suffix);
        } catch (e) {
          console.log(e);
        }
      } else {
        //  其他浏览器
        let url = window.URL.createObjectURL(res.data);
        let link = document.createElement("a");
        link.style.display = "none";
        link.href = url;
        link.setAttribute("download", fileName + suffix);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        window.URL.revokeObjectURL(link.href);
      }
    })
    .catch((err) => {
      console.log(err.message);
    });
};

全屏

// 目标元素全屏
// launchFullScreen(document.getElementById('#app'))
function launchFullScreen(element) {
  if (element.requestFullscreen) {
    element.requestFullscreen();
  } else if (element.mozRequestFullScreen) {
    element.mozRequestFullScreen();
  } else if (element.msRequestFullscreen) {
    element.msRequestFullscreen();
  } else if (element.webkitRequestFullscreen) {
    element.webkitRequestFullScreen();
  }
  return false
}

// 退出全屏
function exitFullScreen() {
  if (document.exitFullscreen) {
    document.exitFullscreen();
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen();
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen();
  } else if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen();
  }
}

获取上一页url

function getLastPageUrl() {
  return document.referrer;
}

获取指定父节点

function findParent(dom, className) {
  let parent = dom.parentElement;
  if (dom && parent.className !== className) {
    parent = findParent(dom.parentElement, className);
  }
  return parent;
}

创建链接字符串

function createLinkStr(str, url) {
  return str.link(url); // `<a href="www.google.com">google</a>`
}

复制、粘贴

// 复制
async function performCopy(event) {
  event.preventDefault();
  if (
    navigator.clipboard &&
    navigator.clipboard.read &&
    navigator.clipboard.write
  ) {
    try {
      await navigator.clipboard.writeText(copyText);
      console.log(`${copyText} copied to clipboard`);
    } catch (err) {
      console.error("Failed to copy: ", err);
    }
  }
}

// 粘贴
async function performPaste(event, target) {
  event.preventDefault();
  if (
    !(
      navigator.clipboard &&
      navigator.clipboard.read &&
      navigator.clipboard.write
    )
  ) {
    return false;
  }
  try {
    const text = await navigator.clipboard.readText();
    // 赋值
    target.value = text
    console.log("Pasted content: ", text);
  } catch (err) {
    console.error("Failed to read clipboard contents: ", err);
  }
}

获取图片原始宽高

function getImageNatural(img, cb) {
  if (img.naturalWidth) {
    // 现代浏览器
    nWidth = img.naturalWidth;
    nHeight = img.naturalHeight;
    cb({ w: nWidth, h: nHeight });
  } else {
    // IE6/7/8
    var image = new Image();
    image.src = img.attr("src");
    if (image.complete) {
      cb({ w: image.width, h: image.height });
    } else {
      image.onload = function () {
        var w = image.width;
        var h = image.height;
        cb({ w: w, h: h });
      };
    }
  }
}

LocalStorage 缓存

class CustomCache {
  constructor(isLocal = true) {
    this.storage = isLocal ? localStorage : sessionStorage;
  }

  setItem(key, value) {
    if (typeof value === "object") value = JSON.stringify(value);
    this.storage.setItem(key, value);
  }

  getItem(key) {
    try {
      return JSON.parse(this.storage.getItem(key));
    } catch (err) {
      return this.storage.getItem(key);
    }
  }

  removeItem(key) {
    this.storage.removeItem(key);
  }

  clear() {
    this.storage.clear();
  }

  key(index) {
    return this.storage.key(index);
  }

  length() {
    return this.storage.length;
  }
}

const localCache = new CustomCache();
const sessionCache = new CustomCache(false);