js常用公共函数总结,建议收藏(持续更新)

1,092 阅读7分钟

平常工作中经常要使用一些公共函数,自己临时写的话会很费时,且容易有bug。在网上找资料, 各种文章又良莠不齐。因此总结出一些常用的公共函数,既能提高自己写代码的效率又能很好的避免一些bug

1.格式化时间


/**
 * 格式化时间为年月日时分秒的格式, 格式可以自定义。 
 * ① 时间戳10位和13位都可以转换成格式化的日期
 * ② java8格式的日期和有效的日期都可以转换成定义的日期格式
 * @param {Date, string}  都有默认参数
 * @example 
 * parseTime() // 2020-07-17 09:53:07
 * parseTime('2018-02-13T06:17') // 2018-02-13 06:17:00
 * parseTime('2020/03/02 06:02') // 2020-03-02 06:02:00
 * parseTime(1541927611000); //2018-11-11 17:13:21
 * parseTime(1541927611000, "{y}年{m}月{d}日 {h}时{m}分{s}秒"); // 2018年11月11日 17时11分31秒
 * parseTime(1541927611, "{y}/{m}/{d} {h}:{m}:{s}"); // 2018/11/11 17:11:31   
 * parseTime(new Date()); //2018-11-11 17:13:21
 * parseTime(new Date().getTime()); //2018-11-11 17:13:21 
 */
export function parseTime(time = new Date(), cFormat = "{y}-{m}-{d} {h}:{i}:{s}") {
    let date;
    if (typeof time === "object") {
        date = time;
    } else {
        if (("" + time).length === 10) time = parseInt(time) * 1000;
        date = new Date(time);
    }
    const formatObj = {
        y: date.getFullYear(),
        m: date.getMonth() + 1,
        d: date.getDate(),
        h: date.getHours(),
        i: date.getMinutes(),
        s: date.getSeconds(),
        a: date.getDay()
    };
    const time_str = cFormat.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
        let value = formatObj[key]; // Note: getDay() returns 0 on Sunday
        if (key === "a") {
            return ["日", "一", "二", "三", "四", "五", "六"][value];
        }
        if (result.length > 0 && value < 10) {
            value = "0" + value;
        }
        return value || 0;
    });
    console.log(time_str);
    return time_str;
}

  • 1.1 时间戳转为多久之前。 需要配合上边的parseTime使用

/**
 * 时间戳转为多久之前
 * @param String timestamp 时间戳
 * @param String | Boolean format 如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
 * @example
 * timeFrom(new Date(+new Date() - 3600 * 1000 * 100 * 365)) // 2017-05-25
 * timeFrom(new Date(+new Date() - 3600 * 1000 * 100 * 365), false) // 4年前
 * 如果为布尔值false,无论什么时间,都返回多久以前的格式
 */
export function timeFrom(dateTime = null, format = '{y}-{m}-{d}') {
        // 如果为null,则格式化当前时间
        if (!dateTime) dateTime = Number(new Date());
        // 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式
        if (dateTime.toString().length == 10) dateTime *= 1000;
        let timestamp = + new Date(Number(dateTime));
        console.log('%c timestamp ', 'background-image:color:transparent;color:red;font-size:2em');
        console.log(timestamp);
        let timer = (Number(new Date()) - timestamp) / 1000;
        // 如果小于5分钟,则返回"刚刚",其他以此类推
        let tips = '';
        switch (true) {
                case timer < 300:
                        tips = '刚刚';
                        break;
                case timer >= 300 && timer < 3600:
                        tips = parseInt(timer / 60) + '分钟前';
                        break;
                case timer >= 3600 && timer < 86400:
                        tips = parseInt(timer / 3600) + '小时前';
                        break;
                case timer >= 86400 && timer < 2592000:
                        tips = parseInt(timer / 86400) + '天前';
                        break;
                default:
                        // 如果format为false,则无论什么时间戳,都显示xx之前
                        if (format === false) {
                                if (timer >= 2592000 && timer < 365 * 86400) {
                                        tips = parseInt(timer / (86400 * 30)) + '个月前';
                                } else {
                                        tips = parseInt(timer / (86400 * 365)) + '年前';
                                }
                        } else {
                                tips = formatTime(timestamp, format);
                        }
        }
        return tips;
}

2. 截取url后边的字符串并以键值对的方式转化为对象

// 方法1 完整版
let url = "http://www.domain.com/?user=andy凌云&id=123&id=456&id=999&city=%E5%B8%9D%E9%83%BD&enabled&disabled=false&time=" + new Date();

export function parseParam(url) {
    const paramsStr = url.split("?")[1]; // 将 ? 后面的字符串取出来
    const paramsArr = paramsStr.split("&"); // 将字符串以 & 分割后存到数组中
    let paramsObj = {};
    // 将 params 存到对象中
    paramsArr.forEach(param => {
        if (/=/.test(param)) {
            // 处理有 value 的参数
            let [key, val] = param.split("="); // 分割 key 和 value
            val = decodeURIComponent(val); // 解码
            val = /^time$/.test(key) ? +new Date() : val; // 如果key是time,就转换成时间戳
            val = /^false$/.test(val) ? false : val; // 判断是否是false
            val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
            if (paramsObj.hasOwnProperty(key)) {
                // 如果对象有 key,则添加一个值
                paramsObj[key] = [].concat(paramsObj[key], val); //这步注意下
            } else {
                // 如果对象没有这个 key,创建 key 并设置值
                paramsObj[key] = val;
            }
        } else {
            // 处理没有 value 的参数
            paramsObj[param] = true;
        }
    });
    console.log(paramsObj);
    return paramsObj;
}
parseParam(url);
// 打印出的结果
/* {
  "user": "andy凌云",
  "id": [
    123,
    456,
    999
  ],
  "city": "帝都",
  "enabled": true,
  "disabled": false,
  "time": 1579354198346
} */        
// 方法2 使用正则
export function getQueryObject(url) {
    url = url == null ? window.location.href : url;
    const search = url.slice(url.indexOf("?") + 1);
    const obj = {};
    const reg = /([^?&=]+)=([^?&=]*)/g;
    search.replace(reg, (rs, $1, $2) => {
        const name = decodeURIComponent($1);
        let val = decodeURIComponent($2);
        val = String(val);
        obj[name] = val;
        return rs;
    });
    console.log(obj);
    return obj;
}
let urls = "http://www.baidu.com?name=andy凌雲&gender='男'&age=88&time=" + new Date();

getQueryObject(urls)

/* {
  "name": "andy凌雲",
  "gender": "'男'",
  "age": "88",
  "time": "Sat Jan 18 2020 21:39:07 GMT+0800 (中国标准时间)"
} */
// 方法3: 利用正则和 JSON.parse()
export function param2Obj(url) {
    const search = url.split('?')[1]
    if (!search) {
        return {}
    }
    return JSON.parse(
        '{"' +
        decodeURIComponent(search)
        .replace(/"/g, '\\"')
        .replace(/&/g, '","')
        .replace(/=/g, '":"')
        .replace(/\+/g, ' ') +
        '"}'
    )
}
let url = "http://www.domain.com/?user=andy凌云&id=123&id=456&id=999&city=%E5%B8%9D%E9%83%BD&disabled=false&time=" + new Date();
let res = param2Obj(url);
/* {
  "user": "andy凌云",
  "id": "999",
  "city": "帝都",
  "disabled": "false",
  "time": "Sat Jan 18 2020 21:43:47 GMT 0800 (中国标准时间)"
} */

        

3.清除数组里的逗号或undefinednull或空字符串

export function cleanArray(actual) {
      const newArray = [];
      for (let i = 0; i < actual.length; i++) {
        if (actual[i]) {
          newArray.push(actual[i]);
        }
      }
      return newArray;
}
let arr = [, undefined, , 1, , ",", " ", "", null, 2, 3, 1, 2, , ,];
cleanArray(arr);
//注意 "" 中只有什么都没有才回被清除
//  [1, ",", " ", 2, 3, 1, 2]

4.微信小程序请求后台(这种封装思想也能用在其它方法的后台请求中,比如vue中的axios请求)

这里写公共方法

const tips = {
  1: "抱歉,出现了一个错误!",
  1005: "appkey无效,请重新输入appkey",
  3000: "期刊不存在"
}; //这里是定义http请求出错的时候,不同状态码给用户返回不同的错误提示
class HTTP {
  request(params) {
    //url ,data , method
    if (!params.method) {
      params.method = "POST";
    }
    wx.request({
      url: params.url,
      method: params.method,
      data: params.data,
      header: {
        "content-type": "application/json"
      },
      success: res => {
        let code = res.statusCode.toString();
        if (code.startsWith("2")) {
          //没有这步输出操作,就不能在外部http的success里调用,这步是关键
          params.success(res.data.data);
        } else {
          let error_code = res.data.error_code;
          this._show_error(error_code);
        }
      },
      fail: err => {
        this._show_error(err.msg); // 当前电脑断网就能进入fail函数
      }
    });
  } //下滑线表示私有方法
  _show_error(error_coe) {
    if (!error_code) {
      error_code = 1;
    }
    wx.showToast({
      title: tips.error_code,
      icon: "none",
      duration: 2000
    });
  }
}
export { HTTP };

这里写调用方法

//引入HTTP,并且必须要new才可以
import { HTTP } from "../../http.js";
let http = new HTTP();
import api from "../../utils/api.js";

http.request({
  url: api.index,
  data: {
    token: wx.getStorageSync("token"),
    lng: wx.getStorageSync("lng"),
    lat: wx.getStorageSync("lat")
  },
  success: res => {
    console.log(res);
  }
});

5. 通过闭包进行加减乘除的运算

export function getNum() {
        let _num = 0;
        let innerObj = {
          add5: () => {
            _num += 5;
            return _num;
          },
          delete8: () => {
            _num -= 8;
            return _num;
          },
          deleteDefine: n => {
            _num -= n;
            return _num;
          },
          divideDefine: n => {
            _num /= n;
            return _num;
          }
        };
    return innerObj;
  }
  let center = getNum();
  let res = center.add5();
  res = center.deleteDefine(25);
  res = center.divideDefine(5);

6. 函数节流

  • 这是简单版本
export function throttle(fn, gapTime = 1000) {
  let _lastTime = null;
  return function() {
    let _nowTime = +new Date();
    if (_nowTime - _lastTime > gapTime || !_lastTime) {
      // 将this和参数传递给原函数
      fn.apply(this, arguments);
      _lastTime = _nowTime;
    }
  }
}
  • 合体版本,也是大多数成熟框架的在使用的
export function throttle(fn, delay) {
  // last为上一次触发毁掉的时间,timer是定时器
  console.log('进来啦');
  let last = 0;
  let timer = null;
  // 将throttle处理结果当做函数返回
  return function() {
    // 保留调用时的this上下文
    let context = this;
    // 保留调用时传入的参数
    let args = arguments;
    // 记录本次触发回调的时间
    let now = +new Date();
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last < delay) {
      // 如果时间间隔小于设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
      clearTimeout(timer);
      timer = setTimeout(function() {
        last = now;
        fn.apply(context, args);
      }, delay);
    } else {
      // 如果时间间隔超出了设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
      last = now;
      fn.apply(context, args);
    }
  };
}
// 用新的throttle包装scroll的回调
const better_scroll = throttle(() => {
    console.log("触发了滚动事件");
},3000);
document.addEventListener("scroll", better_scroll);

6.2 函数防抖

  • 简单版本
export function debounce(fn, interval = 300) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}
  • 复杂版本
/**
 * @param {Function} func
 * @param {number} wait
 * @param {boolean} immediate
 * @return {*}
 */
export function debounce(func, wait, immediate) {
  let timeout, args, context, timestamp, result

  const later = function() {
    // 据上一次触发时间间隔
    const last = +new Date() - timestamp

    // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
    if (last < wait && last > 0) {
      timeout = setTimeout(later, wait - last)
    } else {
      timeout = null
      // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
      if (!immediate) {
        result = func.apply(context, args)
        if (!timeout) context = args = null
      }
    }
  }

  return function(...args) {
    context = this
    timestamp = +new Date()
    const callNow = immediate && !timeout
    // 如果延时不存在,重新设定延时
    if (!timeout) timeout = setTimeout(later, wait)
    if (callNow) {
      result = func.apply(context, args)
      context = args = null
    }

    return result
  }
}

7. 生成uuid

/**
 * 生成uuid
 * @param {number} len 生成指定长度的uuid
 * @param {number} radix uuid进制数
 */
export function uuid(len, radix) {
    let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    let uuid = [], i;
    radix = radix || chars.length;
 
    if (len) {
      for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
    } else {
      let r;
 
      uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
      uuid[14] = '4';
 
      for (i = 0; i < 36; i++) {
        if (!uuid[i]) {
          r = 0 | Math.random()*16;
          uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
        }
      }
    }
 
    return uuid.join('');
}

8. js中浮点数精度问题

js运算浮点数的精度问题还是比较常见的。 要么用现有的第三方库去处理, 要么就统一做一下处理调用。 原理: 我们可以将浮点数toString后indexOf('.'),记录一下小数位的长度,然后将小数点抹掉。 完整代码如下。

/*
 * 判断obj是否为一个整数
 */
function isInteger(obj) {
    return Math.floor(obj) === obj
}

/*
 * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
 * @param floatNum {number} 小数
 * @return {object}
 *   {times:100, num: 314}
 */
function toInteger(floatNum = '') {
    var ret = { times: 1, num: 0 }
    if (isInteger(floatNum)) {
        ret.num = floatNum
        return ret
    }
    var strfi = floatNum + ''
    var dotPos = strfi.indexOf('.')
    var len = strfi.substr(dotPos + 1).length
    var times = Math.pow(10, len)
    var intNum = Number(floatNum.toString().replace('.', ''))
    ret.times = times
    ret.num = intNum
    return ret
}

/*
 * 核心方法,实现加减乘除运算,确保不丢失精度
 * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
 *
 * @param a {number} 运算数1
 * @param b {number} 运算数2
 * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
 * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
 *
 */
function operation(a, b, digits, op) {
    var o1 = toInteger(a)
    var o2 = toInteger(b)
    var n1 = o1.num
    var n2 = o2.num
    var t1 = o1.times
    var t2 = o2.times
    var max = t1 > t2 ? t1 : t2
    var result = null
    switch (op) {
        case 'add':
            if (t1 === t2) { // 两个小数位数相同
                result = n1 + n2
            } else if (t1 > t2) { // o1 小数位 大于 o2
                result = n1 + n2 * (t1 / t2)
            } else { // o1 小数位 小于 o2
                result = n1 * (t2 / t1) + n2
            }
            return result / max
        case 'subtract':
            if (t1 === t2) {
                result = n1 - n2
            } else if (t1 > t2) {
                result = n1 - n2 * (t1 / t2)
            } else {
                result = n1 * (t2 / t1) - n2
            }
            return result / max
        case 'multiply':
            result = (n1 * n2) / (t1 * t2)
            return result
        case 'divide':
            result = (n1 / n2) * (t2 / t1)
            return result
    }
}

// 加减乘除的四个接口
function add(a, b, digits) {
    return operation(a, b, digits, 'add')
}
function subtract(a, b, digits) {
    return operation(a, b, digits, 'subtract')
}
function multiply(a, b, digits) {
    return operation(a, b, digits, 'multiply')
}
function divide(a, b, digits) {
    return operation(a, b, digits, 'divide')
}

// exports
export {
    add,
    subtract,
    multiply,
    divide
}

9. 根据选择的日期, 计算出年龄(日期是'YYYY-MM-DD'或者日期字符串)

export function birthdayToAge(str) {
    let strBirthday = ''
    if (str instanceof Date) {
        strBirthday = moment(str).format("YYYY-MM-DD");
    } else {
        strBirthday = str;
    }

    var returnAge,
        strBirthdayArr = strBirthday.split("-"),
        birthYear = strBirthdayArr[0],
        birthMonth = strBirthdayArr[1],
        birthDay = strBirthdayArr[2],
        d = new Date(),
        nowYear = d.getFullYear(),
        nowMonth = d.getMonth() + 1,
        nowDay = d.getDate();
    if (nowYear == birthYear) {
        returnAge = 0; //同年 则为0周岁
    } else {
        var ageDiff = nowYear - birthYear; //年之差
        if (ageDiff > 0) {
            if (nowMonth == birthMonth) {
                var dayDiff = nowDay - birthDay; //日之差
                if (dayDiff < 0) {
                    returnAge = ageDiff - 1;
                } else {
                    returnAge = ageDiff;
                }
            } else {
                var monthDiff = nowMonth - birthMonth; //月之差
                if (monthDiff < 0) {
                    returnAge = ageDiff - 1;
                } else {
                    returnAge = ageDiff;
                }
            }
        } else {
            returnAge = -1; //返回-1 表示出生日期输入错误 晚于今天
        }
    }
    let resAge = ''
    resAge = returnAge;
    var NowYear = d.getFullYear();
    if (resAge > NowYear - 1) {
        resAge = "";
    }
    return resAge
}

10. js验证输入身份证是否合法

//验证身份证格式
export const IdentityCodeValid = sId => {
  const aCity = { 11: "北京", 12: "天津", 13: "河北", 14: "山西", 15: "内蒙古", 21: "辽宁", 22: "吉林", 23: "黑龙江", 31: "上海", 32: "江苏", 33: "浙江", 34: "安徽", 35: "福建", 36: "江西", 37: "山东", 41: "河南", 42: "湖北", 43: "湖南", 44: "广东", 45: "广西", 46: "海南", 50: "重庆", 51: "四川", 52: "贵州", 53: "云南", 54: "西藏", 61: "陕西", 62: "甘肃", 63: "青海", 64: "宁夏", 65: "新疆", 71: "台湾", 81: "香港", 82: "澳门", 91: "国外" };
  var iSum = 0;
  var info = "";
  if (!/^\d{17}(\d|X|x)$/i.test(sId)) return false;
  sId = sId.replace(/x$/i, "a");
  if (aCity[parseInt(sId.substr(0, 2))] == null) return false;
  var sBirthday = sId.substr(6, 4) + "-" + Number(sId.substr(10, 2)) + "-" + Number(sId.substr(12, 2));
  var d = new Date(sBirthday.replace(/-/g, "/"));
  if (sBirthday != (d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate())) return false;
  for (var i = 17; i >= 0; i--) iSum += (Math.pow(2, i) % 11) * parseInt(sId.charAt(17 - i), 11);
  if (iSum % 11 != 1) return false;
  //aCity[parseInt(sId.substr(0,2))]+","+sBirthday+","+(sId.substr(16,1)%2?"男":"女");//此次还可以判断出输入的身份证号的人性别
  return true;
}

11. 发布订阅模式

class EventEmitter {
    constructor() {
        this.cache = {}
    }
    on(name, fn) {
        if (this.cache[name]) {
            this.cache[name].push(fn)
        } else {
            this.cache[name] = [fn]
        }
    }
    off(name, fn) {
        let tasks = this.cache[name]
        if (tasks) {
            const index = tasks.findIndex(f => f === fn || f.callback === fn)
            if (index >= 0) {
                tasks.splice(index, 1)
            }
        }
    }
    emit(name, once = false, ...args) {
        if (this.cache[name]) {
            // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
            let tasks = this.cache[name].slice()
            for (let fn of tasks) {
                fn(...args)
            }
            if (once) {
                delete this.cache[name]
            }
        }
    }
}

// 测试
let eventBus = new EventEmitter()
let fn1 = function(name, age) {
	console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
	console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '布兰', 12)
// '布兰 12'
// 'hello, 布兰 12'

12. 存储,获取,删除 sessionStorage和localStorage

  • 12.1 设置sessionStorage或者localStorage(默认设置sessionStorage) 默认设置为sessionStorage。 第一个参数为设置名称, 第二个参数为赋的值, 第三个值isLocalStorage默认为false, 如果是真就设置的localStorage,否则是sessionStorage。 默认都将赋值序列化
export function setS(str, params, isLocalStorage = false) {
    const handleParams = JSON.stringify(params);
    if (isLocalStorage) {
        localStorage.setItem(str, handleParams);
    } else {
        sessionStorage.setItem(str, handleParams);
    }
}
  • 12.2 获取sessionStorage 或者localStorage(默认先获取localStorage) 这里有用到了try catch方法,因为若存储的值没有序列化,直接JSON.parse会报错。如果此时报错, 就直接返回未序列化的值
export function getS(data) {
    // 先获取localStorage数据, 如果没有再获取sessionStorage数据。 如果都没有, null;
    let getLocalData = localStorage.getItem(data);
    let getSessionData = sessionStorage.getItem(data);
    if (getLocalData) {
        try {
            getLocalData = JSON.parse(getLocalData);
        } catch (e) {
            // console.error(e);
        }
        return getLocalData;
    } else if (getSessionData) {
        try {
            getSessionData = JSON.parse(getSessionData);
        } catch (e) {
            // console.error(e);
        }
        return getSessionData;
    };
    return null;
}
  • 12.3 清空缓存 如果不传值,清空所有。
export function clearS(str) {
    if (!str) {
        sessionStorage.clear();
        localStorage.clear();
        return;
    }
    sessionStorage.removeItem(str);
    localStorage.removeItem(str);
}

13. 设置有效期的storage

因为不是特别常用, 所以就全都封装到localStorage中了

  • 13.1 设置有效期storage 每次设置的时候,都新建一个对象。 里边有data: 存储的数据, time: 设置时的时间, expire: 过期时间。
// 默认缓存一个小时
export function setTimeS(key, value, expire = 60 * 1000 * 60) {
        let obj = {
                data: value,
                time: Date.now(),
                expire: expire
        };
        localStorage.setItem(key) = JSON.stringify(obj));
}
  • 13.2 获取有限期storage 如果现在的时间减去之前设置时的时间大于设置的过期时间。 就将结果置为null
export function getTimeS(key) {
        const val = localStorage.getItem(key);
        if (!val) {
                return null;
        }
        val = JSON.parse(val);
        if (Date.now() - val.time > val.expire) {
                localStorage.removeItem(key);
                return null;
        }
        return val.data;
}

14. 根据数组中相同类型进行分类

// 根据数组中相同类型进行分类
export const classifyArray = (data, keys = []) => { //keys可以传一个数组
  var c = [];
  var d = {};
  for (var element of data) {
    let element_keyStr = "";
    let element_key = [];
    let element_keyObj = {};
    for (var key of keys) {
      element_key.push(element[key]);
      element_keyObj[key] = element[key];
    }
    element_keyStr = element_key.join("_");
    if (!d[element_keyStr]) {
      c.push({
        ...element_keyObj,
        children: [element]
      });
      d[element_keyStr] = element;
    } else {
      for (var ele of c) {
        let isTrue = keys.some(key => {
          return ele[key] != element[key];
        });
        if (!isTrue) {
          ele.children.push(element);
        }
      }
    }
  }
  return c;
}

比如,左边的图想根据taskType来将数组分类。 会转变成右边的数据类型

      

15. 返回账户金额, 每隔三位数字添加一个逗号,增加千分位


/**
 * 使用 javascript实现threeNumberAPointer方法
 * @description 分割数字, 每隔三位使用逗号分割一次
 * @param {number}
 * @returns {String} 返回分隔后的字符串
 *
 * @example
 * threeNumberAPointer(33)              // return '33'
 * threeNumberAPointer(1234.56)         // return '1,234.56'
 * threeNumberAPointer(123456789)       // return '123,456,789'
 * threeNumberAPointer(987654.321)      // return '987,654.321'
 * threeNumberAPointer(-987654.3)       // return '-987654.3'
 */


// 方法1 使用正则  # (推荐使用)
export function threeNumberAPointer(num) {
  let str = num.toString();
  let fixedLength = str.split('.')[1] ? str.split('.')[1].length : 0;
  return parseFloat(str).toFixed(fixedLength).replace(/\d(?=(?:\d{3})+\b)/g, `$&,`);
};

// 方法2 使用for循环
export function threeNumberAPointer(num) {
  let str = num.toString();
  let [left, right] = str.split('.');
  right = right === undefined ? '' : '.' + right;
  let leftArr = left.split('');
  let resultArr = [];
  let count = 0;
  for (let i = leftArr.length - 1; i >= 0; i--) {
    resultArr.unshift(leftArr[i]);
    count++;
    // 每隔三位且i!==0且i的前一位不是'-'的时候添加','
    if (count % 3 == 0 && i !== 0 && leftArr[i - 1] !== '-') {
      resultArr.unshift(',');
    }
  }
  return resultArr.join('').concat(right);
}

16. 返回银行卡号,每隔四个数字添加一个空格

/** 
 * 返回银行卡号,每隔四个数字添加一个空格
 * @param str:string
*/
export function formatBankCard(str) {
    let newStr = str.replace(/\d(?=(?:\d{4})+\b)/g, `$& `);
    return String(newStr)
}

17. 判断是否为空

参数不传, 空字符串, undefined, null都返回false。其余返回true

/**
 * @desc 根据传入的str,判断str是是否为空
 * @param {*} str 不能传递对象和数组 
 * @example 
 * isEmpty() // true
 * isEmpty(' ') // true
 * isEmpty(0) // false
 * isEmpty(321) // false
 * isEmpty(' 32 ') // false
 * isEmpty(undefined) // true
 * isEmpty(null) // true
 */
export const isEmpty = str => {
// function isEmpty(str){
  if (
    str == null ||
    typeof str == "undefined" ||
    (typeof str == "string" && str.trim() == "")
  ) {
    console.log("true");
    return true;
  } else {
    console.log("false");
    return false;
  }
}

18. 获取当前路径

export const getCurrentPageUrl = () => {
  let currentPageUrl = "";
  if (typeof this.href === "undefined") {
    currentPageUrl = document.location.toString().toLowerCase();
  } else {
    currentPageUrl = this.href.toString().toLowerCase();
  }
  console.log("11111------  currentPageUrl  -----11111");
  console.log(currentPageUrl);
  return currentPageUrl;
};

19. 完美判断是否为网址

/**
 * 完美判断是否为网址
 */
export const isURL = strUrl => {
  var regular = /^\b(((https?|ftp):\/\/)?[-a-z0-9]+(\.[-a-z0-9]+)*\.(?:com|edu|gov|int|mil|net|org|biz|info|name|museum|asia|coop|aero|[a-z][a-z]|((25[0-5])|(2[0-4]\d)|(1\d\d)|([1-9]\d)|\d))\b(\/[-a-z0-9_:\@&?=+,.!\/~%\$]*)?)$/i;
  if (regular.test(strUrl)) {
    return true;
  } else {
    return false;
  }
};

20. 原生JavaScript实现返回顶部的通用方法

export function backTop(btnId) {
    var btn = document.getElementById(btnId);
    var d = document.documentElement;
    var b = document.body;
    window.onscroll = set;
    btn.style.display = "none";
    btn.onclick = function() {
        btn.style.display = "none";
        window.onscroll = null;
        this.timer = setInterval(function() {
            d.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);
            b.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);
            if ((d.scrollTop + b.scrollTop) == 0) clearInterval(btn.timer, window.onscroll = set);
        },
        10);
    };
    function set() {
        btn.style.display = (d.scrollTop + b.scrollTop > 100) ? 'block': "none"
    }
};
backTop('goTop');

21. 原生JavaScript判断变量是否空值

/**
 * 判断变量是否空值
 * undefined, null, '', false, 0, [], {} 均返回true,否则返回false
 */
export function isEmpty(v){
    switch (typeof v){
        case 'undefined' : return true;
        case 'string'    : if(trim(v).length == 0) return true; break;
        case 'boolean'   : if(!v) return true; break;
        case 'number'    : if(0 === v) return true; break;
        case 'object'    : 
            if(null === v) return true;
            if(undefined !== v.length && v.length==0) return true;
            for(var k in v){return false;} return true;
            break;
    }
    return false;
}

22. 把字符串多余的部分用...来表示

/**
 * @desc 字符串截取算法  如果字符串的长度比给定的参数num长,则把多余的部分用...来表示。
 * 切记,插入到字符串尾部的三个点号也会计入字符串的长度。
 * 然而,如果指定的参数num小于或等于3,则添加的三个点号不会计入字符串的长度。
 * @return string
 * 使用方法 truncate(str,num)
 * @param {string} str 母字符串
 * @param {number} num 判断截取的长度
*/
export function truncate(str, num=6) {
  let rep = '...',len = 0
  if(num >= str.length){
      return str
  }
  if(num >3){
     len = 3
  }
  return str.slice(0,num - len)+rep;
}

23. js隐藏手机号中间四位,变成*或其他字符

/**
 * @desc 将手机号中间四位用*或其他字符代替
 * @return string
 * 使用方法 hideFourPhone(phone, str="*")
 * @param {phone} str 手机号
 * @param {str} str 替换的字符串
 * @example
 * hideFourPhone(18311056459)              // return '183****6459'
 */
export function hideFourPhone(phone, str = "*") {
    //  #推荐使用 方法1  使用正则
    let reg = /(\d{3})(\d{4})(\d{4})/;
    let res = phone.replace(
    reg,
    (m, g1, g2, g3) => g1 + [...g2].fill(str).join("") + g3
    );
    // 方法2  截取字符串
    let res2 =
    phone.slice(0, 3) +
    [...phone.slice(3, 7)].fill(str).join("") +
    phone.slice(7);
}

24. 正则判断是否为手机号

这里只判断纯手机号的情况, 对有前缀的或固定电话不适合

/**
 * @params {number,string} phone 手机号码, 可以为数字或字符串,允许有空格。
 * @return {boolean} 如果是电话号码,返回true,否则false
 * @example  以下情况都返回true
 * isValidPhone('18311056454');
 * isValidPhone(' 18311056454');
 * isValidPhone(' 18311056454 ');
 * isValidPhone( 18311056454 );
 */
export function isValidPhone(phone) {
    const phoneReg = /^1[3456789]\d{9}$/
    // 将参数先转变成字符串再去除其中的空格后去匹配正则
    if (phoneReg.test(phone.toString().replace(/\s+/g, ''))) {
        return true
    }
    return false
}

25. 获取url的相对路径

/**
 * 
 * @param {str} url  
 * @description 参数为一个url,只获取这个url的相对路径。 不要前边的http部分和参数
 * @method  首先获取url,然后把url 通过 // 截成两部分(若有//, 否则直接取整个url),
 * 再从后一部分中截取相对路径。若截取到的相对路径中有参数,则把参数去掉。
 * @example 
 * getUrlRelativePath("https://www.baidu.com")      
 * getUrlRelativePath("https://www.baidu.com/andy/fight")      /andy/fight
 * getUrlRelativePath("www.baidu.com/andy/fight2?name2='凌云'&age")   /andy/fight2
 * getUrlRelativePath('http// www. liangshunet. com/pub/item.aspx?t=osw7')   /pub/item.aspx
 */
export function getUrlRelativePath(url) {
  let rightUrlArr = url.includes("//") ? url.split("//")[1] :  url.split()[0];
  let start = rightUrlArr.indexOf("/");
  // 如果indexOf('/')返回-1,说明url没有相对路径,返回空字符串
  if(start === -1){
    return '';
  };
  
  //stop省略,截取从start开始到结尾的所有字符
  let relUrl = rightUrlArr.substring(start); 
  if (relUrl.indexOf("?") != -1) {
    relUrl = relUrl.split("?")[0];
  }
  return relUrl;
}

26. 获取传入时间与当前时间的间隔,多少时间以前

/**
* 计算传入时间距离当前时间有多久。 
* @param {Date} 
* @example
* timeBefore(new Date('2020-07-16'))            11小时前
* timeBefore(new Date('2020-07-16 19:19:22'))    43秒前
* timeBefore(new Date('2020-07-16 19:00:00'))   19分钟前
* timeBefore(new Date('2019-03-04 19:00:00'))    1年前
* timeBefore(new Date('2020-03-04 19:00:00'))    4个月前
*/
export function timeBefore(date) {
  if (!date) {
    console.log('请传入时间')
    return ''
  }
  let dvalue = parseInt(new Date().getTime()) - parseInt(new Date(date).getTime());
  let minTime = 60 * 1000;
  let hourTime = 60 * 60 * 1000;
  let dayTime = 24 * hourTime;
  let monthTime = 30 * dayTime;
  let yearTime = 12 * monthTime;
  if (dvalue < 0) {
    console.log('传入的时间有误')
    return '';
  } else if (dvalue < minTime) {
    return parseInt(dvalue / 1000) + '秒前';
  } else if (dvalue >= minTime && dvalue < hourTime) {
    return parseInt(dvalue / minTime) + '分钟前';
  } else if (dvalue >= hourTime && dvalue < dayTime) {
    return parseInt(dvalue / hourTime) + '小时前';
  } else if (dvalue >= dayTime && dvalue < monthTime) {
    return parseInt(dvalue / dayTime) + '天前';
  } else if (dvalue >= monthTime && dvalue < yearTime) {
    return parseInt(dvalue / monthTime) + '月前';
  } else if (dvalue >= yearTime) {
    return parseInt(dvalue / yearTime) + '年前';
  }
}

27. 加载图片

/**
 * 
 * @param {Array} arr 保存图片的数组
 * @param {} callback 图片全部加载完毕后的回调
 */
export function imgLoadAll(arr, callback) {
    var arrImg = [];
    for (var i = 0; i < arr.length; i++) {
        var img = new Image();
        console.log(img);
        img.src = arr[i];
        img.onload = function () {
            arrImg.push(this);
            if (arrImg.length == arr.length) {
                callback && callback();
            }
        }
    }
}

28. 将数字转换成大写金额

/**
 * 将数字转换成大写金额 
 * 最多只能匹配到千万级别。 只能匹配到小数点后2位。 允许数字和字符串,允许有¥
 * 
 * @param {*} Num 
 * @example 
 * changeToChinese(987654); // 玖拾捌万柒仟陆佰伍拾肆元整
 * changeToChinese(1214.23); // 壹仟贰佰壹拾肆元贰角叁分
 * changeToChinese("456.7"); // 肆佰伍拾陆元柒角
 * changeToChinese("¥999.2323"); // 壹仟贰佰壹拾肆元贰角叁分
 * changeToChinese(" 9 9 9. 07 2 8  "); // 玖佰玖拾玖元柒分
 */
export function changeToChinese(Num) {
    if (typeof Num == "number") {
        Num = new String(Num);
    };
    //判断如果传递进来的不是字符的话转换为字符
    Num = Num.replace(/,/g, "") //替换tomoney()中的“,”
    Num = Num.replace(/ /g, "") //替换tomoney()中的空格
    Num = Num.replace(/¥/g, "") //替换掉可能出现的¥字符
    if (isNaN(Num)) { //验证输入的字符是否为数字
        //alert("请检查小写金额是否正确");
        console.log('请检查小写金额是否正确')
        return "";
    };
    //字符处理完毕后开始转换,采用前后两部分分别转换
    var part = String(Num).split(".");
    var newchar = "";
    //小数点前进行转化
    for (var i = part[0].length - 1; i >= 0; i--) {
        if (part[0].length > 10) {
            return "";
            //若数量超过拾亿单位,提示
        }
        var tmpnewchar = ""
        var perchar = part[0].charAt(i);
        switch (perchar) {
            case "0":
                tmpnewchar = "零" + tmpnewchar;
                break;
            case "1":
                tmpnewchar = "壹" + tmpnewchar;
                break;
            case "2":
                tmpnewchar = "贰" + tmpnewchar;
                break;
            case "3":
                tmpnewchar = "叁" + tmpnewchar;
                break;
            case "4":
                tmpnewchar = "肆" + tmpnewchar;
                break;
            case "5":
                tmpnewchar = "伍" + tmpnewchar;
                break;
            case "6":
                tmpnewchar = "陆" + tmpnewchar;
                break;
            case "7":
                tmpnewchar = "柒" + tmpnewchar;
                break;
            case "8":
                tmpnewchar = "捌" + tmpnewchar;
                break;
            case "9":
                tmpnewchar = "玖" + tmpnewchar;
                break;
        }
        switch (part[0].length - i - 1) {
            case 0:
                tmpnewchar = tmpnewchar + "元";
                break;
            case 1:
                if (perchar != 0) tmpnewchar = tmpnewchar + "拾";
                break;
            case 2:
                if (perchar != 0) tmpnewchar = tmpnewchar + "佰";
                break;
            case 3:
                if (perchar != 0) tmpnewchar = tmpnewchar + "仟";
                break;
            case 4:
                tmpnewchar = tmpnewchar + "万";
                break;
            case 5:
                if (perchar != 0) tmpnewchar = tmpnewchar + "拾";
                break;
            case 6:
                if (perchar != 0) tmpnewchar = tmpnewchar + "佰";
                break;
            case 7:
                if (perchar != 0) tmpnewchar = tmpnewchar + "仟";
                break;
            case 8:
                tmpnewchar = tmpnewchar + "亿";
                break;
            case 9:
                tmpnewchar = tmpnewchar + "拾";
                break;
        }
        var newchar = tmpnewchar + newchar;
    }
    //小数点之后进行转化
    if (Num.indexOf(".") != -1) {
        if (part[1].length > 2) {
            // alert("小数点之后只能保留两位,系统将自动截断");
            part[1] = part[1].substr(0, 2)
        }
        for (i = 0; i < part[1].length; i++) {
            tmpnewchar = ""
            perchar = part[1].charAt(i)
            switch (perchar) {
                case "0":
                    tmpnewchar = "零" + tmpnewchar;
                    break;
                case "1":
                    tmpnewchar = "壹" + tmpnewchar;
                    break;
                case "2":
                    tmpnewchar = "贰" + tmpnewchar;
                    break;
                case "3":
                    tmpnewchar = "叁" + tmpnewchar;
                    break;
                case "4":
                    tmpnewchar = "肆" + tmpnewchar;
                    break;
                case "5":
                    tmpnewchar = "伍" + tmpnewchar;
                    break;
                case "6":
                    tmpnewchar = "陆" + tmpnewchar;
                    break;
                case "7":
                    tmpnewchar = "柒" + tmpnewchar;
                    break;
                case "8":
                    tmpnewchar = "捌" + tmpnewchar;
                    break;
                case "9":
                    tmpnewchar = "玖" + tmpnewchar;
                    break;
            }
            if (i == 0) tmpnewchar = tmpnewchar + "角";
            if (i == 1) tmpnewchar = tmpnewchar + "分";
            newchar = newchar + tmpnewchar;
        }
    }
    //替换所有无用汉字
    while (newchar.search("零零") != -1)
        newchar = newchar.replace("零零", "零");
    newchar = newchar.replace("零亿", "亿");
    newchar = newchar.replace("亿万", "亿");
    newchar = newchar.replace("零万", "万");
    newchar = newchar.replace("零元", "元");
    newchar = newchar.replace("零角", "");
    newchar = newchar.replace("零分", "");
    if (newchar.charAt(newchar.length - 1) == "元") {
        newchar = newchar + "整"
    }
    console.log('%c newchar ', 'background-image:color:transparent;color:blue;font-size:2em');
    console.log(newchar);
    return newchar;
}

29. 距离当前时间差

/**
 * 传递的参数可以是'y-m-d h:m:s' 或者10位或13位的时间戳
 * 传递参数距离当前时间差
 * @param date1
 * @returns {String}
 * *天 **时**分**秒
 * @example 
 * timeToNow('2020-02-21') // 152天 07时12分04秒
 * timeToNow(1583265723000); // 140天 07时00分10秒
 * timeToNow(1583265723); // 140天 07时00分10秒
 * timeToNow(13283265723); //请输入合法的日期
 * timeToNow('2020-02-21 03:04:04') // 152天 07时12分04秒
 * timeToNow('2020/07/21 03:04:04') // 1天 07时15分19秒
 * timeToNow('2010-09-21 03:04:04') // 3592天 07时15分41秒
 * timeToNow('2020-09-21 03:04:04') // 0天 00时00分00秒
 * timeToNow('20200921 03:04:04') //  请传入合法的日期
 * timeToNow(new Date()) //  请传入合法的日期
 */
export function timeToNow(date1) {
    //兼容微信浏览器,主动格式化时间字符串
    // 当只有年月日 给时分秒补0
    let setTime = 0;
    let regExp = /-|\//
    if ((Number(date1) + '').length === 13) {
        setTime = Number(date1)
    } else if ((Number(date1) + '').length === 10) {
        setTime = Number(date1) * 1000
    } else if (typeof date1 === 'number' || date1 === '' || date1 === undefined) {
        console.log('请输入合法的日期')
        return;
    } else {
        if (date1.indexOf(' ') === -1) {
            date1 = `${date1} 00:00:00`
        }
        let arr1 = date1.split(' ');
        let sdate = arr1[0].split(regExp);
        let sTime = arr1[1].split(':');
        let date = new Date(sdate[0], sdate[1] - 1, sdate[2], sTime[0], sTime[1], sTime[2]);
        setTime = new Date(date).getTime();
    }
    let timer = null;
    let nowTime = new Date().getTime(),
        leftTime = 0,
        d = 0,
        h = 0,
        m = 0,
        s = 0;
    leftTime = Math.ceil((nowTime - setTime) / 1000);
    if (nowTime >= setTime) {
        d = ~~(leftTime / 86400);
        h = ~~(leftTime % 86400 / 3600);
        m = ~~(leftTime % 86400 % 3600 / 60);
        s = ~~(leftTime % 86400 % 3600 % 60);
    }
    if ((h + '').length == 1) {
        h = '0' + h;
    }
    if ((m + '').length == 1) {
        m = '0' + m;
    }
    if ((s + '').length == 1) {
        s = '0' + s;
    }
    let res = d + '天 ' + h + '时' + m + '分' + s + '秒';
    console.log('%c res ', 'background-image:color:transparent;color:blue;font-size:2em');
    console.log(res);
    return res
}

31. 获取十六进制随机颜色

/**
 *  获取十六进制随机颜色
 * @return {string} 
 * @example
 * getRandomColor()  #bdeb64
 * getRandomColor()  #5f283d
 */
function getRandomColor() {
    let res = '#' + (function (h) {
        return new Array(7 - h.length).join("0") + h;
    })((Math.random() * 0x1000000 << 0).toString(16));
    console.log(res)
    return res;
};

32. 横线或下划线转驼峰命名

/**
 * 横线或下划线转驼峰命名
 * @export
 * @param {string} str 将要转换的字符串
 * @returns {string}
 * @example
 * camelize('init_data')  // 'initData'
 * camelize('init-data')  // 'initData'
 * camelize('init-first-word')  // 'initFirstWord'
 */
export function camelize(str) {
    const camelizeRE = /[-_](\w)/g;
    return str.replace(camelizeRE, function (_, c) {
        return c ? c.toUpperCase() : ''
    })
}

33. 驼峰命名转横线命名

/**
 *  驼峰命名转横线命名:拆分字符串,使用 - 相连,并且转换为小写
 * @example 
 * hyphenate('initFirstWord')  // 'init-first-word'
 * hyphenate('GetFirstName')   // 'get-first-name'
 * **/
function hyphenate(str) {
	 let hyphenateRE = /\B([A-Z])/g;
	 let res = str.replace(hyphenateRE, '-$1').toLowerCase();
	 return res;
 }

34. 字符串首字母大写

/**
 * 字符串首字母大写
 * @example
 * capitalize('my name is andy凌云')   // 'My name is andy凌云'
 */
export function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
}

35. 获取浏览器信息

/**
 * 获取浏览器信息
 * @export
 * @returns {*}
 * @example
 * Firefox // { type: "Firefox", version: 78 }
 * Chrome  // {type: "Chrome", version: 84}
 */
export function getExplorerInfo() {
    let t = navigator.userAgent.toLowerCase();
    return 0 <= t.indexOf("msie") ? { //ie < 11
        type: "IE",
        version: Number(t.match(/msie ([\d]+)/)[1])
    } : !!t.match(/trident\/.+?rv:(([\d.]+))/) ? { // ie 11
        type: "IE",
        version: 11
    } : 0 <= t.indexOf("edge") ? {
        type: "Edge",
        version: Number(t.match(/edge\/([\d]+)/)[1])
    } : 0 <= t.indexOf("firefox") ? {
        type: "Firefox",
        version: Number(t.match(/firefox\/([\d]+)/)[1])
    } : 0 <= t.indexOf("chrome") ? {
        type: "Chrome",
        version: Number(t.match(/chrome\/([\d]+)/)[1])
    } : 0 <= t.indexOf("opera") ? {
        type: "Opera",
        version: Number(t.match(/opera.([\d]+)/)[1])
    } : 0 <= t.indexOf("Safari") ? {
        type: "Safari",
        version: Number(t.match(/version\/([\d]+)/)[1])
    } : {
        type: t,
        version: -1
    }
}

36. 检测是否为PC端浏览器模式

/**
 * 检测是否为PC端浏览器模式
 * @export
 * @return {Boolean}
 */
export function isPCBroswer() {
    let e = navigator.userAgent.toLowerCase(),
        t = "ipad" == e.match(/ipad/i),
        i = "iphone" == e.match(/iphone/i),
        r = "midp" == e.match(/midp/i),
        n = "rv:1.2.3.4" == e.match(/rv:1.2.3.4/i),
        a = "ucweb" == e.match(/ucweb/i),
        o = "android" == e.match(/android/i),
        s = "windows ce" == e.match(/windows ce/i),
        l = "windows mobile" == e.match(/windows mobile/i);
    return !(t || i || r || n || a || o || s || l)
}

37. 浏览器全屏


/**
 * 浏览器全屏 f11 
 * @export
 */
export function toFullScreen() {
    let elem = document.body;
    elem.webkitRequestFullScreen ?
        elem.webkitRequestFullScreen() :
        elem.mozRequestFullScreen ?
        elem.mozRequestFullScreen() :
        elem.msRequestFullscreen ?
        elem.msRequestFullscreen() :
        elem.requestFullScreen ?
        elem.requestFullScreen() :
        alert("浏览器不支持全屏");
}

38. 退出浏览器全屏

/**
 * 退出全屏 
 * @export
 */
export function exitFullscreen() {
    let elem = parent.document;
    elem.webkitCancelFullScreen ?
        elem.webkitCancelFullScreen() :
        elem.mozCancelFullScreen ?
        elem.mozCancelFullScreen() :
        elem.cancelFullScreen ?
        elem.cancelFullScreen() :
        elem.msExitFullscreen ?
        elem.msExitFullscreen() :
        elem.exitFullscreen ?
        elem.exitFullscreen() :
        alert("切换失败,可尝试Esc退出");
}

39. 检查两个对象是否完全相等

/**
 * 检测两对象是否一致
 * @param {object} x 目标对象1
 * @param {object} y 目标对象2
 * @return {Boolean} 
 */
export function equal(x, y) {
    var i, l, leftChain, rightChain;
    function compare2Objects(x, y) {
        var p;
        // remember that NaN === NaN returns false
        // and isNaN(undefined) returns true
        if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
            return true;
        }
        // Compare primitives and functions.
        // Check if both arguments link to the same object.
        // Especially useful on the step where we compare prototypes
        if (x === y) {
            return true;
        }
        // Works in case when functions are created in constructor.
        // Comparing dates is a common scenario. Another built-ins?
        // We can even handle functions passed across iframes
        if ((typeof x === 'function' && typeof y === 'function') ||
            (x instanceof Date && y instanceof Date) ||
            (x instanceof RegExp && y instanceof RegExp) ||
            (x instanceof String && y instanceof String) ||
            (x instanceof Number && y instanceof Number)) {
            return x.toString() === y.toString();
        }
        // At last checking prototypes as good as we can
        if (!(x instanceof Object && y instanceof Object)) {
            return false;
        }
        if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
            return false;
        }
        if (x.constructor !== y.constructor) {
            return false;
        }
        if (x.prototype !== y.prototype) {
            return false;
        }
        // Check for infinitive linking loops
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
            return false;
        }
        // Quick checking of one object being a subset of another.
        // todo: cache the structure of arguments[0] for performance
        for (p in y) {
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false;
            } else if (typeof y[p] !== typeof x[p]) {
                return false;
            }
        }
        for (p in x) {
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false;
            } else if (typeof y[p] !== typeof x[p]) {
                return false;
            }
            switch (typeof (x[p])) {
                case 'object':
                case 'function':
                    leftChain.push(x);
                    rightChain.push(y);
                    if (!compare2Objects(x[p], y[p])) {
                        return false;
                    }
                    leftChain.pop();
                    rightChain.pop();
                    break;
                default:
                    if (x[p] !== y[p]) {
                        return false;
                    }
                    break;
            }
        }
        return true;
    }
    if (arguments.length < 1) {
        return true; //Die silently? Don't know how to handle such case, please help...
        // throw "Need two or more arguments to compare";
    }
    for (i = 1, l = arguments.length; i < l; i++) {

        leftChain = []; //Todo: this can be cached
        rightChain = [];

        if (!compare2Objects(arguments[0], arguments[i])) {
            return false;
        }
    }
    return true;
}

40. 判断两个对象属性名是否完全相等

  • 应用场景: 在实际开发过程中经常会遇到以下场景。 后端文档中提供示例参数, 前端以此为根据将数据传递给后端。 发现接口报错的情况。这时候,如果靠肉眼去分辨传递参数是否有误,就比较耗时且容易出错。 所以最好能封装成函数通过比较两个对象中的属性名是否完全相同来达到确定参数是否传错的目的。
  • 解决思路: 分别获取两个对象的属性名组成数组, 然后给两个数组排序。 通过every方法,比较所有的参数项。如果都相等返回true,否则返回false
/**
* 
* @param {Object} a  对象a
* @param {Object} b  对象b
* @example 对象a和对象b的属性名完全相等 返回true
*
let obj1 = {
   "classCode": "11",
   "supplier": "22",
};
let obj2 = {
   "supplier": "33",
   classCode: "44",
};
* @result true 
*/
export function isObjectKeyEqual(a, b) {
     // 获取对象a和对象b, 属性名称的数组集合
     let obj1 = Object.getOwnPropertyNames(a);
     let obj2 = Object.getOwnPropertyNames(b);
     if (obj1.length != obj2.length) {
          console.error('两个对象的属性名长度不等');
          return false;
     }
     // 给两个数组集合排序
     obj1.sort();
     obj2.sort();
     // 分别遍历obj1和obj2,只有当每一项都相等才说明两者属性名完全相等
     let res = obj1.every((v, i) => {
          if(v !== obj2[i]){
     		  console.log(`属性名 ${v} 不等`);
     	  }
          return v === obj2[i];
     })
     if (!res) {
          console.error('两个对象的属性长度相等但名不等');
     } else {
          console.log('两个对象的属性名完全相等');
     }
     return res;
}

41. 复杂数组去重

/**
 * 复杂数组去重
 * 数组内部, 不管是引用数据类型还是基础数据类型, 只要是相等。 就去重
 * @param {Array} 
 * @example 
 * [123, "123", 123]  // [123, '123']
 * [[1, 2, 3], [1, "2", 3], [1, 2, 3]] // [[1, 2, 3], [1, "2", 3]]
 * [{ a: 1 }, { a: { b: 1, a: 2 } }, { a: { a: 2, b: 1 } }] // [{ a: 1 }, { a: { b: 1, a: 2 } }]
 * ***/

function equals(a, b) {
	const type = Object.prototype.toString.call(a);
	if (type !== Object.prototype.toString.call(b)) { // 类型不一致,肯定不相等
		return false;
	} else if (type === '[object Array]') { // 数组
		if (a.length !== b.length) return false;
		for (let i = 0; i < a.length; i++) {
			if (!equals(a[i], b[i])) { // 遍历数组,递归判断
				return false;
			}
		}
		return true;
	} else if (type === '[object Object]') { // 对象
		// 转成entries进行数组比较,entries需要排序一下,相等的object排序结果应是一致的
		const entriesA = Object.entries(a)
			.sort();
		const entriesB = Object.entries(b)
			.sort();
		return equals(entriesA, entriesB);
	} else {
		return a === b;
	}
}

function unique(target) {
	const results = [];
	for (let i = 0; i < target.length; i++) {
		if (results.every(result => !equals(target[i], result))) {
			results.push(target[i]);
		}
	}
	return results;
}

const arr1 = [123, "123", 123];
const arr2 = [123, [1, 2, 3], [1, "2", 3], [1, 2, 3]];
const arr3 = [{ a: 1 }, { a: { b: 1, a: 2 } }, { a: { a: 2, b: 1 } }];
console.log(unique(arr1));
console.log(unique(arr2));
console.log(unique(arr3));

42. 深克隆, 简单版本

/*
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
export function deepClone(source) {
    if (!source && typeof source !== 'object') {
        throw new Error('error arguments', 'deepClone')
    }
    const targetObj = source.constructor === Array ? [] : {}
    Object.keys(source).forEach(keys => {
        if (source[keys] && typeof source[keys] === 'object') {
            targetObj[keys] = deepClone(source[keys])
        } else {
            targetObj[keys] = source[keys]
        }
    })
    return targetObj
}

43. JS对象深度合并

// JS对象深度合并
export function deepMerge(target = {}, source = {}) {
	target = deepClone(target);
	if (typeof target !== 'object' || typeof source !== 'object') return false;
	for (var prop in source) {
		if (!source.hasOwnProperty(prop)) continue;
		if (prop in target) {
			if (typeof target[prop] !== 'object') {
				target[prop] = source[prop];
			} else {
				if (typeof source[prop] !== 'object') {
					target[prop] = source[prop];
				} else {
					if (target[prop].concat && source[prop].concat) {
						target[prop] = target[prop].concat(source[prop]);
					} else {
						target[prop] = deepMerge(target[prop], source[prop]);
					}
				}
			}
		} else {
			target[prop] = source[prop];
		}
	}
	return target;
}

44. 封装一个判断javascript类型的函数

主要判断三种情况, 为null, 引用数据类型和基础数据类型并分别处理。
引用数据类型用Object.prototype.toString.call判断得到数据类型后, 用split根据切割成数组并用slice去掉最后一个字符串最后全部转为小写
/**
 * @param {*} val
 * getType(new RegExp()) regexp
 * getType(new Date()) date
 * getType([]) array
 * getType({}) object
 * getType(null) null
 */
export function getType(val) {
    if (typeof val === 'object') {
        const objVal = Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
        return objVal;
    } else {
        return typeof val;
    }
}

45. 获取随机的整数

/**
 * randomExtend() // 1-10
 * randomExtend(10) // 1-10
 * randomExtend(1, 10) // 1-10
 */
export function randomExtend(minNum = 0, maxNum = 10) {
  if (arguments.length === 1) {
    return parseInt(Math.random() * minNum + 1, 10)
  } else {
    return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
  }
}

46. js延迟sleep函数

很多编程语言里都有sleep(),delay()等方法,它能让我们的程序不那么着急的去执行下一步操作,而是延迟、等待一段时间。软件开发中经常会遇到需要这样的函数,比如等待几分钟去检查某一事件是否发生。 下面这个方法用promise封装了setTimeout来进行延迟等待。

export function sleep (time) {
  return new Promise((resolve) => setTimeout(resolve, time));
}

总结:

公共函数可以极大的简化开发流程,有利于代码的组件化和模块化开发,是提高工作效率和减少代码bug的利器。这篇文章后续还会有持续的补充和完善。如果有发现出错或可以优化的地方还望指出。