工具示例
深拷贝
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 }
获取浏览器 Cookie 的值
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的值
清除全部 Cookie
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
})
}