Skip to content
On this page

工具示例

深拷贝

js
const mapTag = '[object Map]'
const setTag = '[object Set]'
const arrayTag = '[object Array]'
const objectTag = '[object Object]'
const argsTag = '[object Arguments]'

const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const numberTag = '[object Number]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const errorTag = '[object Error]'
const regexpTag = '[object RegExp]'
const funcTag = '[object Function]'

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]

function forEach(array, iteratee) {
  let index = -1
  const length = array.length
  while (++index < length) {
    iteratee(array[index], index)
  }
  return array
}

function isObject(target) {
  const type = typeof target
  return target !== null && (type === 'object' || type === 'function')
}

function getType(target) {
  return Object.prototype.toString.call(target)
}

function getInit(target) {
  const Ctor = target.constructor
  return new Ctor()
}

function cloneSymbol(targe) {
  return Object(Symbol.prototype.valueOf.call(targe))
}

function cloneReg(targe) {
  const reFlags = /\w*$/
  const result = new targe.constructor(targe.source, reFlags.exec(targe))
  result.lastIndex = targe.lastIndex
  return result
}

function cloneFunction(func) {
  const bodyReg = /(?<={)(.|\n)+(?=})/m
  const paramReg = /(?<=\().+(?=\)\s+{)/
  const funcString = func.toString()
  if (func.prototype) {
    const param = paramReg.exec(funcString)
    const body = bodyReg.exec(funcString)
    if (body) {
      if (param) {
        const paramArr = param[0].split(',')
        return new Function(...paramArr, body[0])
      } else {
        return new Function(body[0])
      }
    } else {
      return null
    }
  } else {
    return eval(funcString)
  }
}

function cloneOtherType(targe, type) {
  const Ctor = targe.constructor
  switch (type) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag:
    case dateTag:
      return new Ctor(targe)
    case regexpTag:
      return cloneReg(targe)
    case symbolTag:
      return cloneSymbol(targe)
    case funcTag:
      return cloneFunction(targe)
    default:
      return null
  }
}

function clone(target, map = new WeakMap()) {
  // 克隆原始类型
  if (!isObject(target)) {
    return target
  }

  // 初始化
  const type = getType(target)
  let cloneTarget
  if (deepTag.includes(type)) {
    cloneTarget = getInit(target, type)
  } else {
    return cloneOtherType(target, type)
  }

  // 防止循环引用
  if (map.get(target)) {
    return map.get(target)
  }
  map.set(target, cloneTarget)

  // 克隆set
  if (type === setTag) {
    target.forEach(value => {
      cloneTarget.add(clone(value, map))
    })
    return cloneTarget
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((value, key) => {
      cloneTarget.set(key, clone(value, map))
    })
    return cloneTarget
  }

  // 克隆对象和数组
  const keys = type === arrayTag ? undefined : Object.keys(target)
  forEach(keys || target, (value, key) => {
    if (keys) {
      key = value
    }
    cloneTarget[key] = clone(target[key], map)
  })

  return cloneTarget
}

将数组平铺

js
const flatten = arr => (Array.isArray(arr) ? arr.reduce((a, b) => a.concat(flatten(b)), []) : [arr])

console.log(flatten([1, 2, 3, [4, 5], [6, [7, 8, [9, 10]]]]))
js
var foo0 = [1, [2, 3], [4, 5, [6, 7, [8]]], [9], 10]

var foo1 = foo0.join(',').split(',')

console.log(foo1) //  ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']

统计字符串中相同字符出现的次数

js
const countChar = (str, char) => str.split(char).length - 1

console.log(countChar('hello world', 'o')) // 2
js
var arr = 'abcdaabc'

var info = arr.split('').reduce((p, k) => (p[k]++ || (p[k] = 1), p), {})

console.log(info) //{ a: 3, b: 2, c: 2, d: 1 }
js
function getCookie(name) {
  var value = '; ' + document.cookie
  var parts = value.split('; ' + name + '=')
  if (parts.length == 2) return parts.pop().split(';').shift()
}

