Skip to content

常用工具函数

驼峰转小写下划线

javascript
/**
 * 小驼峰转小写下划线 userNameInfo
 * @param { string } v 需要转换的字符串,如:userNameInfo
 * @return user_name_info
 */
export const toUnderline = (v) => {
    return v.replace(/[A-Z]/g,(current) => `_${current.toLowerCase()}`)
}

下划线命名转小驼峰

javascript
/**
 * 下划线命名转小驼峰 user_name_info
 * @param { string } v 需要转换的字符串,如:user_name_info
 * @return userNameInfo
 */
export const getCamelCase = (v) => {
    return v.replace(/_[a-z]/g,(current) => current.split('_')[1].toUpperCase())
}

生成随机Hex颜色

javascript
/**
 * 生成随机Hex颜色
 * @return 返回色值,如:#1eb31
 */
export const getColorHexColor = () => `#${Math.floor(Math.random() * 0xfffff).toString(16)}`
console.log(getColorHexColor()) // #1eb31
javascript
/**
 * 生成随机Hex颜色
 * @return 返回色值,如:#1eb31
 */
getColorHexColor() {
		return '#'+Math.random().toString(16).substr(2, 6)
}

判断变量数据类型

javascript
  /**
   * 判断变量数据类型
   * @param { any } obj 需要判断数据类型的变量
   * @return 数据类型
   */
const getObjType = (obj) => {
  const toString = Object.prototype.toString
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  }
  // 如果是节点
  if (obj instanceof Element) {
    return 'element'
  }
  // 通过toString.call 判断是哪个类型
  // 判断的值为类型[object Boolean], 通过对象取值返回
  return map[toString.call(obj)]
}
console.log(getObjType(true)) // boolean

深拷贝

javascript
/**
  * 深拷贝
  * @param { any } v 需要深拷贝的数据
  * @return 返回年月日时分秒字符串
  */
export const deepClone = (v) => {
   let type = getObjType(v);
   let obj = null;
   // 如果是对象&数组则先清空再做后续操作
   if(type === 'array'){
       obj = []
       for(let i = 0; i < v.length ; i++){
            // 将每一个值都放到deepClone中遍历,如果是非对象&数组则push到obj中
           obj.push(deepClone(v[i]))
       }
   } else if(type === 'object'){
       obj = {}
        for(let k in v){
           obj[k] = deepClone(v[k]) 
       }
   } else {
       // 非对象/数组,直接返回
       return v
   }
   return obj
}

// 使用
let o = {id:1,id2:2,id3:[{val1:3,val2:3}]}
let c = deepClone(o);
c.id3[0].val1 = 9
// { id: 1,id2: 2, id3: { val1: 9, val2: 3 }] }
// { id: 1,id2: 2, id3: { val1: 3, val2: 3 }] }
// 这个案例也解释了为什么data是一个函数的问题,这样不会造成数据污染

如果上面的深拷贝还不明白,那么下面这个例子你应该会明白

javascript
const deepClone = (v) => {
    let obj = {};
    if(typeof v == "object"){
        obj = {}
    } else {
        return v
    }
    for(let k in v){
        // 递归,将返回的值添加到obj
        // 函数的内的数据是相互隔离的
        // obj原本的值不会变,每次调用deepClone都是独立的
         obj[k] = deepClone(v[k])
    }
    return obj
}
let o = {id:1,id2:true,id3:3}
let s = deepClone(o)
s.id = 9
// console.log(s) // {id:9,id2:true,id3:3}
// console.log(o) // {id:1,id2:true,id3:3}

保留指定的小数位数

javascript
/**
 * 保留指定位数小数
 * @param { number } v 需要转换的数值
 * @param { number } s 需要保留的位数,如:2就是保留两位小数 
 * @return 转换后的结果
 */
export const getDecimal = (v, s) => {
    let size = '1'.padEnd((s + 1), 0)
    return parseInt(v * parseInt(size)) / size
}

字符串转时间戳

