工具函数
是否在某环境(微信/支付宝...)
js
export interface EnvList {
[key: string]: RegExp;
}
/**
* 是否在某环境
* @params str 环境标识
* @returns boolean 环境判断
*/
export const isEnv = (str: string): boolean => {
const envList: EnvList = {
weixin: /MicroMessenger/i,
alipay: /AlipayClient/i,
qq: /\bqq\/([\d\.]+)/i,
bestpay: /Bestpay/i,
unionpay: /unionpay/i,
ios: /iphone|ipad|ipod/i,
android: /android/i,
};
return envList[str].test(window.navigator.userAgent.toLowerCase());
};
节流防抖
节流 throttle
js
let timer;
let flag;
/**
* 节流原理:在一定时间内,只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true;
// 如果是立即执行,则在wait毫秒内开始时执行
typeof func === "function" && func();
timer = setTimeout(() => {
flag = false;
}, wait);
}
} else if (!flag) {
flag = true;
// 如果是非立即执行,则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false;
typeof func === "function" && func();
}, wait);
}
}
export default throttle;
防抖 debounce
js
let timeout = null;
/**
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function debounce(func, wait = 500, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout);
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) typeof func === "function" && func();
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === "function" && func();
}, wait);
}
}
export default debounce;
guid
js
/**
* @param {Number} len uuid的长度
* @param {string} first 将返回的首字母置为指定字符
* @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
*/
function guid(len = 32, string = "", radix = null) {
const chars =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");
const uuid = [];
radix = radix || chars.length;
if (len) {
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
} else {
let r;
// rfc4122标准要求返回的uuid中,某些位为固定的字符
uuid[8] = uuid[13] = uuid[18] = uuid[23] = "-";
uuid[14] = "4";
for (let i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | (Math.random() * 16);
uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
}
}
}
// 移除第一个字符,并用指定字符替代,因为第一个字符为数值时,该guuid不能用作id或者class
if (first && first.length === 1) {
uuid.shift();
return `${first}${uuid.join("")}`;
}
return uuid.join("");
}
js
/**
* 生成指定长度和类型的UUID。
* @param {number} [length=32] - UUID的长度,默认为32。
* @param {"digits" | "letters" | "alphanumeric"} [type="alphanumeric"] - UUID的字符类型,默认为"alphanumeric"。
* @returns {string} 生成的UUID字符串。
*/
export const uuid = (
length: number = 32,
type: "digits" | "letters" | "alphanumeric" = "alphanumeric"
): string => {
let result = "";
const characters =
type === "digits"
? "0123456789"
: type === "letters"
? "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
deepClone
js
/**
* @description 深度克隆
* @param {object} obj 需要深度克隆的对象
* @param cache 缓存
* @returns {*} 克隆后的对象或者原值(不是对象)
*/
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj;
if (cache.has(obj)) return cache.get(obj);
let clone;
if (obj instanceof Date) {
clone = new Date(obj.getTime());
} else if (obj instanceof RegExp) {
clone = new RegExp(obj);
} else if (obj instanceof Map) {
clone = new Map(
Array.from(obj, ([key, value]) => [key, deepClone(value, cache)])
);
} else if (obj instanceof Set) {
clone = new Set(Array.from(obj, (value) => deepClone(value, cache)));
} else if (Array.isArray(obj)) {
clone = obj.map((value) => deepClone(value, cache));
} else if (Object.prototype.toString.call(obj) === "[object Object]") {
clone = Object.create(Object.getPrototypeOf(obj));
cache.set(obj, clone);
for (const [key, value] of Object.entries(obj)) {
clone[key] = deepClone(value, cache);
}
} else {
clone = Object.assign({}, obj);
}
cache.set(obj, clone);
return clone;
}
打乱数组
js
/**
* @description 打乱数组
* @param {array} array 需要打乱的数组
* @returns {array} 打乱后的数组
*/
function randomArray(array = []) {
return array.sort(() => Math.random() - 0.5);
}
js
/**
* 对数组进行洗牌操作。
* @param {any[]} array - 需要洗牌的数组。
* @returns {any[]} 洗牌后的数组。
*/
export const shuffleArray = (array: any[]) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
},
对象转 url 参数
js
/**
* @description 对象转url参数
* @param {object} data,对象
* @param {Boolean} isPrefix,是否自动加上"?"
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma
*/
function queryParams(data = {}, isPrefix = true, arrayFormat = "brackets") {
const prefix = isPrefix ? "?" : "";
const _result = [];
if (["indices", "brackets", "repeat", "comma"].indexOf(arrayFormat) == -1)
arrayFormat = "brackets";
for (const key in data) {
const value = data[key];
// 去掉为空的参数
if (["", undefined, null].indexOf(value) >= 0) {
continue;
}
// 如果值为数组,另行处理
if (value.constructor === Array) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case "indices":
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(`${key}[${i}]=${value[i]}`);
}
break;
case "brackets":
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`);
});
break;
case "repeat":
// 结果: ids=1&ids=2&ids=3
value.forEach((_value) => {
_result.push(`${key}=${_value}`);
});
break;
case "comma":
// 结果: ids=1,2,3
let commaStr = "";
value.forEach((_value) => {
commaStr += (commaStr ? "," : "") + _value;
});
_result.push(`${key}=${commaStr}`);
break;
default:
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`);
});
}
} else {
_result.push(`${key}=${value}`);
}
}
return _result.length ? prefix + _result.join("&") : "";
}
生成指定范围内的随机整数
js
/**
* 生成指定范围内的随机整数。
* @param {number} min - 随机数的最小值(包含)。
* @param {number} max - 随机数的最大值(包含)。
* @returns {number} 随机生成的整数。
*/
export const randomInt = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
class Store
定义了一个名为 Store 的类,提供一个简单的状态管理机制,类似于 Vuex,但没有 Vuex 那么复杂和功能丰富。 它允许你定义 state、getters、mutations 和 actions,并通过 commit 和 dispatch 方法来修改和获取状态。
js
// 以下是一个简单的 JavaScript 类,类似于 Vuex,用于管理数据状态
export default class Store {
constructor(options) {
// 初始化 state:如果 options 中提供了 state,则将其赋值给 this.state,否则初始化为空对象。
this.state = options.state || {};
// 初始化 getters:遍历 options 中的 getters 对象,为每个 getter 定义一个属性,并通过 Object.defineProperty 定义一个 getter 函数,该函数调用 getters[key](this.state) 来获取计算属性的值。
this.getters = {};
const { getters } = options;
if (getters) {
Object.keys(getters).forEach((key) => {
Object.defineProperty(this.getters, key, {
get: () => getters[key](this.state),
enumerable: true,
});
});
}
// 初始化 mutations:遍历 options 中的 mutations 对象,为每个 mutation 定义一个方法,该方法调用 mutations[key](this.state, payload) 来修改 state。
this.mutations = {};
const { mutations } = options;
if (mutations) {
Object.keys(mutations).forEach((key) => {
this.mutations[key] = (payload) => {
mutations[key](this.state, payload);
};
});
}
// 初始化 actions:遍历 options 中的 actions 对象,为每个 action 定义一个方法,该方法调用 actions[key](this, payload) 来执行异步操作或复杂逻辑。
this.actions = {};
const { actions } = options;
if (actions) {
Object.keys(actions).forEach((key) => {
this.actions[key] = (payload) => {
actions[key](this, payload);
};
});
}
}
// commit 方法:用于触发一个 mutation。如果 mutationName 对应的 mutation 存在且是一个函数,则调用该函数并传入 payload。否则,打印错误信息。
commit(mutationName, payload) {
if (typeof this.mutations[mutationName] === "function") {
this.mutations[mutationName](payload);
} else {
console.error(`Mutation "${mutationName}" does not exist.`);
}
}
// dispatch 方法: 用于触发一个 action。如果 actionName 对应的 action 存在且是一个函数,则调用该函数并传入 payload。否则,打印错误信息。
dispatch(actionName, payload) {
if (typeof this.actions[actionName] === "function") {
this.actions[actionName](payload);
} else {
console.error(`Action "${actionName}" does not exist.`);
}
}
}
例:
js
import Store from "./store";
import { getUUID } from "./utils/utils";
import { commonService } from "./service/common.service";
// 定义 state
const state = {
info: {
name: "", // 姓名
sex: "", // 性别
age: "", // 年龄
},
detail: {}, // 详情
};
// 定义 getters
const getters = {
info() {
return state.info;
},
detail() {
return state.detail;
},
};
// 定义 mutations
const mutations = {
updateInfo(state, payload) {
state.info = payload;
},
updateDetail(state, payload) {
state.detail = payload;
},
};
// 定义 actions
const actions = {
initInfo(store, payload) {
try{
const res = await commonService.$initInfo({ ...payload.params, traceNo: getUUID()}
if (!res.data.success) {
payload.callback && payload.callback(res);
console.log("初始化失败", res.data.message);
return;
}
store.commit("updateInfo", res.data);
payload.callback && payload.callback(res);
} catch(e) {
console.log("初始化失败", e);
payload.callback && payload.callback({success:false,message:"初始化失败"});
}
},
};
// 创建一个新的 Store 实例
export default new Store({
state,
getters,
mutations,
actions,
});
js
const info = store.getters.info;
store.commit("updateInfo", {
name: "zs",
sex: "男",
age: "18",
});
store.dispatch("initInfo", {
params: {},
callback: (res) => {
// ...
},
});
对象键名转换
只转换第一层键名,键名单词全大写的不支持。
json
{
"FIRST_NAME": "John",
"HOBBIES": ["READING", "TRAVELLING"],
"user Name_Str": '张三',
"userName": '张三',
"user_age": 30,
"user_+age-str": 30,
"userAddress": {
city: '北京',
},
"UserAddressStr": {
"city": '北京',
"street+name-Str2": '长安街'
},
}
// =>
{
"fIRSTNAME":"John", // 不兼容,转换错误
"userNameStr":"张三",
"userName":"张三",
"userAge":30,
"userAgeStr":30,
"userAddress":{
"city":"北京"
},
"userAddressStr":{
"city":"北京",
"street+name-Str2":"长安街"
}
}
// =>
{
"f_i_r_s_t_n_a_m_e":"John", // 不兼容,转换错误
"user_name_str":"张三",
"user_name":"张三",
"user_age":30,
"user_age_str":30,
"user_address":{
"city":"北京"
},
"user_address_str":{
"city":"北京",
"street+name-Str2":"长安街"
}
}
js
/**
* 将对象的所有键转换为小写驼峰命名法。
* 如果传入的不是对象,则直接返回原值。
* @param {Object} obj - 需要转换键名的对象
* @returns {Object} - 键名已转换为小写驼峰命名法的新对象
*/
convertKeysToLowerCaseCamelCase(obj) {
if (Object.prototype.toString.call(obj) !== '[object Object]') {
return obj;
}
const convertToLowerCaseCamelCase = (str) => {
// 第一步:将首字母转换为小写
let transStr = str.charAt(0).toLowerCase() + str.slice(1);
// 第二步:将非字母数字的字符去除并将其后的第一个字符大写
transStr = transStr.replace(/[^a-zA-Z0-9]+(.)/g, (match, letter) => letter.toUpperCase());
return transStr;
};
const newObj = {};
Object.keys(obj).forEach(key => {
const newKey = convertToLowerCaseCamelCase(key);
// newObj[newKey] = this.convertKeysToLowerCaseCamelCase(obj[key]);
newObj[newKey] = obj[key];
});
return newObj;
},
js
/**
* 将对象的键转换为使用指定分隔符的小写形式。
* @param {Object} obj - 需要转换键的对象。
* @param {string} [separator='_'] - 用于连接单词的分隔符,默认为下划线。
* @returns {Object} - 返回一个新对象,其键已被转换为使用指定分隔符的小写形式。
*
* 转换过程包括:
* 1. 将键的首字母转换为小写。
* 2. 将键中的大写字母前添加分隔符并转换为小写。
* 3. 将键中的所有非字母数字字符替换为分隔符。
*/
convertKeysBySeparator(obj, separator = '_') {
if (Object.prototype.toString.call(obj) !== '[object Object]') {
return obj;
}
const convertToSeparator = (str) => {
// 第一步:将首字母转换为小写
let transStr = str.charAt(0).toLowerCase() + str.slice(1);
// 第二步:将大写字母拼接指定字符并小写
transStr = transStr.replace(/([A-Z])/g, (match) => separator + match.toLowerCase());
// 第三步:将所有非字母数字字符替换为指定字符
return transStr.replace(/[^a-zA-Z0-9]+/g, separator);
};
const transformedEntries = Object.entries(obj).map(([key, value]) => {
// return [convertToSeparator(key), this.convertKeysBySeparator(value, separator)]);
return [convertToSeparator(key), value];
});
return Object.fromEntries(transformedEntries);
}