console.log(getCookie('name')) // 输出在Cookie中名为name的值
js
const clearCookies = document.cookie
  .split(';')
  .forEach(
    cookie =>
      (document.cookie = cookie
        .replace(/^ +/, '')
        .replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`))
  )

颜色 RGB 转十六进制

js
const rgbToHex = (r, g, b) => '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)

rgbToHex(0, 51, 255)
// Result: #0033ff

颜色十六进制转 RGB

js
const hexToRgb = hex =>
  hex.replace(
    /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
    (m, r, g, b) =>
      '0' +
      parseInt(r, 16).toString(10) +
      '0' +
      parseInt(g, 16).toString(10) +
      '0' +
      parseInt(b, 16).toString(10)
  )

hexToRgb('#0033ff')
// Result: "0,51,255"

生成随机十六进制颜色

js
const randomHexColor = () => {
  return `#${Math.floor(Math.random() * 16777215).toString(16)}`
}

randomHexColor() // 生成随机颜色
js
const randomHex = () =>
  `#${Math.floor(Math.random() * 0xffffff)
    .toString(16)
    .padEnd(6, '0')}`

console.log(randomHex())
// Result: #92b008

生成随机 RGB 颜色

js
const randomRgbColor = () =>
  `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
    Math.random() * 256
  )})`

randomRgbColor() // 生成随机颜色

日期格式化

js
const dateFormat = (date, format) => {
  if (!date) return
  if (!format) format = 'YYYY-MM-DD HH:mm:ss'
  const obj = {
    YYYY: date.getFullYear(),
    MM: date.getMonth() + 1,
    DD: date.getDate(),
    HH: date.getHours(),
    mm: date.getMinutes(),
    ss: date.getSeconds()
  }
  for (let i in obj) {
    format = format.replace(i, obj[i])
}
return format

时间戳转日期

javascript
/**
 * 将时间戳转换为格式化的日期时间字符串
 * @param {number|string} timestamp 时间戳(支持秒或毫秒)
 * @param {string} [format='YYYY-MM-DD hh:mm:ss'] 自定义格式,默认:YYYY-MM-DD hh:mm:ss
 * @returns {string} 格式化后的日期时间字符串
 */
function timestampToDateTime(timestamp, format = 'YYYY-MM-DD hh:mm:ss') {
  // 处理不同格式的时间戳(支持秒和毫秒)
  const ts = Number(timestamp) * (timestamp < 9999999999 ? 1000 : 1)
  const date = new Date(ts)

  // 验证日期有效性
  if (isNaN(date.getTime())) {
    throw new Error('Invalid timestamp')
  }

  // 日期组件集合
  const components = {
    YYYY: date.getFullYear(), // 年
    MM: padZero(date.getMonth() + 1), // 月(补零)
    DD: padZero(date.getDate()), // 日(补零)
    hh: padZero(date.getHours()), // 时(补零)
    mm: padZero(date.getMinutes()), // 分(补零)
    ss: padZero(date.getSeconds()), // 秒(补零)
    SSS: String(date.getMilliseconds()).padStart(3, '0'), // 毫秒(补零)
    AP: date.getHours() >= 12 ? 'PM' : 'AM', // 上午/下午
    HH: padZero(date.getHours() % 12 || 12), // 12小时制(补零)
  }

  // 格式替换(支持任意组合)
  return format.replace(/YYYY|MM|DD|hh|mm|ss|SSS|AP|HH/g, match => components[match])
}

// 补零函数(私有)
function padZero(n) {
  return n.toString().padStart(2, '0')
}

/* 使用示例 */
console.log(timestampToDateTime(1626986400)) // 秒级时间戳 → "2021-07-22 12:40:00"
console.log(timestampToDateTime(1626986400000)) // 毫秒级时间戳 → "2021-07-22 12:40:00"
console.log(timestampToDateTime(Date.now(), 'YYYY年MM月DD日 HH:mm:ss AP')) // → "2023年08月15日 03:30:45 PM"
console.log(timestampToDateTime(1672502400000, 'YYYY/MM/DD hh:mm:ss.SSS')) // → "2022/12/31 16:00:00.000"

查找日期位于一年中的第几天

js
const dayOfYear = date =>
  Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24)

dayOfYear(new Date())
// Result: 365, (or 366 in leap years)

计算 2 个日期之间相差多少天

js
const getDaysDiff = (dateInitial, dateFinal) =>
  Math.round(Math.abs((dateFinal - dateInitial) / (1000 * 3600 * 24)))

getDaysDiff(new Date('2017-12-31'), new Date('2018-01-01')) // 1
js
const dayDif = (date1, date2) => Math.ceil(Math.abs(date1.getTime() - date2.getTime()) / 86400000)

dayDif(new Date('2020-10-21'), new Date('2021-10-22'))
// Result: 366

检查日期是否为周末

js
const isWeekend = date => date.getDay() === 0 || date.getDay() === 6

isWeekend(new Date()) // true (it's weekend)

数组去重

js
const uniqueArray = array => Array.from(new Set(array))

uniqueArray([1, 2, 2, 3, 4, 4, 5]) // [1, 2, 3, 4, 5]

从 URL 获取查询参数

js
const getQueryParams = url =>
  (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce(
    (a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a),
    {}
  )

getQueryParams('https://example.com/?a=1&b=2&a=3') // { a: '3', b: '2' }

检查对象是否为空

js
const isEmptyObject = obj => obj.constructor === Object && Reflect.ownKeys(obj).length === 0

isEmptyObject({}) // true
isEmptyObject({ a: 1 }) // false

翻转字符串

js
const reverseString = str => str.split('').reverse().join('')

reverseString('foobar') // 'raboof'

校验数组是否为空

js
const isArrayEmpty = arr => Array.isArray(arr) && arr.length === 0

isArrayEmpty([]) // true
isArrayEmpty([1, 2, 3]) // false

获取用户选择的文本

js
const getSelectedText = () =>
  (window.getSelection && window.getSelection().toString()) ||
  (document.selection &&
    document.selection.type != 'Control' &&
    document.selection.createRange().text)

getSelectedText() // "Hello World!"

获取变量的类型

js
const getType = obj => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()

getType('') // string
getType(0) // number
getType() // undefined
getType(null) // null
getType({}) // object
getType([]) // array
getType(0) // number
getType(() => {}) // function
getType(true) // boolean

快速取整

js
console.log(~~47.11) // -> 47

console.log(~~1.9999) // -> 1

console.log(~~[]) // -> 0
console.log(~~{}) // -> NaN

------------console.log(20.15|0);          // -> 20
console.log((-20.15)|0);       // -> -20
------------console.log(20.15^0);          // -> 20
console.log((-20.15)^0);       // -> -20
------------console.log(20.15 < < 0);     // -> 20
console.log((-20.15) < < 0);  //-20


单行写一个评级组件

js
const rate = (num) => {
  let str = '';
  for (let i = 0; i < num; i++) {
    str += '';
  }
  for (let i = 0; i < 5 - num; i++) {
    str += '';
  }
  return str;
}

----

"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);

金钱格式化 1234567890 --> 1,234,567,890

js
const formatMoney = num => {
  return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}

formatMoney(1234567890) // 输出: "1,234,567,890"

防抖函数以限制它被调用的次数

js
function debounce(func, wait) {
  let timeout
  return function () {
    const context = this
    const args = arguments
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      func.apply(context, args)
    }, wait)
  }
}