javascript
  /**
   * 时间字符串转时间戳
   * @param { string } str 时间字符串
   * @return 返回时间戳
   */
  export const getTimes = (str) => {
     return new Date(str.substring(0, 19).replace(/-/g, '/')).getTime();
  }

时间戳转 年月日时分秒

javascript
  /**
   * 时间戳转 年月日时分秒
   * @param { number } timestamp 时间戳
   * @return 返回年月日时分秒字符串
   */
  export const getTimestamp = (timestamp) => {
     let date = new Date(timestamp)
     let Year = String(date.getFullYear())
     let Moth = String(date.getMonth() + 1).padStart(2,'0')
     let Day = String(date.getDate()).padStart(2,'0')
     let Hour = String(date.getHours()).padStart(2,'0')
     let Minute = String(date.getMinutes()).padStart(2,'0')
     let Seconds = String(date.getSeconds()).padStart(2,'0')
     return `${Year}-${Moth}-${Day} ${Hour}:${Minute}:${Seconds}`
  }

获取当月第一天、当前天、最后一天

javascript
/**
 * 获取当前月份的第一天、当天和最后一天
 * @param {string} 例如: '2023-09';参数可传可不传,不传获取当月,传则获取指定月
 * @return {array} [当月第一天,当前天,最后一天]
 **/
export const getFirstandLastDay = (YearandMonty = null) => {
    let date = YearandMonty ? new Date(YearandMonty) : new Date();    
    let year = String(date.getFullYear());
    let month = String(date.getMonth() + 1).padStart(2,'0')
    let day = String(date.getDate()).padStart(2,'0')
    // new Date第三个参数默认1,也就是当月第一天,如果传0则是当月最后一天
    let lastDay = new Date(year,month,0).getDate();
    let firstDate = `${year}-${month}-01`
    let theDay = `${year}-${month}-${day}`
    let lastDate = `${year}-${month}-${lastDay}`
    return [firstDate, theDay, lastDate];
}

数组去重

javascript
  /**
   * 数组去重
   * @param { array } v 需要去重的数组 
   * @return
   */
  export const arrSet = (v) => {
     return [...new Set(v)]
  }

数组对象去重

原理:将数组对象的每个值变成基本类型,去比对基本类型的存储单元位置实现去重效果,去重完成后,将字符串反序列化即可拿到去重后的值。

javascript
  /**
   * 数组对象去重
   * @param { array } v 需要去重的数组 
   * @return
   */
  export const arrObjSet = (v) => {
     return [...new Set(v.map(el=> JSON.stringify(el)))].map(el=> JSON.parse(el))
  }

数组对象根据指定key去重

javascript
/**
 * 数组对象根据指定key去重
 * @param { array } v 需要去重的数组
 * @param { string } key 根据指定key去重
 * @return
 */
export const somethingSet = (v, k) => {
  let res = new Set();
  // 根据Set来判断,先向set中添加,因为本来就没有,此时表达式中返回的是true
  // 一旦set中同样的值已经加过一次,那么has就会返回false,此时表达式返回的也就是false
  // filter根据表达式中的状态true判断是否过滤,所以filter过滤出来的永远都是唯一值
  return v.filter(item => !res.has(item[k]) && res.add(item[k]))
}

有了上面的思路,我们用正常的手段也可以实现同样的效果

javascript
let obj = [
    {id : 1 ,name : 'a'},
    {id : 2 ,name : 'b'},
    {id : 3 ,name : 'c'},
    {id : 4 ,name : 'b'},
    {id : 5 ,name : 'c'},
    {id : 6 ,name : 'd'},
]
somethingSet(obj,'name') // (4) [{…}, {…}, {…}, {…}]
const somethingSet = (v, k) => {
  let res = {};
  return v.filter(item => res[item[k]] != item[k] && (res[item[k]] = item[k]))
}

密码强度检测

javascript
/**
 * 检测密码强度 false小于6位
 * 数字 || 小写英文 || 大写英文 || ._- 分别为一级
 * 符合条件累加,最大值4级
 * @param { string } v 需要校验的值
 * @return 返回密码强度等级
 */
