前端工具类utils --01

116 阅读6分钟

一、进阶工具类方法:

/* eslint-disable no-irregular-whitespace */
// 算法
// 获取数据类型
const getType = (obj) => {
    const class2type = {
        '[object Boolean]': 'boolean',
        '[object Number]': 'number',
        '[object String]': 'string',
        '[object Function]': 'function',
        '[object Array]': 'array',
        '[object Date]': 'date',
        '[object RegExp]': 'regexp',
        '[object Object]': 'object',
        '[object Error]': 'error',
        '[object Symbol]': 'symbol'
    }

    if (obj == null) {
        return obj + ''
    }
    // javascript高级程序设计中提供了一种方法,可以通用的来判断原始数据类型和引用数据类型
    const str = Object.prototype.toString.call(obj)
    console.log('str', str)
    console.log('typeof obj', typeof obj)
    return typeof obj === 'object' || typeof obj === 'function' ? class2type[str] || 'object' : typeof obj
}

// 拆解URL参数中queryString
const getUrlQuery = (queryStr) => {
    //const [, query] = queryStr.split('?')
    // 去除hash标识
    const [query,] = queryStr.split('?')[1].split('#')
    if (query) {
        return query.split('&').reduce((pre, cur) => {
            const [key, val] = cur.split('=')
            pre[key] = decodeURIComponent(val)
            return pre
        }, {})
    }
    return {}
}

// 字符串相加
/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
const addStrings = function (num1, num2) {
    if (num1 === num2 && num1 === "0") {
        return num1
    }

    //  切割成数组倒过来,倒过来是因为可能存在长度不一的字符串
    num1 = num1.split("").reverse();
    num2 = num2.split("").reverse();
    const len = Math.max(num1.length, num2.length);
    let flag = 0;
    const result = [];

    for (let i = 0; i < len; i++) {
        const n1 = +num1[i] || 0;
        const n2 = +num2[i] || 0;
        let sum = n1 + n2 + flag;
        flag = 0;

        //  进1
        if (sum > 9) {
            sum -= 10
            flag = 1
        }
        result.push(sum);
    }
    //  仍存在进1标志,手动进1
    if (flag) {
        result.push(flag)
    }

    return result.reverse().join("");
}

// 翻转字符串里的单词
const reverseWords = function (s) {
    return s.trim().split(/\s+/).reverse().join(' ');
};

// 二分查找
const binarySearch = function (nums, target) {
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        const mid = Math.floor((right - left) / 2) + left;
        const num = nums[mid];
        if (num === target) {
            return mid;
        } else if (num > target) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return -1;
}

// 两个数组的交集
const intersection = function (nums1, nums2) {
    const set_intersection = (set1, set2) => {
        if (set1.size > set2.size) {
            return set_intersection(set2, set1);
        }
        const intersection = new Set();
        for (const num of set1) {
            if (set2.has(num)) {
                intersection.add(num);
            }
        }
        return [...intersection];
    }

    const set1 = new Set(nums1);
    const set2 = new Set(nums2);
    return set_intersection(set1, set2);
};

// 两数之和下标
const twoSumIndex = (nums, target) => {
    const prevNums = {};                    // 存储出现过的数字,和对应的索引               

    for (let i = 0; i < nums.length; i++) {       // 遍历元素   
        const curNum = nums[i];                     // 当前元素   
        const targetNum = target - curNum;          // 满足要求的目标元素   
        const targetNumIndex = prevNums[targetNum]; // 在prevNums中获取目标元素的索引
        if (targetNumIndex !== undefined) {         // 如果存在,直接返回 [目标元素的索引,当前索引]
            return [targetNumIndex, i];
        } else {                                    // 如果不存在,说明之前没出现过目标元素
            prevNums[curNum] = i;                     // 存入当前的元素和对应的索引
        }
    }
}

// 基本计算器(字符串)
const calculate = function (s) {
    const ops = [1];
    let sign = 1;

    let ret = 0;
    const n = s.length;
    let i = 0;
    while (i < n) {
        if (s[i] === ' ') {
            i++;
        } else if (s[i] === '+') {
            sign = ops[ops.length - 1];
            i++;
        } else if (s[i] === '-') {
            sign = -ops[ops.length - 1];
            i++;
        } else if (s[i] === '(') {
            ops.push(sign);
            i++;
        } else if (s[i] === ')') {
            ops.pop();
            i++;
        } else {
            let num = 0;
            while (i < n && !(isNaN(Number(s[i]))) && s[i] !== ' ') {
                num = num * 10 + s[i].charCodeAt() - '0'.charCodeAt();
                i++;
            }
            ret += sign * num;
        }
    }
    return ret;
}

// 验证IP地址
const validIPAddress = (queryIP) => {
    return queryIP.match(/^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)($|(?!\.$)\.)){4}$/) ? "IPv4" :
        queryIP.match(/^(([\da-fA-F]{1,4})($|(?!:$):)){8}$/) ? "IPv6" : "Neither";
}

// 过滤 JSON 中的无效条目
const filterByID = (item) => {
    if (Number.isFinite(item.id) && item.id !== 0) {
        return true;
    }
    return false;
}

// 判断某个字符串长度(要求支持表情)
const unicodeLength = (str) => {
    return Array.from(str).length
}

// 循环递归深拷贝
const deepClone = (obj, hash = new WeakMap()) => {
    if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
    if (typeof obj !== "object") return obj;
    // 是对象的话就要进行深拷贝
    if (hash.get(obj)) return hash.get(obj);
    let cloneObj = new obj.constructor();
    // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
    hash.set(obj, cloneObj);
    for (let key in obj) {
        //   if (obj.hasOwnProperty(key)) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {

            // 实现一个递归拷贝
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }
    return cloneObj;
}

//  判断页面是通过PC端还是移动端访问
const isMobile = () => {
    if (/Mobi|Android|iPhone/i.test(navigator.userAgent)) {
        // 当前设备是移动设备
        return true
    }
    return false
}

// 获取全局对象
const getGlobal = () => {
    if (typeof self !== 'undefined') {
        return self
    }
    if (typeof window !== 'undefined') {
        return window
    }
    if (typeof global !== 'undefined') {
        return global
    }
    throw new Error('无法找到全局对象')
}

// 归并排序
const mergeSort = (arr) => {
    let array = mergeSortRec(arr)
    return array
}
// 若分裂后的两个数组长度不为 1,则继续分裂
// 直到分裂后的数组长度都为 1,
// 然后合并小数组
const mergeSortRec = (arr) => {
    let lenth = arr.length
    if (lenth == 1) {
        return arr
    }
    let mid = Math.floor(lenth / 2)
    let left = arr.slice(0, mid)
    let right = arr.slice(mid, lenth)
    return merge(mergeSortRec(left), mergeSortRec(right))
}

const merge = (left, right) => {
    let result = [],
        ileft = 0,
        iright = 0
    while (ileft < left.length && iright < right.length) {
        if (left[ileft] < right[iright]) {
            // eslint-disable-next-line no-irregular-whitespace
            result.push(left[ileft++])
        } else {
            result.push(right[iright++])
        }
    }
    while (ileft < left.length) {
        result.push(left[ileft++])
    }
    while (iright < right.length) {
        result.push(right[iright++])
    }
    return result
}

// 数组扁平化、去重、排序
const newArray = (arr) => {
    while (arr.some(Array.isArray)) {
        arr = [].concat(...arr)
    }
    arr = [...new Set(arr)].sort((a, b) => a - b)
    return arr
}

// add方法
const add = (...args) => {
    const _add = (...args1) => {
        return add(...args, ...args1)
    }
    _add.value = () => args.reduce((t, e) => t + e)
    return _add
}

// 快速排序算法
const sortArray = (nums) => {
    quickSort(0, nums.length - 1, nums);
    return nums;
}

const quickSort = (start, end, arr) => {
    if (start < end) {
        const mid = sort(start, end, arr);
        quickSort(start, mid - 1, arr);
        quickSort(mid + 1, end, arr);
    }
}

const sort = (start, end, arr) => {
    const base = arr[start];
    let left = start;
    let right = end;
    while (left !== right) {
        while (arr[right] >= base && right > left) {
            right--;
        }
        arr[left] = arr[right];
        while (arr[left] <= base && right > left) {
            left++;
        }
        arr[right] = arr[left];
    }
    arr[left] = base;
    return left;
}

// 带并发的异步调度器 Scheduler
// 页面引用 const scheduler = new Scheduler();
class Scheduler {
    constructor() {
        this.waitTasks = []; // 待执行的任务队列
        this.excutingTasks = []; // 正在执行的任务队列
        this.maxExcutingNum = 2// 允许同时运行的任务数量
    }

    add(promiseMaker) {
        if (this.excutingTasks.length < this.maxExcutingNum) {
            this.run(promiseMaker);
        } else {
            this.waitTasks.push(promiseMaker);
        }
    }

    run(promiseMaker) {
        const len = this.excutingTasks.push(promiseMaker);
        const index = len - 1;
        promiseMaker().then(() => {
            this.excutingTasks.splice(index, 1);
            if (this.waitTasks.length > 0) {
                this.run(this.waitTasks.shift());
            }
        });
    }
}

// 浮点数保留两位小数

// 将浮点数四舍五入,取小数点后2位
// 注⚠️,数据类型不变。
const toDecimal = (x) => {
    var f = parseFloat(x);
    if (isNaN(f)) {
        return;
    }
    f = Math.round(x * 100) / 100;
    return f;
}

// 强制保留2位小数,如:2,会在2后面补上00.即2.00
// 注意⚠️,数据类型变为字符串类型。
const toDecimal2 = (x) => {
    var f = parseFloat(x);
    if (isNaN(f)) {
        return false;
    }
    var f = Math.round(x * 100) / 100;
    var s = f.toString();
    var rs = s.indexOf('.');
    if (rs < 0) {
        rs = s.length;
        s += '.';
    }
    while (s.length <= rs + 2) {
        s += '0';
    }
    return s;
}

// 保留两位小数 浮点数四舍五入 位数不够 不补0
// 注意⚠️,数据类型不变。
const fomatFloat = (src, pos) => {
    return Math.round(src * Math.pow(10, pos)) / Math.pow(10, pos);
}

//加法函数,用来得到精确的加法结果
//说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
//调用:accAdd(arg1,arg2)
//返回值:arg1加上arg2的精确结果
const accAdd = (arg1, arg2) => {
    var r1, r2, m;
    try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
    try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
    m = Math.pow(10, Math.max(r1, r2))
    return (arg1 * m + arg2 * m) / m
}
//给Number类型增加一个add方法,调用起来更加方便。
Number.prototype.add = function (arg) {
    return accAdd(arg, this);
}

//减法函数,用来得到精确的减法结果
//说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的减法结果。
//调用:accSub(arg1,arg2)
//返回值:arg1减去arg2的精确结果
const accSub = (arg1, arg2) => {
    var r1, r2, m, n;
    try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
    try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
    m = Math.pow(10, Math.max(r1, r2));
    //last modify by deeka
    //动态控制精度长度
    n = (r1 >= r2) ? r1 : r2;
    return ((arg1 * m - arg2 * m) / m).toFixed(n);
}

//除法函数,用来得到精确的除法结果
//说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
//调用:accDiv(arg1,arg2)
//返回值:arg1除以arg2的精确结果
const accDiv = (arg1, arg2) => {
    var t1 = 0, t2 = 0, r1, r2;
    try { t1 = arg1.toString().split(".")[1].length } catch (e) { }
    try { t2 = arg2.toString().split(".")[1].length } catch (e) { }
    while (Math) {
        r1 = Number(arg1.toString().replace(".", ""))
        r2 = Number(arg2.toString().replace(".", ""))
        return (r1 / r2) * Math.pow(10, t2 - t1);
    }
}
//给Number类型增加一个div方法,调用起来更加方便。
Number.prototype.div = function (arg) {
    return accDiv(this, arg);
}

//乘法函数,用来得到精确的乘法结果
//说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
//调用:accMul(arg1,arg2)
//返回值:arg1乘以arg2的精确结果
const accMul = (arg1, arg2) => {
    var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
    try { m += s1.split(".")[1].length } catch (e) { }
    try { m += s2.split(".")[1].length } catch (e) { }
    return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m)
}
//给Number类型增加一个mul方法,调用起来更加方便。
Number.prototype.mul = function (arg) {
    return accMul(arg, this);
}

// obj是获取属性的对象,path是路径,fallback是默认值
const getAttributeObj = (obj, path, fallback) => {
    const parts = path.split(".");
    const key = parts.shift();
    if (typeof obj[key] !== "undefined") {
        return parts.length > 0 ?
        getAttributeObj(obj[key], parts.join("."), fallback) :
        obj[key];
    }
    // 如果没有找到key返回fallback
    return fallback;
}
// 防止XSS攻击:对接口返回的数据进行过滤和转义处理
const escapeHtml = (unsafe) => {
    return unsafe
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

// 截断数字
const toFixedNum = (n, fixed) => `${n}`.match(new RegExp(`^-?\d+(?:.\d{0,${fixed}})?`))[0]

//四舍五入
const roundNum = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`)

// 数字补零
const replenishZero = (num, len, zero = 0) => num.toString().padStart(len, zero)

// 字符串转对象
const strParse = (str) => JSON.parse(str.replace(/(\w+)\s*:/g, (_, p1) => `"${p1}":`).replace(/\'/g, "\""))

// uuid
const uuid = (a) => (a ? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid))
uuid()

// 强制等待
const sleep = async (t) => new Promise((resolve) => setTimeout(resolve, t));

export {
    getType,
    getUrlQuery,
    addStrings,
    reverseWords,
    binarySearch,
    intersection,
    twoSumIndex,
    calculate,
    validIPAddress,
    filterByID,
    unicodeLength,
    deepClone,
    isMobile,
    getGlobal,
    mergeSort,
    newArray,
    add,
    sortArray,
    Scheduler,
    // 浮点数保留两位小数
    toDecimal,
    toDecimal2,
    fomatFloat,
    // 浮点数加减乘除
    accAdd,
    accSub,
    accMul,
    accDiv,
    // 递归获取对象属性
    getAttributeObj,
    escapeHtml,
    toFixedNum,
    roundNum,
    replenishZero,
    strParse,
    uuid,
    sleep,


}