utils.js 说明文档
概述
utils.js 是一个通用前端工具类,约 500 行代码,零依赖。涵盖字符串、数组、对象、日期、数字、URL、Cookie、Storage、验证、防抖节流等日常开发中最常用的功能。
核心特点:
- 📦 零依赖 --- 纯原生 JavaScript,不依赖任何第三方库
- 🗂️ 分类清晰 --- 按功能模块组织,调用方式统一
Utils.xxx() - 🔌 多模块支持 --- 同时支持浏览器全局、CommonJS、AMD、ES Module 引入
- 💡 开箱即用 --- 引入即用,无需配置,每个方法独立可拆
- 📝 中文文档 --- 注释和文档均为中文,适合国内开发者
适用场景:
- 不引入 lodash / underscore 等大型工具库的项目
- 传统多页应用或纯静态页面
- Node.js 后端 / Webpack / Vite 等现代构建工具项目
- 需要轻量级常用函数的任何项目
快速开始
支持四种引入方式,项目中选择其一即可:
方式一:浏览器 <script> 标签(全局变量)
html
<script src="utils.js"></script>
<script>
Utils.formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss');
// → "2026-08-11 14:30:00"
Utils.deepClone({ a: 1, b: { c: 2 } });
// → 深拷贝对象
var fn = Utils.debounce(function() { console.log('search'); }, 300);
input.addEventListener('input', fn);
</script>
方式二:CommonJS(require)
javascript
var Utils = require('./utils.js');
Utils.formatDate(new Date(), 'yyyy-MM-dd');
Utils.throttle(handleScroll, 200);
方式三:AMD(require.js / define)
javascript
require(['./utils.js'], function(Utils) {
Utils.formatDate(new Date(), 'yyyy-MM-dd');
});
方式四:ES Module(import)
javascript
import Utils from './utils.js';
Utils.formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss');
Utils.isEmail('test@example.com');
API 参考
一、类型判断
| 方法 | 说明 | 示例 |
|---|---|---|
Utils.isArray(val) |
是否为数组 | Utils.isArray([1,2]) → true |
Utils.isObject(val) |
是否为纯对象 | Utils.isObject({a:1}) → true |
Utils.isFunction(val) |
是否为函数 | Utils.isFunction(fn) → true |
Utils.isString(val) |
是否为字符串 | Utils.isString('abc') → true |
Utils.isNumber(val) |
是否为数字(排除 NaN) | Utils.isNumber(123) → true |
Utils.isBoolean(val) |
是否为布尔值 | Utils.isBoolean(true) → true |
Utils.isUndefined(val) |
是否为 undefined | Utils.isUndefined(x) → true |
Utils.isNull(val) |
是否为 null | Utils.isNull(null) → true |
Utils.isEmpty(val) |
是否为空(null/undefined/''/[]/{}) |
Utils.isEmpty({}) → true |
二、字符串
Utils.trim(str)
去除首尾空白字符(包括全角空格和 \uFEFF),兼容没有 String.prototype.trim 的旧浏览器。
javascript
Utils.trim(' hello world ');
// → "hello world"
Utils.trim(null);
// → ""
Utils.capitalize(str)
首字母大写,其余字母小写。
javascript
Utils.capitalize('hello');
// → "Hello"
Utils.capitalize('HELLO WORLD');
// → "Hello world"
Utils.camelCase(str)
将短横线、下划线、空格分隔的字符串转为驼峰命名。
javascript
Utils.camelCase('hello-world');
// → "helloWorld"
Utils.camelCase('user_first_name');
// → "userFirstName"
Utils.camelCase('background color');
// → "backgroundColor"
Utils.kebabCase(str)
将驼峰或下划线/空格分隔的字符串转为短横线命名。
javascript
Utils.kebabCase('helloWorld');
// → "hello-world"
Utils.kebabCase('user_first_name');
// → "user-first-name"
Utils.truncate(str, len, suffix)
截断字符串,超出指定长度时追加后缀。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
str |
String | --- | 原始字符串 |
len |
Number | --- | 最大长度 |
suffix |
String | '...' |
截断后缀 |
javascript
Utils.truncate('这是一段很长很长的文本内容', 6);
// → "这是一段很长..."
Utils.truncate('hello world', 5, '...');
// → "hello..."
Utils.stripHtml(str)
移除字符串中的 HTML 标签。
javascript
Utils.stripHtml('<p>Hello <b>World</b></p>');
// → "Hello World"
Utils.escapeHtml(str)
转义 HTML 特殊字符,防止 XSS 攻击。
javascript
Utils.escapeHtml('<script>alert("xss")</script>');
// → "<script>alert("xss")</script>"
Utils.unescapeHtml(str)
反转义 HTML 实体。
javascript
Utils.unescapeHtml('<div>Hello</div>');
// → "<div>Hello</div>"
Utils.padStart(str, len, padChar)
在字符串左侧填充字符至指定长度。
javascript
Utils.padStart('7', 3, '0');
// → "007"
Utils.padStart('abc', 6, '-');
// → "---abc"
Utils.padEnd(str, len, padChar)
在字符串右侧填充字符至指定长度。
javascript
Utils.padEnd('7', 3, '0');
// → "700"
三、数字
Utils.formatNumber(num, decimals)
保留指定位数小数。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
num |
Number | --- | 数字 |
decimals |
Number | 2 |
小数位数 |
javascript
Utils.formatNumber(3.14159, 2);
// → "3.14"
Utils.formatNumber(100);
// → "100.00"
Utils.formatCurrency(num, decimals, symbol)
格式化金额。
javascript
Utils.formatCurrency(12345.6);
// → "¥ 12345.60"
Utils.formatCurrency(12345.6, 0, '$');
// → "$ 12346"
Utils.numberWithCommas(num)
添加千分位逗号。
javascript
Utils.numberWithCommas(1234567.89);
// → "1,234,567.89"
Utils.random(min, max)
生成随机整数。
javascript
Utils.random(1, 10); // 1~10 之间的随机整数(含)
Utils.random(100); // 0~100 之间
Utils.random(); // 0~1 之间浮点数
Utils.clamp(num, min, max)
将数值限制在范围内。
javascript
Utils.clamp(150, 0, 100);
// → 100
Utils.clamp(-10, 0, 100);
// → 0
Utils.inRange(num, min, max)
判断数值是否在范围内。
javascript
Utils.inRange(50, 0, 100);
// → true
Utils.inRange(-1, 0, 100);
// → false
四、数组
Utils.unique(arr)
数组去重(基于 indexOf)。
javascript
Utils.unique([1, 2, 2, 3, 1]);
// → [1, 2, 3]
Utils.uniqueBy(arr, key)
对象数组按字段去重。
javascript
var list = [{id:1}, {id:2}, {id:1}];
Utils.uniqueBy(list, 'id');
// → [{id:1}, {id:2}]
Utils.flatten(arr, deep)
展平数组。
javascript
Utils.flatten([[1,2], [3,4]]);
// → [1, 2, 3, 4]
Utils.flatten([1, [2, [3, [4]]]], true);
// → [1, 2, 3, 4]
Utils.chunk(arr, size)
将数组按指定大小分块。
javascript
Utils.chunk([1,2,3,4,5,6,7], 3);
// → [[1,2,3], [4,5,6], [7]]
Utils.shuffle(arr)
随机打乱数组(Fisher-Yates 洗牌算法),不改变原数组。
javascript
Utils.shuffle([1, 2, 3, 4, 5]);
// → [3, 1, 5, 2, 4] (随机)
Utils.range(start, end, step)
生成数字序列。
javascript
Utils.range(5);
// → [0, 1, 2, 3, 4]
Utils.range(1, 10, 2);
// → [1, 3, 5, 7, 9]
Utils.groupBy(arr, key)
按字段分组。
javascript
var users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' }
];
Utils.groupBy(users, 'role');
// → { admin: [{...}, {...}], user: [{...}] }
// 支持回调函数
Utils.groupBy(users, function(u) { return u.name[0]; });
// → { A: [...], B: [...], C: [...] }
Utils.sortBy(arr, key, desc)
按字段排序(不改变原数组)。
javascript
Utils.sortBy(users, 'name'); // 升序
Utils.sortBy(users, 'name', true); // 降序
Utils.remove(arr, item)
从数组中移除指定元素(直接修改原数组)。
javascript
var a = [1, 2, 3, 4];
Utils.remove(a, 3);
// a → [1, 2, 4]
Utils.intersection(a, b)
取两个数组的交集。
javascript
Utils.intersection([1,2,3], [2,3,4]);
// → [2, 3]
Utils.difference(a, b)
取差集(在 a 中但不在 b 中的元素)。
javascript
Utils.difference([1,2,3], [2,3,4]);
// → [1]
五、对象
Utils.deepClone(obj)
深度克隆。支持 Date、RegExp、普通对象、数组。
javascript
var obj = { a: 1, b: [1,2,{c:3}], d: new Date() };
var clone = Utils.deepClone(obj);
clone.b[2].c = 999;
// obj.b[2].c 仍为 3,互不影响
Utils.extend(target, src1, src2, ...)
浅合并,后面的覆盖前面的,直接修改 target。
javascript
Utils.extend({a:1}, {b:2}, {b:3});
// → {a:1, b:3}
Utils.extendDeep(target, src1, src2, ...)
深度合并。
javascript
Utils.extendDeep(
{ a: { x: 1, y: 2 } },
{ a: { y: 999, z: 3 } }
);
// → { a: { x: 1, y: 999, z: 3 } }
Utils.pick(obj, keys)
从对象中挑选指定字段。
javascript
Utils.pick({a:1, b:2, c:3, d:4}, ['a', 'c']);
// → { a: 1, c: 3 }
Utils.omit(obj, keys)
排除指定字段。
javascript
Utils.omit({a:1, b:2, c:3, d:4}, ['a', 'c']);
// → { b: 2, d: 4 }
Utils.objectToQuery(obj)
对象转 URL 查询字符串。
javascript
Utils.objectToQuery({ name: 'Alice', age: 28 });
// → "name=Alice&age=28"
Utils.objectToQuery({ a: 1, b: null, c: 3 });
// → "a=1&c=3" (null 值自动忽略)
Utils.queryToObject(str)
URL 查询字符串解析为对象。
javascript
Utils.queryToObject('name=Alice&age=28');
// → { name: 'Alice', age: '28' }
Utils.queryToObject();
// 自动解析当前页面 URL 参数 (location.search)
六、日期
Utils.formatDate(date, fmt)
格式化日期。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
date |
Date/String/Number | --- | 日期值 |
fmt |
String | 'yyyy-MM-dd HH:mm:ss' |
格式模板 |
格式占位符:
| 占位符 | 含义 | 示例 |
|---|---|---|
yyyy |
年份四位数 | 2026 |
yy |
年份两位数 | 26 |
MM |
月份(01-12) | 08 |
M |
月份(无前导零) | 8 |
dd |
日期(01-31) | 11 |
d |
日期(无前导零) | 11 |
HH |
24小时制(00-23) | 14 |
hh |
12小时制(01-12) | 02 |
mm |
分钟(00-59) | 30 |
ss |
秒(00-59) | 05 |
SSS |
毫秒 | 123 |
t |
上下午 | AM / PM |
javascript
var d = new Date(2026, 7, 11, 14, 30, 5);
Utils.formatDate(d);
// → "2026-08-11 14:30:05"
Utils.formatDate(d, 'yyyy年MM月dd日');
// → "2026年08月11日"
Utils.formatDate(d, 'yyyy-MM-dd hh:mm:ss t');
// → "2026-08-11 02:30:05 PM"
Utils.formatDate(d, 'MM-dd');
// → "08-11"
Utils.formatDate(1702741234567);
// 时间戳也可以直接传入
Utils.timeAgo(date)
显示距离当前时间的相对描述。
javascript
Utils.timeAgo(new Date(Date.now() - 30000));
// → "刚刚"
Utils.timeAgo(new Date(Date.now() - 30 * 60000));
// → "30 分钟前"
Utils.timeAgo(new Date(Date.now() - 5 * 3600000));
// → "5 小时前"
Utils.timeAgo(new Date(Date.now() - 3 * 86400000));
// → "3 天前"
Utils.timeAgo(new Date(Date.now() - 60 * 86400000));
// → "2 个月前"
Utils.timeAgo(new Date(Date.now() - 400 * 86400000));
// → "1 年前"
Utils.getDateDiff(start, end)
计算两个日期的时间差。
javascript
var start = new Date('2026-08-01');
var end = new Date('2026-08-11');
Utils.getDateDiff(start, end);
// → { days: 10, hours: 0, minutes: 0, seconds: 0, milliseconds: 864000000 }
Utils.addDays(date, days)
日期加减天数,返回新 Date 对象。
javascript
Utils.addDays(new Date('2026-08-01'), 10);
// → Date(2026-08-11)
Utils.addDays(new Date('2026-08-01'), -5);
// → Date(2026-07-27)
Utils.isLeapYear(year)
判断是否为闰年。
javascript
Utils.isLeapYear(2024); // → true
Utils.isLeapYear(2026); // → false
Utils.isLeapYear(new Date()); // 也可传入 Date 对象
Utils.daysInMonth(year, month)
获取指定年月的天数。
javascript
Utils.daysInMonth(2026, 2); // → 28
Utils.daysInMonth(2024, 2); // → 29 (闰年)
Utils.daysInMonth(2026, 8); // → 31
七、URL / 浏览器
Utils.parseUrl(url)
解析 URL 各组成部分。
javascript
Utils.parseUrl('https://www.example.com:8080/path/page?a=1#top');
// → {
// protocol: 'https:',
// host: 'www.example.com:8080',
// hostname: 'www.example.com',
// port: '8080',
// pathname: '/path/page',
// search: '?a=1',
// hash: '#top',
// href: 'https://www.example.com:8080/path/page?a=1#top'
// }
Utils.getQueryParam(name, url)
获取 URL 中指定参数的值。
javascript
// 当前页面 URL 为 ?name=Alice&age=28
Utils.getQueryParam('name');
// → "Alice"
Utils.getQueryParam('age', 'https://a.com?age=25&page=1');
// → "25"
Utils.addQueryParam(url, key, value)
给 URL 追加查询参数(已存在则替换)。
javascript
Utils.addQueryParam('/page', 'page', '2');
// → "/page?page=2"
Utils.addQueryParam('/page?page=1', 'page', '2');
// → "/page?page=2"
Utils.addQueryParam('/page#top', 'sort', 'desc');
// → "/page?sort=desc#top"
Utils.removeQueryParam(url, key)
移除 URL 中指定查询参数。
javascript
Utils.removeQueryParam('/page?a=1&b=2&c=3', 'b');
// → "/page?a=1&c=3"
Utils.isMobile()
检测是否为移动设备。
javascript
if (Utils.isMobile()) {
console.log('当前为手机浏览器');
}
Utils.isIE()
检测是否为 IE 浏览器。
javascript
if (Utils.isIE()) {
alert('请使用现代浏览器访问');
}
八、Cookie
Utils.cookie.set(name, value, days, path, domain)
设置 Cookie。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name |
String | --- | Cookie 名称 |
value |
String | --- | Cookie 值 |
days |
Number | undefined |
过期天数(不传则为会话 Cookie) |
path |
String | '/' |
有效路径 |
domain |
String | '' |
有效域名 |
javascript
Utils.cookie.set('username', 'Alice', 7);
Utils.cookie.set('theme', 'dark', 30, '/', '.example.com');
Utils.cookie.get(name)
读取 Cookie。
javascript
Utils.cookie.get('username');
// → "Alice"
Utils.cookie.get('nonexistent');
// → null
Utils.cookie.remove(name, path, domain)
删除 Cookie。
javascript
Utils.cookie.remove('username');
Utils.cookie.remove('theme', '/', '.example.com');
九、Storage
Utils.storage.set(key, value, expires)
写入 localStorage,支持过期时间。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
key |
String | --- | 键名 |
value |
Any | --- | 值(自动 JSON 序列化) |
expires |
Number | undefined |
过期毫秒数(不传则永不过期) |
javascript
Utils.storage.set('user', { name: 'Alice', role: 'admin' });
Utils.storage.set('token', 'abc123', 3600000); // 1 小时后过期
Utils.storage.get(key)
读取 localStorage(自动 JSON 反序列化,过期则返回 null 并清除)。
javascript
var user = Utils.storage.get('user');
// → { name: 'Alice', role: 'admin' }
Utils.storage.get('token');
// 如果过期 → null(已自动清除)
Utils.storage.remove(key) / Utils.storage.clear()
删除指定 key / 清空所有 localStorage。
javascript
Utils.storage.remove('user');
Utils.storage.clear();
十、函数相关
Utils.debounce(fn, delay, immediate)
防抖 --- 高频事件停止触发 delay 毫秒后才执行。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
fn |
Function | --- | 要执行的函数 |
delay |
Number | --- | 延迟毫秒数 |
immediate |
Boolean | false |
是否立即执行一次 |
javascript
// 搜索输入防抖
input.addEventListener('input', Utils.debounce(function(e) {
console.log('搜索:', e.target.value);
}, 300));
// 立即执行模式(第一次立即触发,之后防抖)
btn.addEventListener('click', Utils.debounce(function() {
console.log('提交');
}, 1000, true));
Utils.throttle(fn, delay)
节流 --- 每 delay 毫秒最多执行一次。
javascript
// 滚动事件节流
window.addEventListener('scroll', Utils.throttle(function() {
console.log('scrollTop:', Utils.getScrollTop());
}, 200));
// 按钮防连点
btn.addEventListener('click', Utils.throttle(function() {
console.log('click');
}, 1000));
防抖 vs 节流:
| 防抖 (debounce) | 节流 (throttle) | |
|---|---|---|
| 行为 | 停止触发后等待再执行 | 固定间隔持续执行 |
| 适用 | 搜索输入、窗口 resize 结束 | 滚动、鼠标移动、动画帧 |
Utils.once(fn)
创建只执行一次的函数。
javascript
var init = Utils.once(function() {
console.log('只执行一次');
});
init(); // → "只执行一次"
init(); // 无输出
Utils.memoize(fn)
创建带缓存结果的函数(基于参数 JSON 序列化做 key)。
javascript
var factorial = Utils.memoize(function(n) {
console.log('计算中...');
if (n <= 1) return 1;
return n * factorial(n - 1);
});
factorial(5); // 计算中... x5
factorial(5); // 直接从缓存返回
Utils.delay(fn, ms, ...args)
延迟执行函数。
javascript
Utils.delay(function(name) {
console.log('Hello, ' + name);
}, 2000, 'Alice');
// 2 秒后输出: "Hello, Alice"
十一、验证
所有验证方法在 Utils.validate 命名空间下。
| 方法 | 说明 | 示例 |
|---|---|---|
email(val) |
邮箱格式 | Utils.validate.email('test@example.com') → true |
phone(val) |
手机号(中国大陆 1xx) | Utils.validate.phone('13800138000') → true |
tel(val) |
座机号 | Utils.validate.tel('010-12345678') → true |
idCard(val) |
身份证号(15位或18位) | Utils.validate.idCard('110101199001011234') → true |
url(val) |
URL 格式 | Utils.validate.url('https://example.com') → true |
number(val) |
数字(整数或小数) | Utils.validate.number('3.14') → true |
integer(val) |
整数 | Utils.validate.integer('100') → true |
chinese(val) |
纯中文 | Utils.validate.chinese('你好') → true |
ip(val) |
IPv4 地址 | Utils.validate.ip('192.168.1.1') → true |
qq(val) |
QQ 号(5~11位) | Utils.validate.qq('123456789') → true |
postalCode(val) |
邮政编码(6位) | Utils.validate.postalCode('100000') → true |
isEmpty(val) |
是否为空 | Utils.validate.isEmpty('') → true |
minLength(val, len) |
最小长度 | Utils.validate.minLength('abc', 2) → true |
maxLength(val, len) |
最大长度 | Utils.validate.maxLength('abc', 10) → true |
rangeLength(val, min, max) |
长度范围 | Utils.validate.rangeLength('abc', 1, 5) → true |
javascript
// 表单验证示例
var rules = {
email: { validate: Utils.validate.email, msg: '邮箱格式不正确' },
phone: { validate: Utils.validate.phone, msg: '手机号格式不正确' },
username: { validate: function(v) { return Utils.validate.rangeLength(v, 3, 20); }, msg: '用户名需 3-20 位' }
};
function checkField(field, value) {
if (!rules[field].validate(value)) {
return rules[field].msg;
}
return null;
}
十二、全局唯一 ID
Utils.uid(prefix)
生成短唯一 ID,格式:[前缀]时间戳_自增数_随机串。
javascript
Utils.uid();
// → "m5n8x2k_1_k3x9m2"
Utils.uid('user_');
// → "user_m5n8x2k_2_d8f3g1"
// 高并发下也不会重复(自增计数器保证唯一性)
Utils.guid()
生成标准 UUID(GUID)格式。
javascript
Utils.guid();
// → "a3f1b2c4-5d6e-4f8a-9b0c-d1e2f3a4b5c6"
十三、颜色
Utils.color.random()
生成随机十六进制颜色。
javascript
Utils.color.random();
// → "#a3f2c1"
document.body.style.backgroundColor = Utils.color.random();
Utils.color.hexToRgb(hex)
十六进制转 RGB 对象。
javascript
Utils.color.hexToRgb('#ff6600');
// → { r: 255, g: 102, b: 0 }
Utils.color.hexToRgbStr(hex)
十六进制转 RGB 字符串。
javascript
Utils.color.hexToRgbStr('#ff6600');
// → "rgb(255, 102, 0)"
Utils.color.hexToRgbStr('336699');
// → "rgb(51, 102, 153)"
Utils.color.rgbToHex(r, g, b)
RGB 转十六进制。
javascript
Utils.color.rgbToHex(255, 102, 0);
// → "#ff6600"
十四、数学
javascript
Utils.sum([1, 2, 3, 4, 5]); // → 15
Utils.avg([1, 2, 3, 4, 5]); // → 3
Utils.max([3, 1, 4, 1, 5]); // → 5
Utils.min([3, 1, 4, 1, 5]); // → 1
Utils.degToRad(180); // → 3.14159... (角度转弧度)
Utils.radToDeg(Math.PI); // → 180 (弧度转角度)
十五、DOM
Utils.scrollToTop()
平滑滚动到页面顶部。
javascript
Utils.scrollToTop();
Utils.getScrollTop()
获取当前页面滚动距离。
javascript
Utils.getScrollTop();
// → 450
Utils.copyToClipboard(text)
复制文本到剪贴板。返回 Promise。
javascript
Utils.copyToClipboard('Hello World').then(function() {
console.log('复制成功');
}).catch(function() {
console.log('复制失败');
});
// async/await 风格
await Utils.copyToClipboard('复制的文本');
Utils.getWindowSize()
获取视窗尺寸。
javascript
Utils.getWindowSize();
// → { width: 1920, height: 1080 }
十六、异步相关
Utils.sleep(ms)
异步等待指定毫秒。返回 Promise。
javascript
async function demo() {
console.log('开始');
await Utils.sleep(2000);
console.log('2 秒后');
}
Utils.retry(fn, times, delay)
创建带重试机制的异步函数。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
fn |
Function | --- | 要重试的函数(需返回 Promise) |
times |
Number | 3 |
最大重试次数 |
delay |
Number | 1000 |
重试间隔(毫秒) |
javascript
var fetchWithRetry = Utils.retry(function(url) {
return fetch(url).then(function(res) {
if (!res.ok) throw new Error('请求失败');
return res.json();
});
}, 5, 2000);
fetchWithRetry('/api/unstable-endpoint')
.then(function(data) { console.log('成功:', data); })
.catch(function(err) { console.error('5 次重试后仍失败', err); });
子命名空间一览
| 命名空间 | 说明 |
|---|---|
Utils.cookie.* |
Cookie 读写删 |
Utils.storage.* |
localStorage 封装(支持过期) |
Utils.validate.* |
表单验证方法集 |
Utils.color.* |
颜色格式转换 |
模块规范
utils.js 采用 UMD(Universal Module Definition)包装,自动适配运行环境:
| 环境 | 检测条件 | 导出方式 |
|---|---|---|
浏览器 <script> |
其他情况 | window.Utils |
| CommonJS (Node.js) | typeof module === 'object' |
module.exports |
| AMD (RequireJS) | typeof define === 'function' && define.amd |
define(factory) |
文件信息
utils.js --- 主库文件(约 500 行,零依赖,UMD 模块)
兼容浏览器、Node.js、AMD、ES Module 等多种环境,无需任何构建工具可直接使用。
javascript
/**
* utils.js
* 通用前端工具类
*
* 包含常用方法:
* 字符串、数组、对象、日期、数字、URL、验证、防抖节流、Storage 等
*
* 浏览器引入:
* <script src="utils.js"></script>
* <script>
* Utils.deepClone(obj);
* Utils.formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss');
* Utils.debounce(fn, 300);
* </script>
*
* CommonJS:
* var Utils = require('./utils.js');
*
* AMD:
* require(['./utils.js'], function(Utils) { ... });
*
* ES Module:
* import Utils from './utils.js'; // 或 import { deepClone, formatDate } from './utils.js'
*/
;(function(global, factory) {
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(function() { return factory(); });
} else {
global.Utils = factory();
}
}(typeof window !== 'undefined' ? window : this, function() {
'use strict';
var Utils = {};
// ==================== 类型判断 ====================
var _toString = Object.prototype.toString;
function _getType(val) {
return _toString.call(val).slice(8, -1).toLowerCase();
}
Utils.isArray = Array.isArray || function(val) {
return _getType(val) === 'array';
};
Utils.isObject = function(val) {
return val !== null && typeof val === 'object' && !Utils.isArray(val);
};
Utils.isFunction = function(val) {
return typeof val === 'function';
};
Utils.isString = function(val) {
return typeof val === 'string';
};
Utils.isNumber = function(val) {
return typeof val === 'number' && !isNaN(val);
};
Utils.isBoolean = function(val) {
return typeof val === 'boolean';
};
Utils.isUndefined = function(val) {
return val === undefined;
};
Utils.isNull = function(val) {
return val === null;
};
Utils.isEmpty = function(val) {
if (val === null || val === undefined) return true;
if (Utils.isArray(val) || Utils.isString(val)) return val.length === 0;
if (Utils.isObject(val)) return Object.keys(val).length === 0;
if (typeof val === 'number') return false;
return !val;
};
// ==================== 字符串 ====================
Utils.trim = function(str) {
return str == null ? '' : String(str).replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
Utils.capitalize = function(str) {
if (!str) return '';
str = String(str);
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};
Utils.camelCase = function(str) {
return String(str).replace(/[-_\s]+(.)?/g, function(match, c) {
return c ? c.toUpperCase() : '';
});
};
Utils.kebabCase = function(str) {
return String(str)
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/[\s_]+/g, '-')
.toLowerCase();
};
Utils.truncate = function(str, len, suffix) {
if (!str) return '';
suffix = suffix || '...';
return str.length > len ? str.slice(0, len) + suffix : str;
};
Utils.stripHtml = function(str) {
return String(str).replace(/<[^>]+>/g, '');
};
Utils.escapeHtml = function(str) {
var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/' };
return String(str).replace(/[&<>"'/]/g, function(m) { return map[m]; });
};
Utils.unescapeHtml = function(str) {
var map = { '&': '&', '<': '<', '>': '>', '"': '"', ''': "'", '/': '/' };
return String(str).replace(/&(?:amp|lt|gt|quot|#x27|#x2F);/g, function(m) { return map[m] || m; });
};
Utils.padStart = function(str, len, padChar) {
str = String(str);
padChar = padChar || ' ';
while (str.length < len) { str = padChar + str; }
return str;
};
Utils.padEnd = function(str, len, padChar) {
str = String(str);
padChar = padChar || ' ';
while (str.length < len) { str = str + padChar; }
return str;
};
// ==================== 数字 ====================
Utils.formatNumber = function(num, decimals) {
if (!Utils.isNumber(num)) return num;
decimals = decimals == null ? 2 : decimals;
return Number(num).toFixed(decimals);
};
Utils.formatCurrency = function(num, decimals, symbol) {
decimals = decimals == null ? 2 : decimals;
symbol = symbol || '¥';
return symbol + ' ' + Utils.formatNumber(num, decimals);
};
Utils.numberWithCommas = function(num) {
var parts = Number(num).toFixed(2).split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
};
Utils.random = function(min, max) {
if (min === undefined) return Math.random();
if (max === undefined) { max = min; min = 0; }
return Math.floor(Math.random() * (max - min + 1)) + min;
};
Utils.clamp = function(num, min, max) {
return Math.min(Math.max(Number(num), min), max);
};
Utils.inRange = function(num, min, max) {
return Number(num) >= min && Number(num) <= max;
};
// ==================== 数组 ====================
Utils.unique = function(arr) {
return arr.filter(function(item, index) {
return arr.indexOf(item) === index;
});
};
Utils.uniqueBy = function(arr, key) {
var seen = {};
return arr.filter(function(item) {
var val = item[key];
if (seen[val]) return false;
seen[val] = true;
return true;
});
};
Utils.flatten = function(arr, deep) {
if (deep) {
return arr.reduce(function(flat, item) {
return flat.concat(Utils.isArray(item) ? Utils.flatten(item, true) : item);
}, []);
}
return arr.reduce(function(flat, item) {
return flat.concat(item);
}, []);
};
Utils.chunk = function(arr, size) {
var result = [];
for (var i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
};
Utils.shuffle = function(arr) {
var result = arr.slice();
for (var i = result.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = result[i];
result[i] = result[j];
result[j] = temp;
}
return result;
};
Utils.range = function(start, end, step) {
step = step || 1;
var result = [];
if (end === undefined) {
end = start;
start = 0;
}
for (var i = start; i < end; i += step) {
result.push(i);
}
return result;
};
Utils.groupBy = function(arr, key) {
return arr.reduce(function(groups, item) {
var val = typeof key === 'function' ? key(item) : item[key];
(groups[val] || (groups[val] = [])).push(item);
return groups;
}, {});
};
Utils.sortBy = function(arr, key, desc) {
return arr.slice().sort(function(a, b) {
var valA = typeof key === 'function' ? key(a) : a[key];
var valB = typeof key === 'function' ? key(b) : b[key];
if (valA < valB) return desc ? 1 : -1;
if (valA > valB) return desc ? -1 : 1;
return 0;
});
};
Utils.remove = function(arr, item) {
var idx = arr.indexOf(item);
if (idx > -1) arr.splice(idx, 1);
return arr;
};
Utils.intersection = function(a, b) {
return a.filter(function(item) { return b.indexOf(item) > -1; });
};
Utils.difference = function(a, b) {
return a.filter(function(item) { return b.indexOf(item) === -1; });
};
// ==================== 对象 ====================
Utils.deepClone = function(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof RegExp) return new RegExp(obj);
var clone = Utils.isArray(obj) ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = Utils.deepClone(obj[key]);
}
}
return clone;
};
Utils.extend = function(target) {
var i = 1, len = arguments.length;
for (; i < len; i++) {
var src = arguments[i];
if (src != null) {
for (var key in src) {
if (src.hasOwnProperty(key)) {
target[key] = src[key];
}
}
}
}
return target;
};
Utils.extendDeep = function(target) {
var i = 1, len = arguments.length;
for (; i < len; i++) {
var src = arguments[i];
if (src != null) {
for (var key in src) {
if (src.hasOwnProperty(key)) {
if (Utils.isObject(src[key]) && Utils.isObject(target[key])) {
Utils.extendDeep(target[key], src[key]);
} else {
target[key] = src[key];
}
}
}
}
}
return target;
};
Utils.pick = function(obj, keys) {
var result = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key in obj) result[key] = obj[key];
}
return result;
};
Utils.omit = function(obj, keys) {
var result = {};
for (var k in obj) {
if (obj.hasOwnProperty(k) && keys.indexOf(k) === -1) {
result[k] = obj[k];
}
}
return result;
};
Utils.objectToQuery = function(obj) {
var parts = [];
for (var key in obj) {
if (obj.hasOwnProperty(key) && obj[key] != null) {
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
}
}
return parts.join('&');
};
Utils.queryToObject = function(str) {
var obj = {};
var str2 = str || global.location.search.slice(1);
if (!str2) return obj;
var pairs = str2.split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
if (pair[0]) {
obj[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
}
}
return obj;
};
// ==================== 日期 ====================
Utils.formatDate = function(date, fmt) {
if (!date) return '';
if (typeof date === 'string' || typeof date === 'number') date = new Date(date);
fmt = fmt || 'yyyy-MM-dd HH:mm:ss';
var map = {
'y+': date.getFullYear(),
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'H+': date.getHours(),
'h+': date.getHours() % 12 || 12,
'm+': date.getMinutes(),
's+': date.getSeconds(),
'S+': date.getMilliseconds(),
't': date.getHours() < 12 ? 'AM' : 'PM'
};
for (var key in map) {
if (new RegExp('(' + key + ')').test(fmt)) {
var val = String(map[key]);
if (key !== 't') {
val = RegExp.$1.length === 1 ? val : Utils.padStart(val, RegExp.$1.length, '0');
}
fmt = fmt.replace(RegExp.$1, val);
}
}
return fmt;
};
Utils.timeAgo = function(date) {
if (typeof date === 'string' || typeof date === 'number') date = new Date(date);
var diff = Date.now() - date.getTime();
var minute = 60 * 1000;
var hour = 60 * minute;
var day = 24 * hour;
var month = 30 * day;
var year = 365 * day;
if (diff < minute) return '刚刚';
if (diff < hour) return Math.floor(diff / minute) + ' 分钟前';
if (diff < day) return Math.floor(diff / hour) + ' 小时前';
if (diff < month) return Math.floor(diff / day) + ' 天前';
if (diff < year) return Math.floor(diff / month) + ' 个月前';
return Math.floor(diff / year) + ' 年前';
};
Utils.getDateDiff = function(start, end) {
if (typeof start === 'string' || typeof start === 'number') start = new Date(start);
if (typeof end === 'string' || typeof end === 'number') end = new Date(end);
var diff = end.getTime() - start.getTime();
var day = 24 * 60 * 60 * 1000;
return {
days: Math.floor(diff / day),
hours: Math.floor((diff % day) / (60 * 60 * 1000)),
minutes: Math.floor((diff % (60 * 60 * 1000)) / (60 * 1000)),
seconds: Math.floor((diff % (60 * 1000)) / 1000),
milliseconds: diff
};
};
Utils.addDays = function(date, days) {
var d = new Date(date);
d.setDate(d.getDate() + days);
return d;
};
Utils.isLeapYear = function(year) {
year = Utils.isNumber(year) ? year : new Date(year).getFullYear();
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
};
Utils.daysInMonth = function(year, month) {
return new Date(year, month, 0).getDate();
};
// ==================== URL / 浏览器 ====================
Utils.parseUrl = function(url) {
url = url || global.location.href;
var a = document.createElement('a');
a.href = url;
return {
protocol: a.protocol,
host: a.host,
hostname: a.hostname,
port: a.port,
pathname: a.pathname,
search: a.search,
hash: a.hash,
href: a.href
};
};
Utils.getQueryParam = function(name, url) {
url = url || global.location.href;
var regex = new RegExp('[?&]' + name + '(?:=([^&#]*)|&|#|$)');
var results = regex.exec(url);
if (!results) return null;
if (!results[1]) return '';
return decodeURIComponent(results[1].replace(/\+/g, ' '));
};
Utils.addQueryParam = function(url, key, value) {
var parts = url.split('#');
var hash = parts[1] ? '#' + parts[1] : '';
url = parts[0];
var regex = new RegExp('([?&])' + key + '=[^&]*&?');
var separator;
if (regex.test(url)) {
url = url.replace(regex, function(match, prefix) {
return prefix + key + '=' + encodeURIComponent(value) + '&';
});
} else {
separator = url.indexOf('?') > -1 ? '&' : '?';
url += separator + key + '=' + encodeURIComponent(value);
}
return url + hash;
};
Utils.removeQueryParam = function(url, key) {
var parts = url.split('#');
var hash = parts[1] ? '#' + parts[1] : '';
url = parts[0];
var regex = new RegExp('([?&])' + key + '=[^&]*&?', 'g');
url = url.replace(regex, function(match, prefix) {
return match.charAt(match.length - 1) === '&' ? prefix : '';
});
url = url.replace(/[?&]$/, '');
return url + hash;
};
Utils.isMobile = function() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(global.navigator && global.navigator.userAgent || '');
};
Utils.isIE = function() {
return /MSIE|Trident/.test(global.navigator && global.navigator.userAgent || '');
};
// ==================== Cookie ====================
Utils.cookie = {
set: function(name, value, days, path, domain) {
var expires = '';
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = '; expires=' + date.toUTCString();
}
path = path ? '; path=' + path : '; path=/';
domain = domain ? '; domain=' + domain : '';
document.cookie = name + '=' + encodeURIComponent(value || '') + expires + path + domain;
},
get: function(name) {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var c = Utils.trim(cookies[i]);
if (c.indexOf(name + '=') === 0) {
return decodeURIComponent(c.substring(name.length + 1));
}
}
return null;
},
remove: function(name, path, domain) {
Utils.cookie.set(name, '', -1, path, domain);
}
};
// ==================== Storage ====================
Utils.storage = {
set: function(key, value, expires) {
var data = { value: value };
if (expires) data.expires = Date.now() + expires;
try {
localStorage.setItem(key, JSON.stringify(data));
return true;
} catch (e) {
return false;
}
},
get: function(key) {
try {
var data = JSON.parse(localStorage.getItem(key));
if (!data) return null;
if (data.expires && Date.now() > data.expires) {
localStorage.removeItem(key);
return null;
}
return data.value;
} catch (e) {
return null;
}
},
remove: function(key) {
localStorage.removeItem(key);
},
clear: function() {
localStorage.clear();
}
};
// ==================== 函数相关 ====================
Utils.debounce = function(fn, delay, immediate) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
var callNow = !timer;
timer = setTimeout(function() { timer = null; }, delay);
if (callNow) fn.apply(context, args);
} else {
timer = setTimeout(function() { fn.apply(context, args); }, delay);
}
};
};
Utils.throttle = function(fn, delay) {
var lastTime = 0;
return function() {
var now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, arguments);
lastTime = now;
}
};
};
Utils.once = function(fn) {
var called = false;
return function() {
if (!called) {
called = true;
return fn.apply(this, arguments);
}
};
};
Utils.memoize = function(fn) {
var cache = {};
return function() {
var key = JSON.stringify(arguments);
if (cache[key] === undefined) {
cache[key] = fn.apply(this, arguments);
}
return cache[key];
};
};
Utils.delay = function(fn, ms) {
var args = Array.prototype.slice.call(arguments, 2);
return setTimeout(function() { fn.apply(null, args); }, ms);
};
// ==================== 验证 ====================
Utils.validate = {
email: function(val) {
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(val);
},
phone: function(val) {
return /^1[3-9]\d{9}$/.test(val);
},
tel: function(val) {
return /^(\d{3,4}-)?\d{7,8}$/.test(val);
},
idCard: function(val) {
return /^\d{15}$|^\d{17}[\dXx]$/.test(val);
},
url: function(val) {
return /^(https?:\/\/)?([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$/.test(val);
},
number: function(val) {
return /^-?\d+(\.\d+)?$/.test(val);
},
integer: function(val) {
return /^-?\d+$/.test(val);
},
chinese: function(val) {
return /^[\u4e00-\u9fa5]+$/.test(val);
},
ip: function(val) {
return /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/.test(val);
},
qq: function(val) {
return /^[1-9]\d{4,10}$/.test(val);
},
postalCode: function(val) {
return /^\d{6}$/.test(val);
},
isEmpty: function(val) {
return Utils.isEmpty(val);
},
minLength: function(val, len) {
return String(val).length >= len;
},
maxLength: function(val, len) {
return String(val).length <= len;
},
rangeLength: function(val, min, max) {
var len = String(val).length;
return len >= min && len <= max;
}
};
// ==================== 全局唯一 ID ====================
var _uidCounter = 0;
Utils.uid = function(prefix) {
prefix = prefix || '';
return prefix + Date.now().toString(36) + '_' + (++_uidCounter).toString(36) + '_' + Math.random().toString(36).slice(2, 8);
};
Utils.guid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0;
var v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
// ==================== 颜色 ====================
Utils.color = {
random: function() {
return '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');
},
hexToRgb: function(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
},
hexToRgbStr: function(hex) {
var rgb = Utils.color.hexToRgb(hex);
return rgb ? 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')' : '';
},
rgbToHex: function(r, g, b) {
return '#' + [r, g, b].map(function(x) {
var hex = Number(x).toString(16);
return hex.length === 1 ? '0' + hex : hex;
}).join('');
}
};
// ==================== 数学 ====================
Utils.sum = function(arr) {
return arr.reduce(function(s, n) { return s + n; }, 0);
};
Utils.avg = function(arr) {
return arr.length ? Utils.sum(arr) / arr.length : 0;
};
Utils.max = function(arr) {
return Math.max.apply(null, arr);
};
Utils.min = function(arr) {
return Math.min.apply(null, arr);
};
Utils.degToRad = function(deg) {
return deg * Math.PI / 180;
};
Utils.radToDeg = function(rad) {
return rad * 180 / Math.PI;
};
// ==================== DOM ====================
Utils.scrollToTop = function() {
global.scrollTo({ top: 0, behavior: 'smooth' });
};
Utils.getScrollTop = function() {
return document.documentElement.scrollTop || document.body.scrollTop;
};
Utils.copyToClipboard = function(text) {
if (global.navigator && global.navigator.clipboard) {
return navigator.clipboard.writeText(text);
}
var textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
return Promise.resolve();
};
Utils.getWindowSize = function() {
return {
width: global.innerWidth || document.documentElement.clientWidth,
height: global.innerHeight || document.documentElement.clientHeight
};
};
// ==================== 异步相关 ====================
Utils.sleep = function(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
};
Utils.retry = function(fn, times, delay) {
times = times || 3;
delay = delay || 1000;
return function() {
var args = arguments;
var context = this;
var attempts = 0;
function tryFn() {
try {
var result = fn.apply(context, args);
if (result && typeof result.then === 'function') {
return result.then(function(res) { return res; }).catch(function(err) {
attempts++;
if (attempts >= times) return Promise.reject(err);
return Utils.sleep(delay).then(tryFn);
});
}
return result;
} catch (err) {
attempts++;
if (attempts >= times) throw err;
return new Promise(function(resolve) { setTimeout(resolve, delay); }).then(tryFn);
}
}
return tryFn();
};
};
// ==================== 版本号 ====================
Utils.version = '2.0.0';
return Utils;
}));