export const pwdStrength = (v) => {
  if (v.length < 6) {
    return false
  }
  let i = 0;
  if (/[0-9]/.test(v)) {
    ++i;
  }
  if (/[a-z]/.test(v)) {
    ++i;
  }
  if (/[A-Z]/.test(v)) {
    ++i;
  }
  if (/[_|\-|.]/.test(v)) {
    ++i;
  }
  return i;
}

排序-根据条件对数组对象排序

javascript
/**
 * 排序-根据条件对数组对象排序
 * v:数组对象,k:key,type:0升序|1降序
 * @param { array } v 需要排序的数组
 * @param { string } key 根据指定key排序
 * @param { number } type 0升序|1降序
 * @return 排序结果
 */
export const toSort = (v, k, type) => {
  v.sort(function(a,b){
    if(type == 0){
      return a[k] - b[k] 
    } else {
      return b[k] - a[k]
    }
  })
  return v;
}

原生写法-根据条件对数组对象排序

javascript
/**
 * 排序-根据条件对数组对象排序
 * arr:数组对象,k:key,type:0升序|1降序
 * @param { array } arr 需要转换的数组
 * @param { string } key 根据执行key排序
 * @param { number } type 升序还是降序  
 * @return 排序结果
 */
export const toCycling = (arr, key, type) => {
  const run = (j) => {
    let [A, B] = [arr[j], arr[j + 1]];
    [arr[j + 1], arr[j]] = [A,B];
  } 
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j][key] > arr[j + 1][key] && type == 0) {
        run(j)
      } else if(arr[j][key] < arr[j + 1][key] && type == 1){
        run(j)
      }
    }
  }
  return arr;
}

比对对象差异值,返回新对象的差异值

javascript
/**
 * 比对新对象和旧对象的差异值,返回新对象的差异值
 * @param { object } newObj 新对象
 * @param { object } oldObj 旧对象
 * @return { object } obj 新对象的差异值
 */
 export const Difference = (newObj,oldObj) => {
  let diff = {};
  for(let k in newObj){
      if(newObj[k] != oldObj[k]){
          diff[k] = newObj[k]
      }
  }
  return diff;
}

获取省市区树形结构的所有code

javascript
// 示例数据
let area = [{
        "code": "420000",
        "name": "湖北省",
        "icon" : 'icon',
        "children": [{
            "code": "420100",
            "name": "武汉市",
            "children": [{
                "code": "420101",
                "name": "市辖区"
            }, {
                "code": "420102",
                "name": "江岸区"
            }, {
                "code": "420103",
                "name": "江汉区"
            }, ]
        }]
    },
   // 其它省......
]
javascript
/**
 * 获取树形数据的所有节点code
 * @param {array} area 树形数据 
 * @return {array} 所有节点的code
 */
export const getTreeCode = (area) => {
  let allCode = []
  shell(area)
  function shell(area) {
    area.forEach((item) => {
      bicycle(item)
    })
    function bicycle(area) {
      allCode.push(area.code)
      if (area?.children?.length > 0) {
        shell(area.children)
      }
    }
  }
  return allCode
}

递归适用于深层数据查找,这种查找是单行的,如果要获取所有节点数据则需要遍历递归,拿到所有分支的数据。

判断code在树形数据中是否存在

javascript
/**
 * 判断code在树形数据中是否存在
 * @param {array} area 树形数据 
 * @param {string} code 需要判断的code
 * @return {boolean} true\false
 */
 export const treeCodeExist = (area, code) => {
  let exist = false;
  shell(area)
  function shell(area) {
    area.forEach((item) => {
      bicycle(item)
    })
    function bicycle(area) {
      if(code == area.code){
        return exist = true
      }
      if (area?.children?.length > 0) {
        shell(area.children)
      }
    }
  }
  return exist
}

判断开始时间和结束时间是否大于12个月

