Skip to content

实用 js 工具方法

JS

获取数据类型

js
function mTypeof(value) {
  return value instanceof Element
    ? 'element'
    : Object.prototype.toString
        .call(value)
        .replace(/\[object\s(.+)\]/, '$1')
        .toLowerCase()
}

可根据需要排除对 Element 类型的判断

节流函数

js
function throttle(fn, interval = 300, _this) {
  let prev = +new Date() // 开始时间 ms
  return function (...args) {
    let curr = +new Date() // 当前时间 ms
    if (curr - prev > interval) {
      // 超出间隔,执行
      prev = curr
      _this ? fn.call(_this, ...args) : fn(...args)
    }
  }
}

防抖函数

js
function debounce(fn, delay = 300, _this) {
  let timer // 计时器
  return function (...args) {
    timer && clearTimeout(timer) // 清除delay延时内存在的计时器
    // 延时执行fn
    timer = setTimeout(() => {
      _this ? fn.call(_this, ...args) : fn(...args)
    }, delay)
  }
}

简单对象深拷贝

js
function simpleDeepClone(obj) {
  return JSON.parse(JSON.stringify(obj))
}
缺陷
  • 无法复制 Symbol、函数、undefined、NaN、正则表达式和 Map/Set 等特殊值,会丢失;
  • 无法复制对象的原型链;
  • 性能较低;
  • 无法处理函数和正则表达式的属性,会转换为字符串。

对象深拷贝

递归地拷贝,不同类型自定义不同的拷贝方法

js
function mTypeof(value) {
  return value instanceof Element
    ? 'element'
    : Object.prototype.toString
        .call(value)
        .replace(/\[object\s(.+)\]/, '$1')
        .toLowerCase()
}

function deepClone(val) {
  switch (mTypeof(val)) {
    case 'array':
      return val.map(v => deepClone(v))
    case 'object':
      return Object.keys(val).reduce((prev, curr) => {
        prev[curr] = deepClone(val[curr])
        return prev
      }, {})
    case 'map':
      const tmpMap = new Map()
      val.forEach((v, k) => tmpMap.set(k, deepClone(v)))
      return tmpMap
    case 'set':
      const tmpSet = new Set()
      for (let v of val.values()) tmpSet.add(deepClone(v))
      return tmpSet
    case 'regexp':
      return new RegExp(val)
    case 'date':
      return new Date(val.valueOf())
    case 'function':
      return new Function('return ' + val.toString()).call(this)
    case 'string':
    case 'number':
    case 'boolean':
    case 'null':
    case 'undefined':
    default:
      return val
  }
}

数组去重

js
function removeDuplicates(arr) {
  if (!arr || !(arr instanceof Array)) return arr
  return [...new Set(arr)]
}

async...await... 异常捕捉封装

js
function asyncFuncWrapper(fn, ...args) {
  try {
    const resp = await fn(...args)
    return({ resp })
  } catch (err) {
    return { err }
  }
}

详见async...await...异常捕捉封装

生成随机字符串

js
const randomString = (len) => {
  const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz123456789'
  const strLen = chars.length
  let randomStr = ''
  for (let i = 0; i < len; i++) {
    randomStr += chars.charAt(Math.floor(Math.random() * strLen))
  }
  return randomStr
}

/**
 * 生成UUID类型随机字符串
 *
 * 格式:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
 * x表示任意十六进制数字,y表示(8, 9, A, 或 B)这四个十六进制数字之一
 * 根据 UUID 规范,第 13 位必须是固定的数字 4,而第 17 位必须是 8、9、A 或 B 中的一个
 * @returns {string}
 */
function generateUUID() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    // r                : 随机数 0-15
    // "r & 0x3"        : 只保留 r 后两位,[0000,0011]
    // "(r & 0x3) | 0x8": 第四位设置为 1,[1000,1011]
    let r = (Math.random() * 16) | 0,
      v = c === 'x' ? r : (r & 0x3) | 0x8
    return v.toString(16)
  })
}

字符串首字母大写