节流函数以限制用它的速率

js
function throttle(func, limit) {
  let inThrottle
  return function () {
    const args = arguments
    const context = this
    if (!inThrottle) {
      func.apply(context, args)
      inThrottle = true
      setTimeout(() => (inThrottle = false), limit)
    }
  }
}

生成介于最小值和最大值之间的随机数

js
function getRandomInt(min, max) {
  min = Math.ceil(min)
  max = Math.floor(max)
  return Math.floor(Math.random() * (max - min)) + min // 含最大值,不含最小值
}

getRandomInt(1, 10) // 返回 1 到 10(含 1 和 10)之间的整数

重载

js
function addMethod(object, name, fn) {
  var old = object[name]
  object[name] = function () {
    if (fn.length == arguments.length) return fn.apply(this, arguments)
    else if (typeof old == 'function') return old.apply(this, arguments)
  }
}

打印

js
function tap(x, fn = x => x) {
  console.log(fn(x))
  return x
}
tap(5, x => x * 2)

// 例如
bank_totals_by_client(bank_info(1, banks), table)
  .filter(c => c.balance > 25000)
  .sort((c1, c2) => (c1.balance <= c2.balance ? 1 : -1))
  .map(c => console.log(`${c.id} | ${c.tax_number} (${c.name}) => ${c.balance}`))