javascript
/**
 * 判断开始时间和结束时间是否大于12个月
 * @param {string} startTime 开始时间,如:'2023-09-14'
 * @param {string} endTime 结束时间,如:'2023-12-10'
 * @return {boolean} true\false
 */
const  dateLimit = (startTime,endTime) => {
    let start = startTime.split('-');
    let end = endTime.split('-');
    let y = end[0] - start[0];
    let m = 0;
    if(y > 0){
        let allMonth = y * 12;
        m = allMonth - Number(start[1]) + Number(end[1]);
    } else {
        m = end[1] - start[1];
    }
    if(m === 12){
      if(end[2] > start[2])
      { return true } else
      { return false }
    }
    if(m > 12)
    { return true } else 
    { return false }
}

删除数组中多个指定元素

a = [1,3,4,2,5] / b = [2,3]

预期结果:[1,4,5]

这里要用到倒叙删除,因为数组的length是动态的,在循环中正序删除会导致下标不准。

javascript
/***
 * @param {array} arr 原数组
 * @param {array} ids 需要删除的id数组
 * @return {array} 删除的数组 
 */
const arrMoreDeletion = (arr,ids) => {
  for(let i = arr.length -1 ; i >= 0 ; i--){
     for(let j = 0; j < ids.length ; j++){
       // 如果是数组对象这里就改为arr[i].id
        if(arr[i] == ids[j]){
            arr.splice(i,1)
        }
    }
  }
  return arr;
}

有条件数组交叉合并

将两个乱序的数组按照梳子的组合规律依次插入排列,例如将下面的数据组合成:

[ {花园评分}, {花园描述}, {小区评分}, {小区描述} ] 这种依次排列的数组

javascript
let list = [
    {id:1,name:'花园评分',value:''},
    {id:2,name:'小区评分',value:''},
    {id:3,name:'街道评分',value:''},
    {id:4,name:'小区描述',value:''},
    {id:6,name:'花园描述',value:''},
    {id:7,name:'公园评分',value:''},
    {id:8,name:'街道描述',value:''},
    {id:9,name:'公园描述',value:''}
]

let arr = list.filter(el => el.name.includes('评分'))
let arr2 = list.filter(el => el.name.includes('描述'))
let info = []; // 最终结果
arr.forEach(el => {
   info.push(el);
  let text = el.name.replace('评分','');
  for(let i = 0; i < arr2.length ; i++){
     if(arr2[i].name.includes(text)){
         info.push(arr2[i]);
         break;
     }
  }
})

无条件数组交叉合并

以A组为基础插入B组数据,如果A组数据少于B组,那么B组多出来的数据依次向下排列。

javascript
let listA = [
    { name: 'A组01' },
    { name: 'A组02' },
    { name: 'A组03' }
]

let listB = [
    { name: 'B组01' },
    { name: 'B组02' },
    { name: 'B组03' }
]

/***
 * @param {array} listA 数组A
 * @param {array} listB 数组B
 * @return {array} 数组A\B的交叉数组 
 */
const crossList = (listA = [] , listB = []) => {
    let info = []; // 最终结果
    let a = listA.length;
    let b = listB.length;
    let state = a >= b; // 比较A组是否 >= B组
    listA.forEach((el, index) => {
        info.push(el); // 添加A组每一项
        if (index + 1 <= b) {
            info.push(listB[index]); // 在A组遍历中,同时添加B组数据
        }
    });
    // B组大于A组,将B组的数据在数组末尾依次排列
    if (!state) {
        let diff = b - a;
        let arr = []
        for (let i = 0; i < diff; i++) {
            arr.push(listB.reverse()[i]); // 反转,从尾部向前添加
        }
        info.push(...arr.reverse()); // 二次反转,改为正常顺序添加
    }
    return info;
}

crossList(listA , listB);

生成随机字符串

javascript
/***
 * @param {number} num 需要生成多少位随机字符
 * @return {string} 生成的随机字符 
 */