js
const firstLetterUpper = (str) => {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

生成指定范围内的随机数 [min,max]

js
const randomNum = (min, max) => {
  return Math.floor(Math.random() * (max - min + 1)) + min
}

均等概率得生成 min - max 之间的随机整数

打乱数组顺序

js
const shuffleArray = (arr) => {
  arr.sort(() => 0.5 - Math.random())
  // return arr.toSorted(() => 0.5 - Math.random())
}

直接更改原数组。也可以选择不更改原数组,返回乱序后的数组。

格式化货币,千分位数字

js
const formatMoney = (money) => {
  return money.toLocaleString()
}

正则表达式实现

js
const formatMoneyReg = (money) => {
  money = String(money)
  return money.replace(new RegExp(`(?!^)(?=(\\d{3})+${money.includes('.') ? '\\.' : '$'})`, 'g'), ',')  
}

手动延时

js
function manualDelay(val, delay = 2000) {
  return new Promise((resolve, reject) => {
    setTimeout(val ? resolve : reject, delay, val)
  })  
}

base64编码、解码(含中文)

中文的字符串base64编码、解码

生成颜色渐变列表

js
/**
 * JavaScript 计算两个颜色之间的渐变色值
 *
 * @author kmxz
 * @see https://www.zhihu.com/question/38869928/answer/78527903
 * @param {string} start - 渐变的起始颜色
 * @param {string} end - 渐变的结束颜色
 * @param {number} steps - 渐变中的颜色总数,表示生成几种颜色
 * @param {number} [gamma=1] - 控制颜色渐变的非线性透明度,默认值为 1
 * @returns {string[]} - 返回一个包含渐变颜色的数组,每个颜色的格式为十六进制字符串
 * @example
 * gradientColors('#000', '#fff', 100, 2.2)
 * gradientColors('#0000ff', '#ff0000', 10)
 */
function gradientColors(start, end, steps, gamma = 1) {
  let i, j, ms, me, output = [], so = []
  const normalize = channel => Math.pow(channel / 255, gamma)
  // convert #hex notation to rgb array
  const parseColor = hexStr =>
    hexStr.length === 4
      ? hexStr
          .substr(1)
          .split('')
          .map(function (s) {
            return 0x11 * parseInt(s, 16)
          })
      : [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(s => parseInt(s, 16))
  // zero-pad 1 digit to 2
  const pad = s => (s.length === 1 ? '0' + s : s)

  start = parseColor(start).map(normalize)
  end = parseColor(end).map(normalize)
  for (i = 0; i < steps; i++) {
    ms = i / (steps - 1)
    me = 1 - ms
    for (j = 0; j < 3; j++) {
      so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16))
    }
    output.push('#' + so.join(''))
  }
  return output
}

对象转formdata格式

js
/**
 * 对象转formdata格式
 *
 * @param {Object} obj
 * @returns
 */
function objToFormData(obj) {
  const formData = new FormData()
  if (typeof obj !== 'object') return formData
  Object.entries(obj).forEach(([k, v]) => {
    switch (mTypeof(v)) {
      case 'string':
      case 'number':
      case 'boolean':
      case 'file':
      case 'blob':
      case 'regexp':
      case 'date':
        formData.append(k, v)
        break
      case 'object':
        formData.append(k, JSON.stringify(v))
        break
      case 'array':
        v.forEach(p => formData.append(k, p))
        break
      case 'set':
        for (let p of v) formData.append(k, p)
        break
      case 'null':
      case 'undefined':
        formData.append(k, '')
        break
      case 'map':
      case 'function':
      default:
        break
    }
  })
  return formData
}

function mTypeof(value) {
  return value instanceof Element
    ? 'element'
    : Object.prototype.toString
        .call(value)
        .replace(/\[object\s(.+)\]/, '$1')
        .toLowerCase()
}

DOM

滚动到页面顶部

js
function scrollToTop() {
  window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
}

滚动到页面底部

js
function scrollToBottom() {
  window.scrollTo({
    top: document.documentElement.offsetHeight,
    left: 0,
    behavior: 'smooth'
  })
}

全屏显示元素

js
function goToFullScreen(element) {
  element = element || document.body
  if (element.requestFullscreen) {
    element.requestFullscreen()
  } else if (element.mozRequestFullScreen) {
    element.mozRequestFullScreen()
  } else if (element.msRequestFullscreen) {
    element.msRequestFullscreen()
  } else if (element.webkitRequestFullscreen) {
    element.webkitRequestFullscreen()
  }
}

退出浏览器全屏状态

js
function goExitFullscreen() {
  if (document.exitFullscreen) {
    document.exitFullscreen();
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen();
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen();
  } else if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen();
  }
}

设备类型判断

js
const isMobile = () => {
  return !!navigator.userAgent.match(
    /(iPhone|iPod|Android|ios|iOS|iPad|Backerry|WebOS|Symbian|Windows Phone|Phone)/i
  )
}

const isAndroid = () => {
  return /android/i.test(navigator.userAgent.toLowerCase())
}

const isIOS = () => {
  let reg = /iPhone|iPad|iPod|iOS|Macintosh/i
  return reg.test(navigator.userAgent.toLowerCase())
}

复制字符串至剪切板

js
function copyText(text) {
  const textarea = document.createElement('input') //创建input对象
  const currentFocus = document.activeElement //当前获得焦点的元素
  document.body.appendChild(textarea) //添加元素
  textarea.value = text
  textarea.focus()
  if (textarea.setSelectionRange)
    textarea.setSelectionRange(0, textarea.value.length) //获取光标起始位置到结束位置
  else textarea.select()
  let flag
  try {
    flag = document.execCommand('copy') //执行复制
  } catch (err) {
    flag = false
  }
  document.body.removeChild(textarea) //删除元素
  currentFocus.focus()
  return flag
}