// 链式调用中没有得到任何返回
// 因此 我们需要监听(tap)它

// bank_totals_by_client(tap(bank_info(1, banks)), table)

// 继续调试下一个函数, filter

//  .filter(c => tap(c).balance > 25000)

//  filter 内的条件
//   .filter(c => tap(c.balance > 25000))

简单监听 DOM 事件

很多人还在这样做:

  • element.addEventListener('type', obj.method.bind(obj))
  • element.addEventListener('type', function (event) {})
  • element.addEventListener('type', (event) => {})

上面所有的例子都创建了一个匿名事件监控句柄,且在不需要时无法删除它。这在你不需要某句柄,而它却被用户或事件冒泡偶然触发时,可能会导致性能问题或不必要的逻辑问题。

更安全的事件处理方式如下:

js
const handler = function () {
  console.log('Tada!')
}
element.addEventListener('click', handler)
// 之后
element.removeEventListener('click', handler)

更好的写法:

js
function handleEvent(eventName, { onElement, withCallback, useCapture = false } = {}, thisArg) {
  const element = onElement || document.documentElement

  function handler(event) {
    if (typeof withCallback === 'function') {
      withCallback.call(thisArg, event)
    }
  }

  handler.destroy = function () {
    return element.removeEventListener(eventName, handler, useCapture)
  }

  element.addEventListener(eventName, handler, useCapture)
  return handler
}

// 你需要的时候
const handleClick = handleEvent('click', {
  onElement: element,
  withCallback: event => {
    console.log('Tada!')
  },
})

// 你想删除它的时候
handleClick.destroy()

浮点运算示例

js
// 加
function add(arg1, arg2) {
  let digits1, digits2, maxDigits
  try {
    digits1 = arg1.toString().split('.')[1].length || 0
  } catch {
    digits1 = 0
  }
  try {
    digits2 = arg2.toString().split('.')[1].length || 0
  } catch {
    digits2 = 0
  }
  maxDigits = 10 ** Math.max(digits1, digits2)
  return (mul(arg1, maxDigits) + mul(arg2, maxDigits)) / maxDigits
}

// 减
function sub(arg1, arg2) {
  let digits1, digits2, maxDigits
  try {
    digits1 = arg1.toString().split('.')[1].length || 0
  } catch {
    digits1 = 0
  }
  try {
    digits2 = arg2.toString().split('.')[1].length || 0
  } catch {
    digits2 = 0
  }
  maxDigits = 10 ** Math.max(digits1, digits2)
  return (mul(arg1, maxDigits) - mul(arg2, maxDigits)) / maxDigits
}

// 乘
function mul(arg1, arg2) {
  let digits = 0
  const s1 = arg1.toString()
  const s2 = arg2.toString()
  try {
    digits += s1.split('.')[1].length
  } catch {}
  try {
    digits += s2.split('.')[1].length
  } catch {}
  return (Number(s1.replace(/\./, '')) * Number(s2.replace(/\./, ''))) / 10 ** digits
}