export const randomString = (num) => {
  let str = "",
    arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  let index = null;
  for (let i = 0; i < num; i++) {
    index = Math.round(Math.random() * (arr.length - 1));
    str += arr[index];
  }
  return str;
}

过滤所有节点树中的子节点

所有的子节点和父节点在一维数组中,过滤出所有节点的子项,这里的子项也就是children的最后一层

场景:后端返回所有勾选和半勾选节点,比如返回杭州市并且返回杭州市下的某几个区,我们要拿这样的数据回显树勾选节点,如果直接回显,由于返回了杭州市,杭州市下的所有区会全部勾选,所以需要过滤子节点,通过子节点反向勾选父节点,如果子节点其中某几个节点勾选,父节点半选,如果子节点全部勾选,父节点就会自动勾选。

原理:根据返回的fiLevel层级分组,例如:1、2、3组(省、市、区)然后遍历所有层级,通过fvParentId比对是否有children,最后过滤出没有children的节点。

javascript
let treeAll = [
    {
        "id":3425,
        "fiAreaCode":"330000",
        "fiDeptId":null,
        "fiLevel":1,
        "fvParentId":"0",
        "fiAffiliation":0,
        "fvAreaName":"浙江省",
        "fvEstateType":null
    },
    {
        "id":3426,
        "fiAreaCode":"330100",
        "fiDeptId":null,
        "fiLevel":2,
        "fvParentId":"330000",
        "fiAffiliation":0,
        "fvAreaName":"杭州市",
        "fvEstateType":null
    },
    {
        "id":3457,
        "fiAreaCode":"310005",
        "fiDeptId":null,
        "fiLevel":3,
        "fvParentId":"330100",
        "fiAffiliation":0,
        "fvAreaName":"临平区",
        "fvEstateType":null
    },
    {
        "id":3458,
        "fiAreaCode":"310009",
        "fiDeptId":null,
        "fiLevel":3,
        "fvParentId":"330100",
        "fiAffiliation":0,
        "fvAreaName":"上城区",
        "fvEstateType":null
    },
    {
        "id":3425,
        "fiAreaCode":"33001200",
        "fiDeptId":null,
        "fiLevel":1,
        "fvParentId":"0",
        "fiAffiliation":0,
        "fvAreaName":"我的省",
        "fvEstateType":null
    },
        {
        "id":3425,
        "fiAreaCode":"3300123400",
        "fiDeptId":null,
        "fiLevel":1,
        "fvParentId":"0",
        "fiAffiliation":0,
        "fvAreaName":"我的省2",
        "fvEstateType":null
    },
]

function shiftArea(treeAll) {
    let treeObj = {};
  // 层级分组
   treeAll.forEach(item => {
       let level = item.fiLevel;
       if(!treeObj[level]){
           treeObj[level] = [];
       }
       treeObj[level].push(item);
   })
   
   
  let all = []; // 保存所有节点
  // 遍历所有节点
  Object.values(treeObj).forEach(item => {
      // 对节点深层遍历
      item.forEach(item2 => {
          item2.chindren = [];  // 子级默认为空数组
          // 设置children子级节点
          treeAll.forEach(item3 => {
              if(item2.fiAreaCode == item3.fvParentId){
                  item2.chindren.push(item3)
              }
          })
          all.push(item2)
      })
  })
  // 过滤出所有子级节点
  let outcome = all.filter(item => item.chindren.length == 0)
  return outcome
}

shiftArea(treeAll)

获取数值在数组中的近似值

javascript
/***
 * @param {array} arr 数组,如:[23, 30, 35, 47, 16, 21]
 * @param {number} num 当前值,如37
 * @return {string} 当前值在数组中最接近的值
 */
const closest = (arr, num) => {
    var ret = arr[0];
    var distance = Math.abs(ret - num);
    for(var i = 1; i < arr.length; i++){
        var newDistance = Math.abs(arr[i] - num);
        if(newDistance < distance){
            distance = newDistance;
            ret = arr[i];
        }
    }
    return ret;
}

