/** * 返回一个自带节流效果的函数,用res表示。 * * fn:需要被节流的函数 * interval:最短多长时间允许执行一次fn * * 功能要点: * 1.fn代码里如有this,res被执行时,this会指向res的调用者; * 2.res被执行时的实参会映射到fn的形参; * 3.第一次调用res时,会立即执行fn。 */ export function throttle(fn, interval) { let lastRunTime = 0 return function (...args) { let elapsedTime = Date.now() - lastRunTime if (elapsedTime < interval) { return } let context = this lastRunTime = Date.now() fn.apply(context, args) } } /** * 返回一个自带消抖效果的函数,下文用fnDebounced表示。 * * fn: 需要被消抖的函数 * delay: 消抖时长 * isImmediateCall: 是否在一组操作中的第一次调用时立即执行fn * isRememberLastCall:是否在一组中最后一次调用后等delay时长再执行fn * * 如果isRememberLastCall为false,意味着fn不会被延迟执行,所以fnDebounced执行时,要么在内部调用fn,同步返回fn返回值;要么内部决定本次不调用fn,同步返回null。 * 如果isRememberLastCall为true,意味着fn可能被延迟执行,所以fnDebounced会返回一个Promise,在fn被调用时用其返回值resolve该Promise,或者在fn的延时调用计划被取消时用'canceled'resolve该Promise。(不宜reject,否则又没有人去catch,会导致浏览器报错。) */ export function debounce(fn, delay = 250, isImmediateCall = false, isRememberLastCall = true) { console.assert(isImmediateCall || isRememberLastCall, 'isImmediateCall 和 isRememberLastCall 至少应有一个是true,否则没有意义!') let timer = null let retPromiseLastTimeResolver = null // 上次调用的时刻 let lastCallTime = 0 if (isImmediateCall && !isRememberLastCall) { return function (...args) { let ret = null const currentTime = Date.now() if (currentTime - lastCallTime >= delay) { ret = fn.apply(this, args) } lastCallTime = currentTime return ret } } else if (!isImmediateCall && isRememberLastCall) { return function (...args) { if (timer) { clearTimeout(timer) timer = null } if (retPromiseLastTimeResolver) { retPromiseLastTimeResolver('canceled') retPromiseLastTimeResolver = null } const ret = new Promise((resolve, reject) => { retPromiseLastTimeResolver = resolve timer = setTimeout(() => { timer = null retPromiseLastTimeResolver = null resolve(fn.apply(this, args)) }, delay) }) return ret } } else if (isImmediateCall && isRememberLastCall) { return function (...args) { const currentTime = Date.now() if (currentTime - lastCallTime >= delay) { // 一组操作中的第一次 const res = fn.apply(this, args) lastCallTime = currentTime return Promise.resolve(res) } else { // 一组中的后续调用 if (timer) { // 在此之前存在中间调用 lastCallTime = currentTime clearTimeout(timer) timer = null } if (retPromiseLastTimeResolver) { retPromiseLastTimeResolver('canceled') retPromiseLastTimeResolver = null } const ret = new Promise((resolve, reject) => { retPromiseLastTimeResolver = resolve timer = setTimeout(() => { lastCallTime = 0 timer = null retPromiseLastTimeResolver = null resolve(fn.apply(this, args)) }, delay) }) return ret } } } else { console.error('不应该执行到这里!') } } /** 同时验证⼿机号码和固定电话号码(带区号或不带区号或带分机号) 规则说明: 1、可以是1开头的11位数字(⼿机号) 2、可以是“区号-电话号-分机号”或者是“(区号)电话号-分机号”格式 3、区号是0开头的3~4位数字,可以没有区号 4、电话号是5~8位数字,不能以0开头 5、分机号是1~8位数字,可以没有分机号 合法数据⽰例: ①13812341234 ②010-12345678 ③(0432)1234567-1234 ④12345678 */ export function isValidPhoneNumber(value) { const reg = /^1\d{10}$|^400[0-9]{7}|^(0\d{2,3}-?|\(0\d{2,3}\))?[1-9]\d{4,7}(-\d{1,8})?$/ // const reg = /^400[0-9]{7}|^1[34578]\d{9}$|^0[0-9]{2,3}-[0-9]{8}/ return reg.test(value) } // 深拷贝,为了解决循环引用和共同引用的问题,引入了WeakMap,又因为引入WeakMap可能会导致被拷贝对象被挂上【作为WeakMap的探针的】匿名函数(是pollyfill的行为吧?),所以不会拷贝非根元素的匿名函数。 export function deepClone(target, hash = new WeakMap()) { // 定义一个变量 let result = null // 如果当前需要深拷贝的是一个对象的话 if (typeof target === 'object') { if (hash.has(target)) { // 如果是循环引用 result = hash.get(target) } else if (Array.isArray(target)) { // 如果是一个数组的话 result = [] // 将result赋值为一个数组,并且执行遍历 hash.set(target, result) for (let i in target) { if (!(typeof(target[i]) === 'function' && !target.name)) { // 递归克隆数组中的每一项 result.push(deepClone(target[i], hash)) } } // 判断如果当前的值是null的话;直接赋值为null } else if (target === null) { result = null // 判断如果当前的值是一个RegExp对象的话,直接赋值 } else if (target.constructor === RegExp) { result = target } else { // 否则是普通对象,直接for in循环,递归赋值对象的所有值 result = {} hash.set(target, result) for (let i in target) { if (!(typeof(target[i]) === 'function' && !target.name)) { result[i] = deepClone(target[i], hash) } } } } else if (typeof target === 'function') { result = target } else { // 如果不是对象也不是函数,直接赋值 result = target } // 返回最终结果 return result } export function isObjectBroad(p) { return typeof(p) === 'object' || typeof(p) === 'function' } // 判断两个对象内容是否相同。未考虑循环引用、共同引用的情况。 export function isSameObject(object1, object2) { const keys1 = Object.keys(object1) const keys2 = Object.keys(object2) if (keys1.length !== keys2.length) { return false } for (let index = 0; index < keys1.length; index++) { const val1 = object1[keys1[index]] const val2 = object2[keys2[index]] const areObjects = isObjectBroad(val1) && isObjectBroad(val2) if ( (areObjects && !isSameObject(val1, val2)) || (!areObjects && (val1 !== val2)) ) { return false } } return true } export function ossImagePreviewUrlSuffix(downScaleRate = 10) { return `?x-oss-process=image/resize,p_${downScaleRate}&${Math.random()}` } export function postOrderTraversal(root, routine) { if (root.children && Array.isArray(root.children)) { for (const child of root.children) { postOrderTraversal(child, routine) } } routine(root) } export function nodeIdList2nodeInfoListByNodeTree(nodeIdList, nodeTree) { console.assert(nodeIdList && nodeTree && nodeTree.id && nodeTree.name, 'nodeIdList2nodeInfoListByNodeTree: invalid param!') if (nodeIdList.length === 0) { return null } console.assert(nodeTree.id === nodeIdList[0], 'nodeIdList2nodeInfoListByNodeTree: 不可能的任务!') let ret = [ { id: nodeTree.id, name: nodeTree.name, } ] if (nodeIdList[1] || nodeIdList[1] === 0) { console.assert(nodeTree.children && nodeTree.children.length > 0, 'nodeIdList2nodeInfoListByNodeTree: 不可能的任务2!') const nextLevelRoot = nodeTree.children.find((item) => { return item.id === nodeIdList[1] }) console.assert(nextLevelRoot, 'nodeIdList2nodeInfoListByNodeTree: invalid param 2!') ret = ret.concat(nodeIdList2nodeInfoListByNodeTree(nodeIdList.slice(1, nodeIdList.length), nextLevelRoot)) } return ret } export function capitalize(str) { if (!str) { return } if (str.length === 1) { return str[0].toUpperCase() } return str[0].toUpperCase() + str.slice(1, str.length) }