function div(arg1, arg2) {
  let int1 = 0
  let int2 = 0
  let digits1
  let digits2
  try {
    digits1 = arg1.toString().split('.')[1].length || 0
  } catch (e) {
    digits1 = 0
  }
  try {
    digits2 = arg2.toString().split('.')[1].length || 0
  } catch (e) {
    digits2 = 0
  }
  int1 = Number(arg1.toString().replace(/\./, ''))
  int2 = Number(arg2.toString().replace(/\./, ''))
  return ((int1 / int2) * 10) ** (digits2 - digits1 || 1)
}

四舍五入

js
function round(value: number, precision = 0): number {
  if (!Number.isInteger(precision)) {
    throw new Error('Precision must be an integer.')
  }
  // 乘数 是 10 的 precision 次幂
  const multiplier = Math.pow(10, precision)
  return Math.round(value * multiplier) / multiplier
}

const result1 = round(1.2345) // result1 将会是 1
const result2 = round(1.2345, 2) // result2 将会是 1.23
const result3 = round(1.2345, 3) // result3 将会是 1.235
const result4 = round(1.2345, 3.1) // 这将会抛出一个错误

创建一个新对象,该对象由满足条件的属性组成。

该函数接受一个对象和一个谓词函数,并返回一个新对象,该对象仅包含谓词函数返回 true 的属性。

typeScript

export function pick<T extends Record<string, any>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>;

  for (const key of keys) {
    result[key] = obj[key];
  }

  return result;
}

使用自定义相等性函数计算两个数组之间的对称差异。

对称差异是指在任一数组中存在,但不在它们的交集中的元素集合,根据自定义的相等性函数进行比较。

js
function symmetricDifference(arr1, arr2, isEqual) {
  // 使用扩展运算符...将两个数组合并,得到一个新的数组result
  const result = [...arr1, ...arr2].filter(item => {
    // 使用some方法判断item是否在arr1中存在,如果存在则返回false,否则返回true
    const isItemInArr1 = arr1.some(arrItem => isEqual(item, arrItem))
    // 使用some方法判断item是否在arr2中存在,如果存在则返回false,否则返回true
    const isItemInArr2 = arr2.some(arrItem => isEqual(item, arrItem))

    // 如果item在arr1中存在,但不在arr2中存在,或者item在arr2中存在,但不在arr1中存在,则返回true
    return (isItemInArr1 && !isItemInArr2) || (!isItemInArr1 && isItemInArr2)
  })

  return result
}

const arr1 = [1, 2, 3, 4]
const arr2 = [3, 4, 5, 6]
const result = symmetricDifference(arr1, arr2, (a, b) => a === b)
console.log(result) // 输出: [1, 2, 5, 6]

根据自定义相等性函数,从两个给定的数组创建一个唯一值数组。

例如

js
const array1 = [{ id: 1 }, { id: 2 }]
const array2 = [{ id: 2 }, { id: 3 }]
const areItemsEqual = (a, b) => a.id === b.id
const result = unionWith(array1, array2, areItemsEqual)
// result 将是 [{ id: 1 }, { id: 2 }, { id: 3 }] 因为 { id: 2 } 在两个数组中被视为相等
js
function unionWith(arr1, arr2, isEqual) {
  const result = [...arr1, ...arr2].filter(item => {
    const isItemInArr1 = arr1.some(arrItem => isEqual(item, arrItem))
    const isItemInArr2 = arr2.some(arrItem => isEqual(item, arrItem))
    return !(isItemInArr1 && isItemInArr2)
  })
  return result
}

安全生成 UUID