let arr = [23, 30, 35, 47, 16, 21]
let num = 37
console.log(closest(arr,num)); // 35

根据上限值计算涨幅和减幅

javascript
/**
 * 根据当前值和上限值计算涨幅和减幅,例如:
 * 上限100,当前45,那么45 = 100涨幅122.22222222222223%
 * 上限100,当前145,145 = 100,减幅31.03448275862069%
 * @param {Object} a 当前值
 * @param {Object} max 上限值
 * @returns {Object || Boolean} false表示无任何涨幅或不符合规则,Object.type:1涨幅 2减幅
 */
getPercent(a,max) {
  // 无涨幅或不符合规则
  if(a == 0 || !a) return false
  
  if (a < max) {
    // 涨幅
    // 即 max 相对于 a 增加了百分之多少
    const increase = ((max - a) / a) * 100;
    return {
      percent: increase,
      type: 1, // 涨幅
    }
  } else if (a > max) {
    // 减幅
    // 即 a 相对于 max 减少了百分之多少
    const decrease = ((a - max) / a) * 100;
    return {
      percent: decrease,
      type: 2, // 减幅
    }
  } else {
    // 没有任何涨幅,与max对等
    return false
  }
}

根据涨幅和减幅计算实际值

javascript
/**
 * 根据涨幅和减幅计算实际值,调用上面的计算涨幅函数
 * @param {Number} num 要计算的值
 * @param {Object} extent
 * @returns {Number} 计算后的值,保留两位小数
 */
getValue(num,extent) {
  if(!extent) return num
  if(extent.type == 1){
    let n =  num * (1 + (extent.percent / 100)); // 根据涨幅公式计算最终值
    return parseInt(n * 100) / 100
  } else {
    let n = item.value * (1 - (extent.percent / 100)); // 根据涨幅公式计算最终值
    return parseInt(n * 100) / 100
  }
}

秒转时分秒

javascript
/**
 * 根据涨幅和减幅计算实际值,调用上面的计算涨幅函数
 * @param {Number} num 要计算的值
 * @param {Object} extent
 * @returns {Number} 计算后的值,保留两位小数
 */
const formatTime = (seconds) => {
  let hours = Math.floor(seconds / 3600);
  let minutes = Math.floor((seconds - (hours * 3600)) / 60);
  let remainingSeconds = seconds - (hours * 3600) - (minutes * 60);

  // 添加前导零
  hours = String(hours).padStart(2, '0');
  minutes = String(minutes).padStart(2, '0');
  remainingSeconds = String(remainingSeconds).padStart(2, '0');
  return `${hours}小时${minutes}分${remainingSeconds}秒`
}

console.log(formatTime(661)); // 输出: 00小时11分01秒
console.log(formatTime(7200)); // 输出: 02小时00分00秒

过滤出前后指定N年符合区间的时间

javascript
/**
 * 从当前时间开始,过滤出前后指定N年符合区间的时间
 * @param {string} time 当前时间
 * @param {monthStar} 指定月份第一天
 * @param {monthEnd} 指定月份最后一天
 * @param {diff} 前后多少年
 * @returns {Boolean} 是否符合:treu / false
 */
const getTimeFrame = (time,monthStar,monthEnd,diff) => {
    const getTimestamp = (str) => new Date(str.substring(0, 19).replace(/-/g, '/')).getTime();
    let date = new Date();
    let year = date.getFullYear();
    let star = year - diff;
    let end = year + diff
    let arr = [];
    for(let i = 0 ; i < end - star ; i ++){
        arr.push({star:`${star+i}-${monthStar}`,end:`${star+i}-${monthEnd}`})
    }
    let state = arr.some(el => {
        return getTimestamp(time) >= getTimestamp(el.star) && getTimestamp(time) <= getTimestamp(el.end)
    })
    return state
}
// 从当前时间开始,过滤出前后10年符合区间的时间
getTimeFrame('2024-12-01','12-01','12-31',10);  // true

基于 MIT 许可发布