前端常用函数整理

前端常用函数整理(我可是把收藏的方法都放上来了,给个好评!)


/**
    window.scroll(x, y), window.scrollTo(x, y) 功能一致, 设置滚动条滚动距离
    window.scrollBy(x, y) 设置滚动条滚动距离, 在之前的值的基础上累加; 例子:可实现自动阅读功能
 
    遍历时间上: for循环遍历 < for…of遍历 < forEach遍历 < for…in遍历 < map遍历
    for循环遍历可以通过 break 关键字跳出循环

    
    解决苹果手机输入框聚焦后,页面自动放大问题 -- 【重要】
        input, input:focus,
        textarea, textarea:focus,
        select, select:focus{
            font-size: 16px !important;
        }

    扩展:
        cookie: 最好放在 HTTP 请求的头信息 Authorization 字段里面

    postcss-pxtorem 是一款 postcss 插件,用于将单位转化为 rem

    深拷贝的解决方案 :
        使用lodash 插件
        使用递归解决深拷贝
        如果数据中没有函数,undefined 可以使用json.stringify+json.parse实现深拷贝

    超出显示省略号:
        white-space	nowrap	让文字在一行内显示, 不换行
        overflow	hidden	当内容超过盒子宽度, 隐藏溢出部分
        text-overflow	ellipsis	如果溢出的内容是文字, 就用省略号代替
        -webkit-line-clamp	数字	控制可以显示的行数

        单行:
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;

        多行:
            overflow: hidden; //超出的文本隐藏
            text-overflow: ellipsis; //溢出用省略号显示
            display: -webkit-box;
            -webkit-line-clamp: 2; // 超出多少行
            -webkit-box-orient: vertical;

    滚动条样式:
        ::-webkit-scrollbar {
            width: 12px;
            height: 10px;
        }
        ::-webkit-scrollbar-thumb {
            height: 30px;
            background-color: rgb(153 153 153);
            -webkit-border-radius: 7px;
            outline-offset: -2px;
            border: 2px solid rgb(255 255 255);
        }
        ::-webkit-scrollbar-track-piece {
            background-color: rgb(255 255 255);
            -webkit-border-radius: 3px;
        }
        

    原生深拷贝 structuredClone:
        structuredClone()


    微信小程序地理位置问题:
        微信官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#requiredPrivateInfos
        详情:https://blog.csdn.net/qq_41602125/article/details/126710029
        在 app.json 中,与 pages 同级:
            精确位置信息: 
                "requiredPrivateInfos": [
                    "getFuzzyLocation", 
                    "getLocation",
                    "onLocationChange",
                    "startLocationUpdateBackground"
                    "chooseAddress"
                ]
            模糊位置信息: 
                "requiredPrivateInfos": [
                    "getFuzzyLocation"
                ]
        注意:声明了模糊位置信息就无法声明精确位置信息。
             若同时声明模糊位置信息和精确位置信息,在编译代码时会出现错误
        

    navigator.onLineAPI 来检测网络状态(兼容性有点差)

    匹配字符串:只能是数字或小数; d{1,3} 可以修改匹配个数
    rex = /^((0{1}\.\d{1,3})|([1-9]\d*\.{1}\d{1,3})|([1-9]+\d*))$/;
*/





window.myPlugin = {

    /**
     * 关键字样式
     * @param {Array} list 原数组
     * @param {Object} params 参数对象
     *    class     样式类
     *    style     行内样式
     *    keywords  关键字
     *    callback  回调函数
     * @returns {Array} 新数组
     */
    keywordsStyleFun(list, params) {
        if (!Array.isArray(list)) throw Error('keywordsReplace方法的参数1不是一个数组!');
        if (typeof params !== 'object' || params === null) throw Error('keywordsReplace方法的参数2(对象)不是一个对象!');
        if (!params.keywords) throw Error('keywordsReplace方法的参数2(对象)的属性keywords不能为空!');
        params = Object.assign({
            class: 'keywords_class',
            style: 'color:#1890ff;',
            keywords: '',
            childListName: 'childList',
            callback: () => { },
        }, params);
        let keywordsStyle = `<span class="${params.class}" style="${params.style}">${params.keywords}</span>`;
        // let keywordsStyle = `<rich-text class="${params.class}" style="${params.style}" nodes="${params.keywords}"></rich-text>`; // 微信小程序
        return list.map(item => {
            let row = {
                isKeywords: false,
                ...item
            }
            if (row.title.indexOf(params.keywords) > -1) {
                row.isKeywords = true;
                row.title = row.title.replace(params.keywords, keywordsStyle);
            }
            if (row[params.childListName] && row[params.childListName].length > 0) {
                row[params.childListName] = row[params.childListName].map(childItem => {
                    let childRow = {
                        isKeywords: false,
                        ...childItem
                    }
                    if (childRow.childTitle.indexOf(params.keywords) > -1) {
                        childRow.isKeywords = true;
                        childRow.childTitle = childRow.childTitle.replace(params.keywords,
                            keywordsStyle);
                    }
                    return childRow;
                });
            }
            return row;
        });
    },


    /**
     * 找到最接近 n 的值
     * @param {Array} arr 数组
     * @param {Number|String} n 
     * @param {String} key 对象的键名
     * @returns 数字或对象
     */
    closest(arr, n, key) {
        if (key) {
            return arr[arr.map(item => Math.abs(item[key] - n)).findIndex((val, index, findArr) => Math.min(...findArr) == val)];
        } else {
            return arr[arr.map(item => Math.abs(item - n)).findIndex((val, index, findArr) => Math.min(...findArr) == val)];
        }
    },

    /**
     * 把base64转成File对象
     * @param {String} base64Data base64数据
     * @param {String} name File对象的名称
     * @returns File对象
     */
    base64ToFile(base64Data, name) {
        const dataArr = base64Data.split(',')
        const type = dataArr[0].match(/:(.*);/)[1]
        const byteString = window.atob(dataArr[1])
        const buffer = new ArrayBuffer(byteString.length)
        const arr = new Uint8Array(buffer)
        for (let i = 0; i < byteString.length; i++) {
            arr[i] = byteString.charCodeAt(i)
        }
        return new File([buffer], name, { type })
    },


    /**
     * 使用 jsqr 插件解析二维码: npm install jsqr
     * 需要在 html 中创建: 
     *  <canvas ref="myCanvas" id="myCanvas" width="600" height="600" style="display: none" />
     * @param {} file 上传的二维码图片 e.target.files[0]
     * @returns {} 
     */
    jsqrParseQRCode(file) {
        // jsqr解析二维码
        let img = new Image()
        let reader = new FileReader();
        reader.readAsDataURL(file)
        // console.log(file)
        reader.onload = (e) => {
            console.log(e)
            img.src = e.target.result
            let myCanvas = document.getElementById('myCanvas');
            //   let myCanvas = _this.$refs.myCanvas;
            let myCanvaswd = myCanvas.getContext('2d')
            img.onload = function () {
                myCanvaswd.drawImage(img, 0, 0, 600, 600)
                let imageData = myCanvaswd.getImageData(0, 0, 600, 600)
                let picData = {}
                /*
                    imageData.data: Uint8ClampedArray表单中的一个RGBA像素值
                    imageData.width: 要解码的图像的宽度
                    imageData.height: 要解码的图像的高度
                    inversionAttempts: 
                        attemptBoth(默认)  尝试两者
                        dontInvert         不要倒置
                        onlyInvert         仅反转
                        invertFirst        反转第一个
                */
                picData = jsqr(imageData.data, imageData.width, imageData.height, {
                    inversionAttempts: 'attemptBoth'
                })
                console.log(picData)
                if (picData && picData.data) {

                } else {
                    //   _this.$message.error('识别二维码失败,请重新上传');
                }
            }
        }
    },

    /**
     * 按字母排序(忽略大小写)
     * @param {Array} arr 需要排序的数组
     * @param {String} key 若,数组的元素为对象,key 为键名
     * @param {Array} arr 
     * @returns {Array} 新数组
     * 备注: 
     *   1.若需要对姓名的拼音首字母进行排序,可使用当前目录的 pinyin.js 的方法提取姓名的拼音
     *   2.若对姓名的拼音首字母进行分组,先使用 sortAlphabetically 方法排序,再使用 grouping 方法分组
     *    参考例子:
            let arr = ['张三', '李四', '王五', '赵六', '黄蓉', '小明', '小红', '老六', '饕餮']
            let newArr = arr.map(item => {
                let row = {};
                row.pinyin = pinyin.getFullChars(item);
                row.initial = row.pinyin.split('')[0];
                row.name = item;
                return row;
            });
            let sortNewArr = myPlugin.sortAlphabetically(newArr, 'initial');
            console.log(sortNewArr)
            let list = myPlugin.grouping(sortNewArr, 'initial');
            console.log(list)
     * 
     */
    sortAlphabetically(arr, key) {
        let list = arr.slice();
        if (key) {
            list.sort(function (s1, s2) {
                x1 = s1[key].toUpperCase();
                x2 = s2[key].toUpperCase();
                if (x1 < x2) {
                    return -1;
                }
                if (x1 > x2) {
                    return 1;
                }
                return 0;
            });
        } else {
            list.sort(function (s1, s2) {
                x1 = s1.toUpperCase();
                x2 = s2.toUpperCase();
                if (x1 < x2) {
                    return -1;
                }
                if (x1 > x2) {
                    return 1;
                }
                return 0;
            });
        }
        return list;
    },

    /**
     * 两日期之间相差的天数
     * @param {Date, Date} date1 日期1
     * @param {Date, Date} date2  日期2
     * @returns {Number} 
     */
    getDayDiff(date1, date2) {
        date1 = typeof date1 == 'string' ? new Date(date1) : date1;
        date2 = typeof date2 == 'string' ? new Date(date2) : date2;
        // 86400000 = 1000 * 60 * 60 * 24  一天
        return Math.ceil(Math.abs(date1.getTime() - date2.getTime()) / 86400000);
    },

    /**
     * 将华氏温度转换为摄氏温度
     * @param {Number|String} 度数
     * @returns {Number} 
     */
    fahrenheitToCelsius(fahrenheit) {
        return (fahrenheit - 32) * 5 / 9;
    },
    /**
     * 将摄氏温度转华氏温度
     * @param {Number|String} 度数
     * @returns {Number} 
     */
    celsiusToFahrenheit(celsius) {
        return celsius * 9 / 5 + 32;
    },

    /**
     * 获取参数/获取浏览器url参数,并转化为对象
     * @param {String} search 默认 获取浏览器url参数
     * @returns {} 
     * 注意:若 URLSearchParams 无效 ,请 npm install url-search-params-polyfill
     */
    getLocationSeearchParams(search) {
        var str = search || window.location.search;
        var url = new URL(str);
        var searchParams = new URLSearchParams(str);
        // 把键值对列表转换为一个对象
        var paramsObj = Object.fromEntries(searchParams.entries());
        url.paramsObj = paramsObj;
        return url
    },

    /**
     * 屏蔽部分字符串
     * @param {String} str
     * @returns {String} 
     */
    maskedPartString(str) {
        return str.replace(/^(.{4})(?:\d+)(.{4})$/, "$1******$2");
    },

    /**
     * 获取星期的中文和是否是工作日
     * @param {Number} week  0-6
     * @returns {String} 
     *    '星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'
     */
    getWeekCN(week) {
        let weekArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
        // 是否是工作日
        let isWeekday = week % 6 !== 0 ? true : false;
        return {
            isWeekday,
            weekCN: weekArr[week],
        }
    },

    /**
     * 获取当前日期的前几天/后几天
     * @param {String|Date} dateVal  日期:2022-09-01
     * @param {Number} symbol  负数时,为前几天; 正数是,为后几天
     * @returns {Object} 
     *      返回当前日期 前几天/后几天 的对象
     *      包含 日期字符串、日期对象、年、月、日、时、分、秒、星期、时间戳
     */
    getPrevNextDate(dateVal = new Date(), symbol) {
        let date = new Date(dateVal);
        date.setDate(date.getDate() + symbol);
        let year = date.getFullYear();
        let month = date.getFullYear();
        let day = date.getFullYear();
        let hour = date.getFullYear();
        let minute = date.getFullYear();
        let second = date.getFullYear();

        month = month + 1 < 10 ? "0" + (month + 1) : month + 1;
        day = day < 10 ? "0" + day : day;

        hour = hour + 1 < 10 ? "0" + (hour + 1) : hour + 1;
        minute = minute + 1 < 10 ? "0" + (minute + 1) : minute + 1;
        second = second + 1 < 10 ? "0" + (second + 1) : second + 1;

        let dateText = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
        let week = date.getDay(); // 0 为星期日
        let timeStamp = date.getTime(); // 时间戳
        return {
            dateText,
            date,
            year,
            month,
            day,
            hour,
            minute,
            second,
            week,
            timeStamp
        }
    },

    /**
     * 判断两个日期是否在范围之内
     * @param {String} date1  时间:2022-09-01
     * @param {String} date2  时间:2022-09-30
     * @param {Number} differ 设置的两个日期范围,如:30, 2022-09-01 ~ 2022-09-30
     * @returns {Boolean} true 范围在 differ 内; false 范围在 differ 外
     */
    operatingFormatter(date1, date2, differ = 30) {
        differ = Math.sign(differ) == 1 ? -differ : differ;
        let GetDateStr = function (date, AddDayCount) {
            var dd = new Date(date);
            if (dd == 'Invalid Date') throw Error(dd)
            dd.setDate(dd.getDate() + AddDayCount);//获取AddDayCount天后的日期  
            var y = dd.getFullYear();
            var m = (dd.getMonth() + 1) < 10 ? "0" + (dd.getMonth() + 1) : (dd.getMonth() + 1);//获取当前月份的日期,不足10补0  
            var d = dd.getDate() < 10 ? "0" + dd.getDate() : dd.getDate();//获取当前几号,不足10补0  
            return y + "-" + m + "-" + d;
        }
        let checkDate = function (date1, date2) {
            var oDate1 = new Date(date1);
            var oDate2 = new Date(date2);
            if (oDate1 == 'Invalid Date') throw Error(oDate1)
            if (oDate2 == 'Invalid Date') throw Error(oDate2)
            if (oDate1.getTime() > oDate2.getTime()) {
                return true; // 范围在 differ 内
            } else {
                return false; // 范围在 differ 外
            }
        }
        return checkDate(date1, GetDateStr(date2, differ));
    },

    /**
     * JS转换时间戳为“刚刚”、“1分钟前”、“2小时前”“1天前”等格式
     * @param {String} stringTime  时间:2022-09-07
     * @returns {String}  
     */
    getTimer(stringTime) {
        let minute = 1000 * 60; // 1分钟
        let hour = minute * 60; // 1小时 1000*60*60
        let day = hour * 24; // 1天 1000*60*60*24
        let week = day * 7; // 1周 1000*60*60*24*7
        let month = day * 30; // 1个月 1000*60*60*24*30
        let time1 = new Date().getTime();//当前的时间戳
        let time2 = Date.parse(new Date(stringTime));//指定时间的时间戳
        let time = time1 - time2;
        let result = null;
        if (time < 0) {
            console.log("设置的时间不能早于当前时间!");
        } else if (time / month >= 1) {
            result = "发布于" + parseInt(time / month) + "月前!";
        } else if (time / week >= 1) {
            result = "发布于" + parseInt(time / week) + "周前";
        } else if (time / day >= 1) {
            result = "发布于" + parseInt(time / day) + "天前";
        } else if (time / hour >= 1) {
            result = "发布于" + parseInt(time / hour) + "小时前";
        } else if (time / minute >= 1) {
            result = "发布于" + parseInt(time / minute) + "分钟前";
        } else {
            result = "刚刚";
        }
        return result;
    },

    /**
     * 判断两个时间是否有交集
     * @param {*} start1  开始时间1
     * @param {*} end1    结束时间1
     * @param {*} start2  开始时间2
     * @param {*} end2    结束时间2
     * @returns {Boolean} true || false
     */
    isDateIntersection(start1, end1, start2, end2) {
        var startdate1 = new Date(start1.replace("-", "/").replace("-", "/"));
        var enddate1 = new Date(end1.replace("-", "/").replace("-", "/"));
        var startdate2 = new Date(start2.replace("-", "/").replace("-", "/"));
        var enddate2 = new Date(end2.replace("-", "/").replace("-", "/"));

        if (startdate1 >= startdate2 && startdate1 <= enddate2) {
            return true;
        }
        if (enddate1 >= startdate2 && enddate1 <= enddate2) {
            return true;
        }
        if (startdate1 <= startdate2 && enddate1 >= enddate2) {
            return true;
        }
        return false;
    },


    /**
     * 计算小时差
     * 若是要计算从开始时间到结束时间共几天的话需要加1
     * startDate: 开始时间;格式: YYYY-MM-DD 或者 YYYY-MM-DD hh:mm:ss
     * endDate: 结束时间;格式: YYYY-MM-DD 或者 YYYY-MM-DD hh:mm:ss
     * */
    differHourTime(startDate, endDate) { // 一小时等于60 * 60 * 1000毫秒
        return Math.floor((new Date(endDate).getTime() - new Date(startDate).getTime()) / 3600000)
    },

    /**
     * 计算分钟差
     * 若是要计算从开始时间到结束时间共几天的话需要加1
     * startDate: 开始时间;格式: YYYY-MM-DD 或者 YYYY-MM-DD hh:mm:ss
     * endDate: 结束时间;格式: YYYY-MM-DD 或者 YYYY-MM-DD hh:mm:ss
     * */
    differMinuteTime(startDate, endDate) { // 一分钟等于60 * 1000毫秒
        return Math.floor((new Date(endDate).getTime() - new Date(startDate).getTime()) / 60000)
    },

    /**
     * 判断是否是空值
     */
    keyIsNull(val) {
        if (val === '' || val === null || val === undefined) return true;
        return false;
    },

    /**
     * 获取当前日期30天以前的日期
     * @returns {String} 返回字符串日期,如:2022-10-01
     */
    getDateAfter30() {
        let date = new Date();
        // 获取三十天前日期
        // 最后一个数字30可改,30天的意思
        var lw = new Date(date - 1000 * 60 * 60 * 24 * 30);
        var lastY = lw.getFullYear();
        var lastM = lw.getMonth() + 1;
        var lastD = lw.getDate();
        // 三十天之前日期
        var startdate = lastY + "-" + (lastM < 10 ? "0" + lastM : lastM) + "-" + (lastD < 10 ? "0" + lastD : lastD);
        return startdate;
    },

    /**
     * 保留小数(没有四舍五入)
     * @param {Number} val 数值
     * @param {Number} amount 保留多少位(默认保留2位)
     * @returns {Number|String} 返回数值|字符串
     */
    keepDecimals(val, amount = 2) {
        if (val === null || val === '' || val === undefined) return '';
        let arr = String(val).split('.');
        if (arr[1]) {
            let zero = '';
            let len = arr[1].length;
            if (len < amount) {
                for (let i = 0; i < amount - len; i++) {
                    zero += '0';
                }
                return arr[0] + '.' + arr[1].slice(0, amount) + zero;
            }
            return arr[0] + '.' + arr[1].slice(0, amount)
        }
        return parseFloat(val).toFixed(amount);
    },

    /**
     * AES 加密和解密 -----------------------------------------
     * import CryptoJS from 'crypto-js'
     * 使用 AES.encrypt(oldPassword,'absoietlj32fai12');
     */
    encrypt(word, keyStr) {
        keyStr = keyStr ? keyStr : "absoietlj32fai12";
        let key = CryptoJS.enc.Utf8.parse(keyStr);
        let srcs = CryptoJS.enc.Utf8.parse(word);
        let encrypted = CryptoJS.AES.encrypt(srcs, key, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        });
        return encrypted.toString();
    },
    //解密
    decrypt(word, keyStr) {
        keyStr = keyStr ? keyStr : 'absoietlj32fai12';
        var key = CryptoJS.enc.Utf8.parse(keyStr);//Latin1 w8m31+Yy/Nw6thPsMpO5fg==
        var decrypt = CryptoJS.AES.decrypt(word, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });
        return CryptoJS.enc.Utf8.stringify(decrypt).toString();
    },
    /**
     * AES 加密和解密 -----------------------------------------
     */



    /**
     * 获取滚动条高度
     */
    getScrollTop() {
        let scroll_top = 0;
        if (document.documentElement && document.documentElement.scrollTop) {
            scroll_top = document.documentElement.scrollTop;
        } else if (document.body) {
            scroll_top = document.body.scrollTop;
        }
        return scroll_top;
    },

    /**
     * 导出 .doc | .xlsx | .docx
     */
    printBlob(res, type, name) {
        let fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
        // 0: .doc  1: .xlsx  2 .docx
        if (type == 0) {
            fileType = 'application/msword'
        } else if (type == 1) {
            fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        } else if (type == 2) {
            fileType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8'
        }

        let blob = new Blob([res], { type: fileType });   //规定文件类型 res就是返回你的数据流了
        let link = document.createElement('a');
        // link.download = fileName//a标签添加属性
        let objectUrl = URL.createObjectURL(blob);
        link.setAttribute("href", objectUrl);
        link.setAttribute("download", name);
        link.click();
        /*
            释放内存:
                URL.revokeObjectURL()方法会释放一个通过URL.createObjectURL()创建的对象URL. 
                当你要已经用过了这个对象URL,然后要让浏览器知道这个URL已经不再需要指向对应的文件的时候,
                就需要调用这个方法.
        */
        window.URL.revokeObjectURL(link.href)
    },

    /**
     * 过滤无效的数值
     * @param {Number} val 数值
     * @returns {Number|String} 返回数值|字符串
     */
    numFilter(val) {
        if (val === null || val === '--') return '--';
        return val;
    },

    /**
     * 修改字体大小 rem
     */
    rem() {
        let htmlW = document.documentElement.clientWidth; // html的宽度
        let bodyW = document.body.clientWidth; // body宽度
        let w = htmlW | bodyW; // 兼容ie,屏幕宽度
        // 100px = 1rem
        document.documentElement.style.fontSize = (w / 750 * 100) + 'px';
    },

    /**
     * 生成指定数目和范围的随机数
     * @param {Number} min 最小数字
     * @param {Number} max 最大数字
     * @param {Number} countNum 指定数目
     * @returns {Number|Array} 返回数值(不包括 max )
     */
    rangeRandomNum(min, max, countNum) {
        if (countNum) {
            let arr = [];
            for (let i = 0; i < countNum; i++) {
                let n = Math.floor(Math.random() * (max - min)) + min;
                if (arr.indexOf(n) == -1) arr.push(n);
            }
            return arr;
        }
        return Math.floor(Math.random() * (max - min)) + min;
    },


    /**
     * 是否为正整数
     * @param {String|Number} s 数字
     * @returns {Boolean} 返回 true | false
     */
    isPositiveNum(s) {
        let re = /^[0-9]*[1-9][0-9]*$/;
        return re.test(s);
    },

    /**
     * 格式化数值(默认等于超过 10000 显示如:1W,1W+ 的格式)
     * @param {String|Number} n 正整数数值
     * @param {String|Number} quantity 保留多少位小数(默认:1)
     * @returns {String|Number} 返回数字
     */
    formatTenThousand(n, quantity, digits) {
        n = parseInt(n);
        quantity = quantity || 1;
        digits = digits || 10000;
        if (n < digits) return n;
        if (n == digits) return '1W';
        let num = Number(n) / 10000;
        let integer = String(num).split('.')[0]; // 整数部分
        let decimal = String(num).split('.')[1]; // 小数部分
        let decimalAmount = decimal.substr(0, quantity) // 小数多少位
        return integer + '.' + decimalAmount + 'W+';

        // n = parseInt(n);
        // digit = digit || 1;
        // if (n < 10000) return n;
        // let num = Number(n) / 10000;
        // let re = /^[0-9]*[1-9][0-9]*$/;
        // // 是否是正整数
        // if (re.test(n)){
        //     return num.toFixed(digit) + 'W+'
        //     // return num + 'W';
        // }  
        // if (num > 1) return num.toFixed(digit) + 'W+';
        // return n;
    },

    /**
     * 判断是否是【闰年】
     * @param {String|Number} year 年份
     * @returns {Boolean} 返回 true | false
     */
    isLeapYear(year) {
        let flag = false;
        if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
            flag = true;
        }
        return flag;
    },

    /**
     * 编码/解码
     * @param {Any} target 要编码的目标
     * @param {Boolean} type true 编码(默认);false 解码
     * @returns {Array} 返回一个保留 digit 位的小数
     */
    encodedDecode: function (target, type) {
        if (type) {
            // 编码
            return encodeURIComponent(JSON.stringify(target));
        } else {
            // 解码
            return JSON.parse(decodeURIComponent(target))
        }
    },


    /**
     * 数组降维
     * @param {Array} arr 要降维的数组
     * @returns {Array} 返回一个新数组
     * 也可用 ES6 中,数组方法 flat()
     */
    reduceArray: function (arr) {
        let newArr = [];
        // begin: 传入的newArr, current: 当前值
        arr.reduce((begin, current) => {
            Array.isArray(current) ?
                begin.push(...arguments.callee(current)) :
                begin.push(current);
            return begin
        }, newArr);
        return newArr;

        // 方式二(递归):
        // var result = [];
        // arr.forEach(function (item) {
        //     if (Array.isArray(item)) {
        //         result = result.concat(flatten(item));
        //     } else {
        //         result.push(item);
        //     }
        // });
        // return result;

    },

    /***
     * 格式化数字,按千位逗号分隔
     * @param {Number|String} num 数值
     * @param {Boolean} decimal 保留多少位小数(默认:2)
     * @return {String} 返回格式化后的数值字符串
     */
    formatNumber: function (num, decimal = 2) {
        let temp = '';
        if (num == '0.00') return '0.00';
        if (num == 0) return 0;
        if (num == null || num == '') return '';
        if (num.toString().indexOf('.') > -1) {
            let n = parseFloat(num).toFixed(decimal);
            temp = n.toString().split('.');
            if (temp[1]) {
                return temp[0].replace(/(\d)(?=(\d{3})+$)/g, '$1,') + '.' + (temp[1] || '00');
            } else {
                return temp[0].replace(/(\d)(?=(\d{3})+$)/g, '$1,')
            }
        } else {
            return num.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,');
        }

        // 将浮点数点左边的数每三位添加一个逗号
        // return number.toLocaleString('en')


        // let temp = '';
        // if(num.toString().indexOf('.') > -1){
        //     temp = num.toString().split('.');
        //     return temp[0].replace(/(\d)(?=(\d{3})+$)/g,'$1,') + '.' + (temp[1] || '00');
        // }else{
        //     return num.toString().replace(/(\d)(?=(\d{3})+$)/g,'$1,');
        // }
        // return temp;    
    },

    /***
     * 判断是否为对象或数组,且不能为 null
     * @param {Object|Array} obj 传入的对象或数组
     * @return {Boolean} 返回 true | false
     */
    isObject: function (obj) {
        return typeof obj === 'object' && obj !== null;
    },

    /***
     * 不能为负数,最多保留两位小数
     * @param {Number|String} num 传入的数字
     * @return {Boolean} 返回 true | false
     */
    notLossTowDecimals: function (num) {
        let reg = /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/;
        return reg.test(num)
    },

    /***
     * 保留多少位小数(百分比也可传入:'33.3333333333304%' )
     * @param {Number|String} num 传入的数值
     * @param {Number|String} digit 需要保留多少位小数(默认:2)
     * @return {Number|String} 返回数字或字符串数字或字符串百分比
     */
    formatFloat: function (num, digit) {
        digit = digit || 2;
        let reg = /%/g;
        if (num === null || num === '--') return '--';
        if (reg.test(num)) {
            return Number(num.split('%')[0]).toFixed(digit) + '%';
        }
        return Number(num).toFixed(digit);
    },

    /**
     * 小数位四舍五入(百分比不行)
     * @param {Number} num 要格式化的数值
     * @param {Number} digit 表示保留多少位小数
     * @returns {Number} 返回一个保留 digit 位的小数
     */
    formatFloat2: function (num, digit) {
        let pow = Math.pow(10, digit); // 表示保留多少位小数
        return (Math.round(num * pow) / pow).toFixed(digit);
    },

    /***
     * 获取两个数组中不共有的元素
     * @param {Array} a1 传入的数组
     * @param {Array} a2 传入的数组
     * @param {String} prop 根据那个元素对象的属性进行获取
     * @return {Array} newArr 返回一个新数组
     * 【注意:a2 的元素必须都是能在 a1 中找到】
     */
    getDiffElement: function (a1, a2, prop) {
        let arr1 = a1, arr2 = a2;
        if (a1.length < a2.length) {
            arr1 = a2;
            arr2 = a1;
        }
        // 获取两个数组中不共有的对象
        if (prop) {
            let newArr = [];
            arr1.forEach((v1, i1, a1) => {
                let boo = false;
                arr2.forEach((v2, i2, a2) => {
                    if (v1[prop] == v2[prop]) boo = true;
                });
                if (!boo) {
                    newArr.push(v1);
                }
            });
            return newArr;
        }

        let newArr1 = [], newArr2 = [];
        newArr1 = arr1.filter(item => {
            return !arr2.includes(item)
        });
        newArr2 = arr2.filter(item => {
            return !arr1.includes(item)
        });
        return [].concat(newArr2, newArr1);
    },

    /***
     * 获取两个数组中共有的元素
     * @param {Array} arr1 传入的数组
     * @param {Array} arr2 传入的数组
     * @param {String} prop 根据那个元素对象的属性进行获取
     * @return {Array} newArr 返回一个新数组
     * 【注意:arr2 的元素必须都是能在 arr1 中找到】
     */
    intersection: function (arr1, arr2, prop) {
        let newArr = [];
        arr1.forEach(item => {
            arr2.forEach(val => {
                if (prop && item[prop] == val[prop]) {
                    newArr.push(item);
                } else if (item == val) {
                    if (item == val) newArr.push(item);
                }
            });
        });
        return newArr;
    },

    /***
     * 根据指定格式返回【日期对象】
     * @param {time, format, type} 
     *         date:      获取的时间(一定是Date对象)
     *         format:    日期返回格式(默认为 '-')
     *         type:      选择返回年月日(date)或时分秒(time)或两种都有(默认:all)
     * @return {Date} 返回【日期对象】
     */
    setDateObj(time, format, type) {
        type = type || 'all';
        format = format || '-';
        let dateStrs = [];
        let timeStrs = [];
        let date = new Date();
        if (type == 'date') {
            if (!time) {
                throw Error(`The parameter format requires "yy-mm-dd", but the obtained parameter is ${time}`);
            }
            dateStrs = time.split(format);
            date.setFullYear(dateStrs[0]); // 设置某一年
            date.setMonth(dateStrs[1] - 1); // 设置某个月
            date.setDate(dateStrs[2]); // 设置某一天
        } else if (type == 'time') {
            if (!time) {
                throw Error(`The parameter format requires "hh-mm-ss", but the obtained parameter is ${time}`);
            }
            timeStrs = time.split(':');
            date.setHours(timeStrs[0]); // 设置小时
            date.setMinutes(timeStrs[1]); // 设置分钟
            date.setSeconds(timeStrs[2]); // 设置秒
        } else if (type == 'all') {
            let text = `The parameter format requires "yy-mm-dd hh-mm-ss", but the obtained parameter is ${time}`
            let timeArr = time.split(' ');
            if (!timeArr[0]) throw Error(text);
            if (!timeArr[1]) throw Error(text);
            dateStrs = timeArr[0].split(format);
            timeStrs = timeArr[1].split(':');
            date.setFullYear(dateStrs[0]); // 设置某一年
            date.setMonth(dateStrs[1] - 1); // 设置某个月
            date.setDate(dateStrs[2]); // 设置某一天
            date.setHours(timeStrs[0]); // 设置小时
            date.setMinutes(timeStrs[1]); // 设置分钟
            date.setSeconds(timeStrs[2]); // 设置秒
        }
        return date;
    },

    /***
     * 根据Date对象或Date字符串 拆分为对象
     * @param {Date} date 获取的Date对象 | Date字符串(2022-4-1 12:12:12)
     * @param {String} format 根据年月日格式拆分字符串(默认为 '-')
     * @param {String} calendar 选择返回年月日或时分秒或两种都有 
     * @return {Object} 返回对象
     *      { year, month, day, hour, minutes, seconds }
     */
    splitDate(date, format, calendar) {
        calendar = calendar || 'all';
        format = format || '-';
        let year, month, day, hour, minutes, seconds;

        if (typeof date == 'string') {
            let timeStr = [], dateStr = [], timeArr = [],
                dateArr = [], timeAll = [];
            if (date.indexOf(" ") == -1) {
                if (date.indexOf(":") == -1) {
                    dateStr = date.split(format); // 年月日数组
                } else {
                    timeStr = date.split(':'); // 时分秒数组
                }
            } else {
                timeAll = date.split(' ');
                timeArr = timeAll[1];
                dateArr = timeAll[0];
                timeStr = timeArr.split(':'); // 时分秒数组
                dateStr = dateArr.split(format); // 年月日数组
            }
            return {
                year: dateStr[0], // 年
                month: dateStr[1], // 月
                day: dateStr[2], // 日
                hour: timeStr[0], // 时
                minutes: timeStr[1], // 分
                seconds: timeStr[2] // 秒
            }
        }

        if (calendar == 'date') {
            // 只要--年月日
            year = date.getFullYear();
            month = date.getMonth() + 1;
            day = date.getDate();
        } else if (calendar == 'time') {// 只要--时分秒
            hour = date.getHours();
            minutes = date.getMinutes();
            seconds = date.getSeconds();
        } else {
            year = date.getFullYear();
            month = date.getMonth() + 1;
            day = date.getDate();
            hour = date.getHours();
            minutes = date.getMinutes();
            seconds = date.getSeconds();
        }
        return { year, month, day, hour, minutes, seconds }
    },

    /***
     * 根据指定格式返回【日期字符串】
     * @param {Date|String} date 获取的Date对象或时间字符串
     * @param {String} format 设置年月日返回格式(默认为 '-')
     * @param {String} calendar 选择返回年月日或时分秒或两种都有 
     * @param {String} isZhCn 中文格式;true中文
     * @return {String|Object} 返回指定格式的【日期字符串】
     * 扩展:
     *      返回以"-"方式的年月日: 
     *          new Date().toISOString().slice(0, 10) 
     */
    getDate(date, format, calendar, isZhCn) {
        calendar = calendar || 'all';
        isZhCn = isZhCn || false;
        format = format || '-';

        // 如果传入的 date 参数是 Date 对象 ------------------------
        let year = '', month = '', day = '', hour = '', minutes = '',
            seconds = '';
        // 不足两位数,补0
        let fillZero = (n) => {
            let str = n < 10 ? '0' + n : n;
            return str;
        }
        if (calendar == 'date') {
            // 只要--年月日
            year = date.getFullYear();
            month = fillZero(date.getMonth() + 1);
            day = fillZero(date.getDate());
            if (isZhCn) {
                return `${year + '年' + month + '月' + day + '日'}`
            }
            return year + format + month + format + day;
        } else if (calendar == 'time') {
            // 只要--时分秒
            hour = fillZero(date.getHours());
            minutes = fillZero(date.getMinutes());
            seconds = fillZero(date.getSeconds());
            return `${hour}:${minutes}:${seconds}`
        } else {
            // 全部--年月日 时分秒
            year = date.getFullYear();
            month = fillZero(date.getMonth() + 1);
            day = fillZero(date.getDate());
            hour = fillZero(date.getHours());
            minutes = fillZero(date.getMinutes());
            seconds = fillZero(date.getSeconds());
            if (isZhCn) {
                return `${year + '年' + month + '月' + day + '日'} ${hour}:${minutes}:${seconds}`
            }
            return `${year + format + month + format + day} ${hour}:${minutes}:${seconds}`
        }
    },

    /***
     * 给一周的数组排序(如:["星期一","星期二","星期三","星期四","星期五","星期六","星期日"])
     * @param {Array} weeks 传入的周数组
     * @return {Array} 返回排好序的数组(升序)
     */
    sortWeeks(weeks) {
        var _weeks = [];//创建临时排序的数组
        for (var i = 0; i < weeks.length; i++) {
            if (weeks[i] == "星期一") {
                var _week = {};
                _week["id"] = 1;
                _week["name"] = "星期一";
                _weeks.push(_week);
            }
            if (weeks[i] == "星期二") {
                var _week = {};
                _week["id"] = 2;
                _week["name"] = "星期二";
                _weeks.push(_week);
            }
            if (weeks[i] == "星期三") {
                var _week = {};
                _week["id"] = 3;
                _week["name"] = "星期三";
                _weeks.push(_week);
            }
            if (weeks[i] == "星期四") {
                var _week = {};
                _week["id"] = 4;
                _week["name"] = "星期四";
                _weeks.push(_week);
            }
            if (weeks[i] == "星期五") {
                var _week = {};
                _week["id"] = 5;
                _week["name"] = "星期五";
                _weeks.push(_week);
            }
            if (weeks[i] == "星期六") {
                var _week = {};
                _week["id"] = 6;
                _week["name"] = "星期六";
                _weeks.push(_week);
            }
            if (weeks[i] == "星期日") {
                var _week = {};
                _week["id"] = 7;
                _week["name"] = "星期日";
                _weeks.push(_week);
            }
        }
        _weeks.sort(function (a, b) { return a.id - b.id; });
        //将weeks清空并将排序好的值赋给weeks
        weeks = [];
        for (var i = 0; i < _weeks.length; i++) {
            weeks.push(_weeks[i].name);
        }
        return weeks;
    },

    /***
     * 根据指定的属性,将对象进行分组
     * @param {Array} arr 传入的数组
     * @param {String} key 要根据对象中的哪个属性进行分组
     * @returns {Array} 返回数组
     */
    grouping: function (arr, key) {
        // 方法1:根据对象属性的唯一性
        let groups = {}
        arr.forEach(item => {
            let group = item[key]; // 获取属性的值
            groups[group] = groups[group] || [];
            groups[group].push(item);
        })
        return Object.keys(groups).map(item => {
            return groups[item]
        })


        // 方法2:
        // var _arr = [],
        //     _t = [],
        //     // 临时的变量
        //     _tmp;
        // // 按照特定的参数将数组排序将具有相同值得排在一起
        // arr = arr.sort(function (a, b) {
        //     var s = a[key],
        //         t = b[key];
        //     return s < t ? -1 : 1;
        // });
        // if (arr.length) {
        //     _tmp = arr[0][key]; // 数组第一个对象的属性值
        // }
        // // 将相同类别的对象添加到统一个数组
        // for (var i in arr) {
        //     if (arr[i][key] === _tmp) {
        //         _t.push(arr[i]);

        //     } else {
        //         _tmp = arr[i][key];
        //         _arr.push(_t);
        //         _t = [arr[i]];
        //     }
        // }
        // // 将最后的内容推出新数组
        // _arr.push(_t);
        // return _arr;
    },

    /**
     * 获取元素在数组中的下标, 如果元素为对象,则根据对象 唯一字段 获取
     * @param {Array} arr 传入的数组
     * @param {Number|String|Object} obj 传入的数字或对象
     * @param {String} key 传入的元素对象字段名; 传入的字段名要数组中的所有元素都拥有
     * @returns 如果没有找到,则返回 -1; 
                如果在数组中没有找到元素对象的字段名,则返回 undefined
        注意:数组中的所有元素对象的格式要一致, 如:
            [ {id:1, name:Tom}, {id:2, name: Jack} ]
     */
    getArrIndex: function (arr, obj, key) {
        var len = arr.length;
        if (obj instanceof Object) {
            while (len--) {
                if (arr[len][key]) {
                    if (arr[len][key] == obj[key]) {
                        return len;
                    }
                } else {
                    return undefined;
                }
            }
        } else {
            while (len--) {
                if (arr[len] == obj) {
                    return len;
                }
            }
        }
        return -1;
    },

    /**
    * 字符串首字母大写
    * @param {String} str 
    * @returns 
    */
    firstCapitalLetters: function (str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    },

    /**
     * 去除空格
     * @param {*} str 字符串
     * @param {*} s  左 | 右 | 左右 | 所有 空格
     * @returns 
     */
    rmSpace: function (str, s) {
        s = s || 'all';
        if (s == 'all') {
            return str.replace(/\s+/g, '');
        } else if (s == 'left') {
            return str.replace(/^\s*/, '');
        } else if (s == 'right') {
            return str.replace(/(\s*$)/g, '');
        } else if (s == 'about') {
            return str.replace(/^\s+|\s+$/g, '');
        }
    },

    /**
    * 将字符串转换成二进制形式,中间用空格隔开
    * @param {String|Array} obj 字符串或数组 
    * @return {String|Array} 
    */
    strToBinary: function (obj) {
        var result = [];
        var list;
        if (Object.prototype.toString.call(obj) == '[object String]') {
            list = obj.replace(/\s+/g, '').split(''); // 去除空格,并分割为字符串数组
        } else {
            list = obj;
        }
        for (var i = 0; i < list.length; i++) {
            if (i != 0) {
                result.push(' ');
            }
            var item = list[i];
            var binaryStr = item.charCodeAt().toString(2);
            result.push(binaryStr);
        }
        return result.join('');
    },

    /**
    * 将二进制字符串转换成 Unicode 字符串
    * @param {String|Array} obj 字符串或数组
    * @return {String|Array} 
    */
    binaryToStr: function (obj) {
        var result = [];
        var list = [];
        if (Object.prototype.toString.call(obj) == '[object String]') {
            list = obj.split(' ');
        } else {
            list = obj;
        }
        for (var i = 0; i < list.length; i++) {
            var item = list[i];
            var asciiCode = parseInt(item, 2);
            var charValue = String.fromCharCode(asciiCode);
            result.push(charValue);
        }
        return result.join("");
    },

    /**
    * 导出 excel 表格
    * @param {String} FileName 文件名称
    * @param {String} TableHtml 表格
    * @param {String} css 样式
    */
    ExcelExportLdc: function (FileName, TableHtml, css) {
        var templet = `
            <html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">
                <head>
                    <!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>
                    <x:Name>${'worksheet1'}</x:Name>
                    <x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet>
                    </x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]-->

                    <style type="text/css">

                        ${css || ''}
                    
                    </style>
                </head>

                <body>${TableHtml}</body>

            </html>
        `
        // console.log(templet)

        // base64
        var base64 = function (s) {
            return window.btoa(unescape(encodeURIComponent(s)))
        }
        // 点击下载
        var a = document.createElement('a');
        a.href = 'data:application/vnd.ms-excel;base64,' + base64(templet)
        a.download = FileName + ".xls";
        a.click();
    },

    /**
    * base64 加密
    * @param {} s 
    * 解密:window.atob()
    */
    base64: function (s) {
        return window.btoa(unescape(encodeURIComponent(s)))
    },

    /**
    * 获取用户当前 ip 地址或当前地址(省,市) --- jq写法
    * @param {Boolean} boo : 
    *      false 获取用户当前 ip 地址;true 获取用户当前 ip 地址和当前地址(省,市)
    * @param {Function} callback 回调函数
    * @returns 
    * 注意:使用该方法前,先引入 jquery.js
    * 也可以直接在页面添加:
    *      <script src="https://pv.sohu.com/cityjson?ie=utf-8"></script>
    *      <script src="https://ip.ws.126.net/ipquery"></script>
    *    这样可以直接使用 returnCitySN, localAddress 变量
    */
    getIpProvinceCity_JQ: function (boo, callback) {
        var obj;
        if (boo) {
            $.getScript('https://pv.sohu.com/cityjson?ie=utf-8', function () {
                $.getScript('https://ip.ws.126.net/ipquery', function () {
                    obj = {
                        returnCitySN: returnCitySN,
                        localAddress: localAddress
                    }
                    callback(obj)
                })
            })
        } else {
            $.getScript('https://pv.sohu.com/cityjson?ie=utf-8', function () {
                obj = returnCitySN;
                callback(obj)
            })
        }
    },

    /**
    * 获取浏览器类型
    * @param {}  
    * @returns {String} browser 浏览器类型
    */
    getBrowser: function () {
        var userAgent = window.navigator.userAgent; // 获取浏览器
        var browser;
        // Firefox
        if (userAgent.indexOf('Firefox') !== -1) {
            browser = 'Firefox'
        }
        // Edge
        if (userAgent.indexOf('Edge') !== -1) {
            browser = 'Edge'
        }
        // IE浏览器
        if (userAgent.match(/msie ([\d.]+)/)) {
            browser = 'IE'
        }
        // Chrome浏览器
        if (userAgent.match(/Chrome\/([\d.]+)/)) {
            browser = 'Chrome'
        }
        // safari
        if (userAgent.match(/Version\/([\d.]+).*Safari/)) {
            browser = 'Safari'
        }
        // opera
        if (userAgent.match(/Opera.([\d.]+)/)) {
            browser = 'Opera'
        }
        return browser;
    },

    //用 js 原生写法: ajax
    ajax: function (url, callback) {
        var xhr;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();	//IE7+,Firefox,Chrome,Safari,Opera
        } else {
            xhr = new ActiveXObject("Microsoft.XMLHTTP");	//IE5,6
        }
        //发送异步请求
        xhr.open("GET", url, true);
        //发送请求
        xhr.send();
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && xhr.status == 200) {
                console.log(xhr)
                //获取服务器响应
                callback(xhr.responseText)
            }
        };
    },

    /**
    * 根据指定个数分割数组(伪数组不行)
    * @param {Array} array     数组
    * @param {Number} subNum   以 subNum 个元素为一个数组拆分
    * @returns {Array}
    */
    group: function (array, subNum) {
        let index = 0;
        let newArray = [];
        while (index < array.length) {
            newArray.push(array.slice(index, index += subNum));
        }
        return newArray;
    },

    /**
    * 根据指定个数分割数组(伪数组也可以传进)
    * @param {Array} arr 数组  
    * @param {Number} size 以 size 个为一个数组进行分割
    * @return {Array} 返回一个新数组,新数组里面是以size个为一个的数组
    */
    chunkArr: function (arr, size) {
        var myArr;
        //判断如果没有 length,或者size没有传值,size小于1,就返回空数组
        if (!arr.length || !size || size < 1) return [];
        if (!Array.isArray(arr)) {
            myArr = Array.from(arr);
        } else {
            myArr = arr;
        }
        let start = null, end = null, result = [];
        for (let i = 0; i < Math.ceil(myArr.length / size); i++) {
            start = i * size;
            end = start + size;
            result.push(myArr.slice(start, end))
        }
        return result;
    },

    /**
    * 获取 url 地址栏 ? 后面的参数
    * @param {}  
    * @param {} 
    * @returns {Object}
    */
    getUrl: function () {
        var url = window.location.search; // 获取从 ? 后面开始的 url
        var myObj = {};
        if (url.indexOf('?') != -1) {
            var arr = url.substr(1).split('&')
            arr.forEach(item => {
                var temp = item.split('=')
                myObj[temp[0]] = decodeURI(temp[1])
            })
        }
        return myObj
    },

    /**
    * 获取地址栏 ? 后面指定的参数的值
    * @param {String} strName 指定参数
    * @returns 
    */
    getUrlSpecify: function (strName) {
        var strHref = document.location.toString();
        var intPos = strHref.indexOf("?");
        var strRight = strHref.substr(intPos + 1); // 获取到右边的参数部分
        var arrTmp = strRight.split("&"); // 以&分割成数组

        for (var i = 0; i < arrTmp.length; i++) { // 循环数组    
            var dIntPos = arrTmp[i].indexOf("=");
            var paraName = arrTmp[i].substr(0, dIntPos);
            var paraData = arrTmp[i].substr(dIntPos + 1);
            if (paraName == strName) {
                return decodeURI(paraData);
            }
        }
        return "";
    },

    /**
    * 对象混合
    * @param {Object} obj1
    * @param {Object} obj2
    * 返回一个新对象
    */
    mixin: function (obj1, obj2) {
        var newObj = {};
        // 复制obj2的属性
        for (var prop in obj2) {
            newObj[prop] = obj2[prop];
        }
        // 找到obj1中有而obj2中没有的属性
        for (var prop in obj1) {
            if (!(prop in obj2)) {
                newObj[prop] = obj1[prop];
            }
        }
        return newObj;
    },

    /**
    * 对象克隆(复制)
    * @param {Object} target 克隆对象
    * @param {Boolean} deep 是否深度克隆
    * @return {Array|Object} 返回一个新数组或新对象
    */
    clone: function (target, deep) {
        if (Array.isArray(target)) {
            if (deep) {
                // 深度克隆
                let newArr = [];
                for (let i = 0; i < target.length; i++) {
                    if (Object.prototype.hasOwnProperty.call(target, i)) {
                        newArr.push(arguments.callee(target[i], deep));
                    }
                }
                return newArr;
            }
            return target.slice();

        } else if (Object.prototype.toString.call(target) === "[object Object]") {
            let newObj = {};
            for (let prop in target) {
                // 是否是自身的属性
                if (Object.prototype.hasOwnProperty.call(target, prop)) {
                    if (deep) {
                        // 深度克隆
                        newObj[prop] = arguments.callee(target[prop], deep);
                    } else {
                        newObj[prop] = target[prop];
                    }
                }
            }
            return newObj;

        } else {
            // 原始类型
            return target; // 递归函数的终止条件
        }
    },

    /**
     * 函数防抖
     * 	通过setTimeout 的方式,在一定的时间间隔内,将多次触发变成一次触发 
     * @param {Object} target 目标对象
     * @param {Function} fn 回调函数
     * @param {Number} delay 时间
     * @param {Boolean} isOne true(默认) 只执行1次;false 执行第一次和最后一次
     * 补充:isOne为false时,只点击一次的时候立即执行。当点击多次时第一次和最后一次执行。
     */
    debounce: function (target, fn, delay = 500, isOne = true) {
        delay = delay || 500;
        if (typeof target == 'function') {
            fn = target;
            target = this;
        }
        if (isOne) { // ---------- 只执行一次 ----------
            // 如果定时器存在清空定时器
            if (target.timeOutId) cleartimeOutIdout(target.timeOutId);
            target.timeOutId = setTimeout(() => {
                fn(); // arguments 是获取 return 这个函数的参数
            }, delay);

        } else { // ---------- 执行第一次和最后一次 ----------
            //定义一个firstClick,判断是否第一次执行,初始值为true
            var firstClick = !target.timeOutId;
            //第一次会立即执行
            if (firstClick) {
                fn();
            }
            // 如果定时器存在清空定时器
            if (target.timeOutId) {
                clearTimeout(target.timeOutId);
            }
            // 设置定时器,此时firstClick会变为false,规定时间后timeOutId才会为null
            target.timeOutId = setTimeout(() => {
                target.timeOutId = null;
                // 如果firstClick为true,执行;点击多次时,最后一次执行。
                if (!firstClick) {
                    fn();
                }
            }, delay);
        }
    },

    /**
    * 函数节流:
    * 	减少一段时间的触发频率(间隔时间执行)
    * @param {Function} callback 回调函数
    * @param {Number} time 规定时间
    * @param {Boolean} immediately 值为true, 则使用时间戳的写法; 值为false, 则使用定时器的写法。
    */
    throttle: function (callback, time, immediately) {
        if (immediately === undefined) immediately = true; // immediately没有传值时,默认为 true

        if (immediately) {
            // 写法1:第一次立即触发,下一次后等规定的时间触发
            var t;
            return function () {
                if (!t || Date.now() - t >= time) { // 之前没有计时 或 距离上次执行的时间已超过规定的时间
                    callback.apply(null, arguments);
                    t = Date.now(); // 得到的时间戳
                }
            }
        } else {
            // 写法2:第一次等规定的时间触发
            var timer;
            return function () {
                if (timer) {
                    return;
                }
                var args = arguments; // 利用闭包,保持参数数组
                timer = setTimeout(function () {
                    callback.apply(null, args);
                    timer = null;
                }, time);
            }
        }
    },

    /**
    * 判断数据类型
    * @param {Object} target 目标对象。没有传参时, 返回 Undefined
    * 注意:该方法最后返回如:
    *          String, Number, Boolean, Undefined, Null, Symbol
    *          Array, Object, Function, RegExp, 等等...
    * 
    * 如果不用该方法,可以直接使用 Object.prototype.toString.call(参数), 返回值是一个字符串; 
    * 但是,与其返回值对比的格式是:
    *      "[object, Number]"   "[object, String]"  "[object, Boolean]"
    *      "[object, Undefined]"   "[object, Symbol]"   "[object, Null]"
    *      "[object, Array]"   "[object, Object]"   "[object, Function]"
    *      等等...
    */
    dataType: function (target) {
        var str = Object.prototype.toString.call(target);
        var pattern = /\s[a-z]+/gi;
        return str.match(pattern)[0];
    },

    /**
    * 圣杯模式继承
    * 注意:顺序很重要!!! 一定是调用该方法之后,才 new 出一个对象
    * @param {Object} Target 目标对象
    * @param {Object} Origin 源对象
    */
    inherit: (function () {
        var F = function () { };
        return function (Target, Origin) {
            F.prototype = Origin.prototype;
            Target.prototype = new F();
            Target.prototype.constructor = Target;
            // 超类:找到真正继承的
            Target.prototype.uber = Origin.prototype;
        }
    }()),

    /**
    * 数组排序
    * @param {Array} arr 数组
    * @param {Boolean} boo false: 升序(默认); true: 降序
    * 升序:a - b
    * 降序:b - a
    */
    mySort: function (arr, boo) {
        boo = boo || false;
        if (boo) {
            arr.sort(function (a, b) {
                return b - a; // 降序
            });
        } else {
            arr.sort(function (a, b) {
                return a - b; // 升序
            });
        }
    },

    /**
    * 数组去重
    * @param {Array} arr 数组
    * 不用该方法,也可以用双重for循环 || ES6的 Set 构造函数
    * @return {Array} 返回新数组
    */
    unique: function (arr) {
        // var temp = {}, newArr = [], length = arr.length;
        // for(var i = 0; i < length; i++){
        //     if(!temp[arr[i]]){
        //         temp[arr[i]] = '???'; // 占位
        //         newArr.push(arr[i]);
        //     }
        // }
        // return newArr;

        // 方式2:
        // let newArr = []
        // for (let i = 0; i < arr.length; i++) {
        //     if (!newArr.includes(arr[i])) {
        //         newArr.push(arr[i])
        //     }
        // }
        // return newArr;

        // 方式3:
        // let newArr = []
        // for(let i = 0; i < arr.length; i++) {
        //     if(newArr.indexOf(arr[i]) < 0) {
        //         newArr.push(arr[i])
        //     } 
        // }
        // return newArr;

        // 方式4:
        let res = {};
        arr.forEach(item => {
            if (!res[item]) res[item] = item;
        })
        return Object.values(res);
    },

    /**
    * 数组里的对象去重并合并
    * @param {Array} arr 数组
    * @param {String} prop 对象属性 
    * @returns 
    */
    uniqueMerge: function (arr, prop) {
        let newArr = [];
        arr.forEach(function (item) {
            let hasPush = false;
            newArr.forEach((item2, index, thisArr) => {
                if (item[prop] == item2[prop]) {
                    hasPush = true;
                    thisArr[index] = { ...item, ...item2 };
                    return;
                }
            });
            if (!hasPush) {
                newArr.push(item);
            }
        });
        return newArr;
    },

    /**
    * json 数据转 xml 或 xml 数据转 json
    * @param {*} data 传入的 xml 或 json 数据;传入 json 数据时,最好以对象的形式传入
    * 拓展 --> 匹配xml格式:^<[^/]\S+?>.*$
    * 【【 注意:调用该方法,需要先引入 xml2json.js,否则创建 X2JS 对象失败 】】
    * 参考网址:https://www.hangge.com/blog/cache/detail_1798.html
    */
    conversionFormatX2JS: function (data) {
        var x2js = new X2JS();
        var newData;
        // 判断数据类型
        var dataType = function (target) {
            return Object.prototype.toString.call(target);
        }
        if (dataType(data) == '[object String]') { // xml 转 json
            newData = x2js.xml_str2json(data);
            if (!newData) throw new Error('请确认传入数据是否是"xml"格式');
        } else if (dataType(data) == '[object Object]') {
            newData = x2js.json2xml_str(data);
        } else if (dataType(data) == '[object Array]') {
            newData = x2js.json2xml_str({
                array: data
            });
        } else {
            newData = data;
        }
        return newData;
    },

    /**
    * 判断是否是微信浏览器
    * @return {Boolean}
    */
    isWeiXin: function () {
        var ua = window.navigator.userAgent.toLowerCase();
        if (ua.match(/MicroMessenger/i) == 'micromessenger') {
            return true; // 微信端
        } else {
            return false;
        }
    },

    /**
    * 创建DOM对象, 并且添加属性,返回新创建的元素
    * @param {String} nodeName 创建的新元素名(必填)
    * @param {Element} parentNode 被插入的父元素; 默认插入到 body
    * @param {String} param 创建的新元素的属性对象
    * @return {Element} el 返回新创建的元素
    * 注意:自定义属性要带有 data- ,会被添加到行内中,即添加到元素属性 dataset 对象中;
    *      否则,直接给元素添加属性
    */
    createElement: function (nodeName, parentNode, param) {
        var el = document.createElement(nodeName);
        for (var prop in param) {
            if (Object.prototype.toString.call(param[prop]) == '[object Function]') {
                el.addEventListener(prop, param[prop]);
            } else if (prop == 'class') {
                el.classList.add(param[prop]);
            } else if (prop.indexOf('data-') > -1) {
                el.setAttribute(prop, param[prop]);
            } else {
                el[prop] = param[prop];
            }
        }
        if (parentNode) {
            parentNode.appendChild(el);
        } else {
            document.body.appendChild(el);
        }
        return el;
    },

    /**
    * 获取获取在元素中的位置
    * @param {String} id 元素id
    */
    getMousePositionInElement(id) {
        const element = document.getElementById(id);
        element.addEventListener('mousemove', (event) => {
            const rect = element.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;
            console.log(`Mouse position in element: (${x}, ${y})`);
        });
    },

    /**
     * 获取元素在浏览器中的位置(让元素相对于浏览器定位才能获取)
     * @param {Object} el DOM 元素
     * @returns 
     * 注意:offsetLeft/Top 元素左上角相对父元素左上角的距离
            所以,要获取到元素在浏览器中的位置,请保持元素的祖先元素不是定位元素
       扩展:
            获取鼠标在元素中的坐标:
                x = 鼠标坐标e.pageX - 元素在浏览器中的坐标offsetLeft
                y = 鼠标坐标e.pageY - 元素在浏览器中的坐标offsetTop
    */
    getElePlace: function (el) {
        return {
            offsetLeft: el.offsetLeft,
            offsetTop: el.offsetTop
        }
    },

    /**
    * 获取滚动条滚动的距离
    */
    getScrollOffset: function () {
        if (window.pageXOffset) {
            return {
                x: window.pageXOffset,
                y: window.pageYOffset
            }
        } else {
            return {
                x: document.body.scrollLeft || document.documentElement.scrollLeft,
                y: document.body.scrollTop || document.documentElement.scrollTop
            }
        }
    },

    /**
    * 获取浏览器可视区域大小
    */
    getViewportOffset: function () {
        if (window.innerWidth) {
            return {
                w: window.innerWidth, // 包括滚动条width
                h: window.innerHeight
            }
        } else {
            if (document.compatMode == 'BackCompat') { // 怪异模式
                return {
                    w: document.body.clientWidth, // 不包括滚动条width
                    h: document.body.clientHeight
                }
            } else { // 标准模式
                return {
                    w: document.documentElement.clientWidth, // 不包括滚动条width
                    h: document.documentElement.clientHeight
                }
            }
        }
    },

    /**
    * 获取当前元素所有最终使用的CSS属性值
    * @param {Element} el 当前元素对象(必填) 
    * @param {String} pseudoEl 当前元素的伪元素
    * @param {String} prop 当前元素或当前元素的伪元素的属性名
    * 注意:因为保留字的原因,传入的 float 属性名,可以用 cssFloat 代替; IE 8 支持的是 styleFloat
    *      IE 的 currentStyle属性不支持伪类样式的获取
    */
    getStyle: function (el, pseudoEl, prop) {
        pseudoEl = pseudoEl || null;
        if (window.getComputedStyle) {
            if (prop) {
                return window.getComputedStyle(el, pseudoEl)[prop];
            } else {
                return window.getComputedStyle(el, pseudoEl);
            }
        } else {
            if (prop) {
                return el.currentStyle[prop];
            } else {
                return el.currentStyle;
            }
        }
    },

    /* 未完善 =========================================================================================== */

    /**
    * 禁用复制粘贴:默认整个页面禁用复制粘贴剪切
    * @param {Element} el 'Dom 对象'
    * @param {Object} param 一个对象
    *      selectstart:禁用选择,防止复制;
    *      paste:禁用粘贴
    *      copy:禁用复制
    *      cut:禁用剪切,防止复制
    * @param {Function} callback 回调函数:禁用剪切,禁用复制
    * 
    */
    disableCopyPaste: function (el, param, callback) {
        param = Object.assign({}, {
            selectstart: false,
            paste: false,
            copy: false,
            cut: false
        }, param);
        callback = callback || function () { };
        // 禁用选择,防止复制
        var selectstart = function () {
            return param.selectstart;
        }
        // 禁用粘贴
        var paste = function () {
            return param.paste;
        }
        // 禁用复制
        var copy = function () {
            callback();
            return param.copy;
        }
        // 禁用剪切,防止复制
        var cut = function () {
            callback();
            return param.cut;
        }
        if (!el || el instanceof HTMLBodyElement) {
            console.log('body')
            var body = document.body || document.documentElement;
            body.onselectstart = selectstart;
            body.onpaste = paste;
            body.oncopy = copy;
            body.oncut = cut;
        } else if (el instanceof Element) {
            console.log('element')
            el.onselectstart = selectstart;
            el.onpaste = paste;
            el.oncopy = copy;
            el.oncut = cut;
        } else {
            throw new Error('传入的实参格式是否正确?');
        }
    },

    /**
    * 判断是安卓端还是ios
    * @param {} 
    * @return {String}
    */
    getClientType() {
        var userAgent = navigator.userAgent;
        var isAndroid = userAgent.indexOf('Android') > -1 || userAgent.indexOf('Adr') > -1; //android终端
        var isiOS = !!userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
        if (isAndroid) {
            return "android";
        } else if (isiOS) {
            return "ios";
        }
        return '';
    },

    /**
    * 判断浏览器类型, 并处理逻辑
    * @param {Function} callback 回调函数
    * ### 未完善 ###
    */
    judgeBrowserType: function (callback) {
        var Sys = {};
        var ua = window.navigator.userAgent.toLowerCase();
        var s;
        (s = ua.match(/msie ([\d.]+)/)) ? Sys.ie = s[1] :
            (s = ua.match(/firefox\/([\d.]+)/)) ? Sys.firefox = s[1] :
                (s = ua.match(/chrome\/([\d.]+)/)) ? Sys.chrome = s[1] :
                    (s = ua.match(/opera.([\d.]+)/)) ? Sys.opera = s[1] :
                        (s = ua.match(/version\/([\d.]+).*safari/)) ? Sys.safari = s[1] :
                            (s = ua.match(/MicroMessenger/i)) ? Sys.WeiXin = s[1] : 0;

        if (Sys.ie) {
            /* 处理 ie 的逻辑 */
            callback();
            console.log('IE: ' + Sys.ie);

        } else if (Sys.firefox) {
            /* 处理 firefox 的逻辑 */
            callback();
            console.log('firefox: ' + Sys.firefox);

        } else if (Sys.chrome) {
            /* 处理 chrome 的逻辑 */
            callback();
            console.log('chrome: ' + Sys.chrome);

        } else if (Sys.opera) {
            /* 处理 opera 的逻辑 */
            callback();
            console.log('opera: ' + Sys.opera);

        } else if (Sys.safari) {
            /* 处理 safari 的逻辑 */
            callback();
            console.log('safari: ' + Sys.safari);

        } else if (Sys.WeiXin) {
            /* 处理 WeiXin 内置浏览器的逻辑 */
            callback();
        }
    },
}

/* 补充 =========================================================================================== */





/**
 * 把一个元素插入到另一个元素后面(无兼容写法)
 * @param {Element} targetNode 要插入的元素
 * @param {Element} afterNode 指定元素的后面
 * 调用:父元素.insertAfter(targetNode, afterNode)
 */
Element.prototype.insertAfter = function (targetNode, afterNode) {
    var beforeNode;
    // 获取下一个兄弟元素节点
    if (afterNode[0]) {
        beforeNode = afterNode[0].nextElementSibling
    } else {
        beforeNode = afterNode.nextElementSibling;
    }

    if (beforeNode == null || beforeNode == undefined) {
        this.appendChild(targetNode);
    } else {
        this.insertBefore(targetNode, beforeNode);
    }
}

/**
* 替换敏感词
* @param {Array} tag
* @param {*} data 数据
*/
// doreplace: function(tag, data) {
//     var target = document.getElementsByTagName(tag);
//     for (var j = 0; j < target.length; j++) {
//         for (var n = 0; n < data.length; n++) {
//             if (target[j].innerHTML.indexOf(data[n]['敏感词']) > -1) {
//                 var str = target[j].innerHTML.replace(eval('/(' + data[n]['敏感词'] + ')/g'), data[n]['替换']);
//                 target[j].innerHTML = str;
//             }
//         }
//     }
// },










常用函数整理未完,敬请期待…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值