js
function generateUniqueId() {
  // 优先使用现代浏览器/Node.js的UUID生成方法
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID() // 标准API
  }

  // 使用crypto.getRandomValues的强化方案
  if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
    const buffer = new Uint8Array(16)
    crypto.getRandomValues(buffer)

    // 设置UUID版本位 (v4)
    buffer[6] = (buffer[6] & 0x0f) | 0x40
    buffer[8] = (buffer[8] & 0x3f) | 0x80

    return Array.from(buffer)
      .map((byte, index) => {
        switch (index) {
          case 4:
          case 6:
          case 8:
          case 10:
            return byte.toString(16).padStart(2, '0') + '-'
          default:
            return byte.toString(16).padStart(2, '0')
        }
      })
      .join('')
      .toLowerCase()
  }

  // 终极回退方案(非加密安全)
  return (
    Date.now().toString(36) +
    Math.random().toString(36).slice(2, 10) +
    performance.now().toString(36).replace('.', '')
  )
}

// 使用示例
console.log(generateUniqueId()) // 输出类似:'6ec0bd7f-11c0-43da-8156-0c2b4d279e1c'

环境检测策略优化:

优先检测 crypto.randomUUID(Chrome 92+ / Firefox 95+ / Node.js 14.17+)

二级检测 crypto.getRandomValues(IE 11+ / 主流浏览器)

最后使用时间戳混合方案

加密安全强化:

// 旧版本方案的问题 Math.random() // 伪随机数,不适用于安全场景

// 新方案使用加密级随机源 crypto.getRandomValues(buffer) // 密码学安全随机数 UUID 格式规范:

// 正确设置版本标识位 buffer[6] = (buffer[6] & 0x0f) | 0x40; // 设置版本 4 buffer[8] = (buffer[8] & 0x3f) | 0x80; // 设置变体位

各环境兼容性对比: 方法 浏览器支持 Node.js 支持 随机源质量

crypto.randomUUID Chrome 92+ Node.js 14.17+ 加密安全 crypto.getRandomValues IE 11+ Node.js 0.10+ 加密安全

时间戳混合方案 全平台 全版本 非安全

如果需要生成短 ID,可以添加参数控制输出格式:

javascript
function generateId({ length = 12, radix = 36 } = {}) {
  const buffer = new Uint8Array(length)
  crypto.getRandomValues(buffer)
  return Array.from(buffer)
    .map(b => b.toString(radix).padStart(2, '0'))
    .join('')
    .slice(0, length)
}

// 示例:12位base62短ID
console.log(generateId()) // 类似'y5Hq9vKp7Lm3'

自定义排序的工具函数

javascript
/**
 * 根据指定字段和自定义顺序对集合进行排序
 * @param {Array} collection - 要排序的对象数组
 * @param {string} field - 排序依据的字段名
 * @param {Array} sortOrder - 自定义顺序数组
 * @returns {Array} - 排序后的新数组
 */
function sortByCustomOrder(collection, field, sortOrder) {
  // 处理空输入:如果集合为空或sortOrder为空,直接返回原集合的浅拷贝
  if (!collection || collection.length === 0) return [...(collection || [])]
  if (!sortOrder || sortOrder.length === 0) return [...collection]

  // 创建顺序映射表(值 -> 索引)
  const orderMap = new Map()

  // 只记录每个值第一次出现的索引(避免重复值覆盖)
  sortOrder.forEach((value, index) => {
    if (!orderMap.has(String(value))) {
      orderMap.set(String(value), index)
    }
  })

  // 创建原数组的浅拷贝(避免修改原数组)
  return [...collection].sort((a, b) => {
    // 获取字段值(处理可能不存在的字段)
    const valA = a && field in a ? String(a[field]) : undefined
    const valB = b && field in b ? String(b[field]) : undefined

    // 获取索引位置(未找到则置为Infinity,排在最后)
    const indexA = valA !== undefined && orderMap.has(valA) ? orderMap.get(valA) : Infinity
    const indexB = valB !== undefined && orderMap.has(valB) ? orderMap.get(valB) : Infinity

    // 按索引位置排序
    return indexA - indexB
  })
}

上次更新于: