Source: jPublic.js

/**
 * @file js工具库
 * @copyright jPublic.js 2020
 * @author https://github.com/smltq/jPublic.git
 */
(function () {
    // 基线开始
    //----------------------
    /**
     * 获得root,兼容web,微信,note等
     */
    var root = (typeof self == 'object' && self.self === self && self) ||
        (typeof global == 'object' && global.global === global && global) || this || {};

    var ArrayProto = Array.prototype, ObjProto = Object.prototype;

    /**
     * 为快速访问核心原型创建快速引用变量
     */
    var push = ArrayProto.push,
        toString = ObjProto.toString,
        hasOwnProperty = ObjProto.hasOwnProperty;

    /**
     * 定义将要实现的 ECMAScript 5 原生方法
     */
    var nativeKeys = Object.keys;

    var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
    var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

    /**
     * 创建全局对象:_
     * @global
     * @module _
     */
    var _ = function (obj) {
        if (obj instanceof _) return obj;
        if (!(this instanceof _)) return new _(obj);
        this._wrapped = obj;
    };

    /**
     * 当前版本号
     * @type {string}
     * @default
     * @readOnly
     * @alias module:_.VERSION
     */
    _.VERSION = '1.8.3';

    if (typeof exports != 'undefined' && !exports.nodeType) {
        if (typeof module != 'undefined' && !module.nodeType && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
    } else {
        root._ = _;
    }
    // 基线结束

    //私有成员开始
    //----------------------
    var optimizeCb = function (func, context, argCount) {
        if (context === void 0) return func;
        switch (argCount == null ? 3 : argCount) {
            case 1:
                return function (value) {
                    return func.call(context, value);
                };
            case 3:
                return function (value, index, collection) {
                    return func.call(context, value, index, collection);
                };
            case 4:
                return function (accumulator, value, index, collection) {
                    return func.call(context, accumulator, value, index, collection);
                };
        }
        return function () {
            return func.apply(context, arguments);
        };
    };

    var shallowProperty = function (key) {
        return function (obj) {
            return obj == null ? void 0 : obj[key];
        };
    };

    var has = function (obj, path) {
        return obj != null && hasOwnProperty.call(obj, path);
    }

    var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
    var getLength = shallowProperty('length');
    var isArrayLike = function (collection) {
        var length = getLength(collection);
        return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
    };

    var builtinIteratee;
    var cb = function (value, context, argCount) {
        if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
        if (value == null) return _.identity;
        if (_.isFunction(value)) return optimizeCb(value, context, argCount);
        if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
        return _.property(value);
    };

    var createAssigner = function (keysFunc, defaults) {
        return function (obj) {
            var length = arguments.length;
            if (defaults) obj = Object(obj);
            if (length < 2 || obj == null) return obj;
            for (var index = 1; index < length; index++) {
                var source = arguments[index],
                    keys = keysFunc(source),
                    l = keys.length;
                for (var i = 0; i < l; i++) {
                    var key = keys[i];
                    if (!defaults || obj[key] === void 0) obj[key] = source[key];
                }
            }
            return obj;
        };
    };

    var collectNonEnumProps = function (obj, keys) {
        var nonEnumIdx = nonEnumerableProps.length;
        var constructor = obj.constructor;
        var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

        //构造函数是一种特殊情况
        var prop = 'constructor';
        if (has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

        while (nonEnumIdx--) {
            prop = nonEnumerableProps[nonEnumIdx];
            if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
                keys.push(prop);
            }
        }
    };
    //私有成员结束

    //集合函数开始
    //---------------
    /**
     * 生成可应用于集合中的每个元素的回调。<br>
     * _.iteratee支持许多常见回调用例的简写语法。<br>
     * 根据值的类型,_.iteratee 各种结果
     * @param {*}   value   迭代值
     * @param {Object}  context 上下文
     * @alias module:_.iteratee
     * @method
     * @example
     * // 空值
     * _.iteratee();
     * => _.identity()
     * // 函数
     * _.iteratee(function(n) { return n * 2; });
     * => function(n) { return n * 2; }
     * // 对象
     * _.iteratee({firstName: 'Chelsea'});
     * => _.matcher({firstName: 'Chelsea'});
     * // 其它
     * _.iteratee('firstName');
     * => _.property('firstName');
     */
    _.iteratee = builtinIteratee = function (value, context) {
        return cb(value, context, Infinity);
    };

    /**
     * 返回一个断言函数,这个函数会给你一个断言可以用来辨别给定的对象是否匹配attrs指定键/值属性。
     * @param attrs
     * @alias module:_.matcher
     * @example
     * var ready = _.matcher({selected: true, visible: true});
     * var readyToGoList = _.filter(list, ready);
     */
    _.matcher = function (attrs) {
        attrs = _.extendOwn({}, attrs);
        return function (obj) {
            return _.isMatch(obj, attrs);
        };
    };

    /**
     * 类似于 extend, 但只复制自己的属性覆盖到目标对象。(注:不包括继承过来的属性)。
     * @type {Function}
     * @method
     * @alias module:_.extendOwn
     * @example
     * var a = {
     *       foo: false
     *   };
     *
     * var b = {
     *       bar: true
     *   };
     * _.extendOwn(a,b)
     * =>{ foo: false, bar: true };
     */
    _.extendOwn = createAssigner(_.keys);

    /**
     * 判断properties中的键和值是否包含在object中。
     * @param {Object} object   查找目标
     * @param {Object} attrs    查找对象
     * @alias module:_.isMatch
     * @example
     * var stooge = {name: 'moe', age: 32};
     * _.isMatch(stooge, {age: 32});
     * => true
     */
    _.isMatch = function (object, attrs) {
        var keys = _.keys(attrs), length = keys.length;
        if (object == null) return !length;
        var obj = Object(object);
        for (var i = 0; i < length; i++) {
            var key = keys[i];
            if (attrs[key] !== obj[key] || !(key in obj)) return false;
        }
        return true;
    };

    /**
     * 遍历list中的所有元素,按顺序用遍历输出每个元素。<br>
     * 如果传递了context参数,则把iteratee绑定到context对象上。<br>
     * 每次调用iteratee都会传递三个参数:(element, index, list)。<br>
     * 如果list是个JavaScript对象,iteratee的参数是 (value, key, list))。<br>
     * 返回list以方便链式调用。<br>
     * @param {Object} obj   遍历目标
     * @param {Function} iteratee   迭代器
     * @param {Object} context  绑定的目标对象
     * @alias module:_.each
     * @example
     * _.each([1, 2, 3], alert);
     * => 依次提示每个数字...
     * _.each({one: 1, two: 2, three: 3}, alert);
     * => 依次提示每个数字...
     */
    _.each = function (obj, iteratee, context) {
        iteratee = optimizeCb(iteratee, context);
        var i, length;
        if (isArrayLike(obj)) {
            for (i = 0, length = obj.length; i < length; i++) {
                iteratee(obj[i], i, obj);
            }
        } else {
            var keys = _.keys(obj);
            for (i = 0, length = keys.length; i < length; i++) {
                iteratee(obj[keys[i]], keys[i], obj);
            }
        }
        return obj;
    };
    //集合函数结束

    //通用函数开始
    //---------------
    /**
     * 返回一个对象里所有的方法名, 而且是已经排序的 — 也就是说, 对象里每个方法(属性值是一个函数)的名称.
     * @param {Object}  obj 查找对象
     * @returns {this}
     * @alias module:_.functions
     * @example
     * _.functions(_);
     * => ["arrayDiff", "arrayEquals", "arrayIsRepeat", "clone", "debounce", "defineColor" ...
     */
    _.functions = function (obj) {
        var names = [];
        for (var key in obj) {
            if (_.isFunction(obj[key])) names.push(key);
        }
        return names.sort();
    };

    /**
     * 获得当前Url参数值
     * @param {String}  name    参数名
     * @returns {string|null}
     * @alias module:_.getUrlParam
     */
    _.getUrlParam = function (name) {
        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
        var r = window.location.search.substr(1).match(reg);
        if (r != null) {
            return decodeURIComponent(r[2]);
        }
        return null;
    };

    /**
     * 函数去抖,空闲时间大于或等于wait,执行fn
     * @param {Function}    fn          要调用的函数
     * @param {Integer}     wait        延迟时间(单位毫秒)
     * @param {Boolean}     immediate   给immediate参数传递false绑定的函数先执行,而不是wait之后执行
     * @returns 实际调用函数
     * @alias module:_.debounce
     * @example
     * var lazyLayout = _.debounce(calculateLayout, 300);
     * $(window).resize(lazyLayout);
     */
    _.debounce = function (fn, wait, immediate) {
        var timeout;
        return function () {
            var context = this, args = arguments;
            var later = function () {
                timeout = null;
                if (!immediate) fn.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) fn.apply(context, args);
        }
    };

    /**
     * 函数节流 每wait时间间隔,执行fn
     * @param {Function}    fn      要调用的函数
     * @param {Integer}     wait    延迟时间,单位毫秒
     * @param {Object}      scope   scope代替fn里this的对象
     * @returns {Function} 实际调用函数
     * @alias module:_.throttle
     * @example
     * var throttled = _.throttle(updatePosition, 100);
     * $(window).scroll(throttled);
     */
    _.throttle = function (fn, wait, scope) {
        wait || (wait = 250);
        var last, deferTimer;
        return function () {
            var context = scope || this;
            var now = _.now(), args = arguments;
            if (last && now < last + wait) {
                clearTimeout(deferTimer);
                deferTimer = setTimeout(function () {
                    last = now;
                    fn.apply(context, args);
                }, wait);
            } else {
                last = now;
                fn.apply(context, args);
            }
        };
    };

    /**
     * 函数只执行一次
     * @param {Function}    fn      要执行的函数
     * @param {Object}      context 上下文
     * @returns {function(): *}
     * @alias module:_.runOnce
     * @example
     * var a = 0;
     * var canOnlyFireOnce = runOnce(function () {
     *      a++;
     *      console.log(a);
     * });
     * canOnlyFireOnce(); =>1
     * canOnlyFireOnce(); => nothing
     * canOnlyFireOnce(); => nothing
     */
    _.runOnce = function (fn, context) {
        var result;
        return function () {
            if (fn) {
                result = fn.apply(context || this, arguments);
                fn = null;
            }
            return result;
        };
    };

    /**
     * 轮询条件函数,根据状态执行相应回调
     * @param {Function}    fn          条件函数
     * @param {Function}    callback    成功回调
     * @param {Function}    errback     失败回调
     * @param {Integer}     timeout     超时间隔(毫秒)
     * @param {Integer}     interval    轮询间隔(毫秒)
     * @alias module:_.poll
     * @example
     * 确保元素可见
     * poll(
     *    function () {
     *       return document.getElementById('lightbox').offsetWidth > 0;
     *    },
     *    function () {
     *       // Done, success callback
     *    },
     *    function () {
     *      // Error, failure callback
     *    }
     * );
     */
    _.poll = function (fn, callback, errback, timeout, interval) {
        var endTime = Number(new Date()) + (timeout || 2000);
        interval = interval || 100;
        (function p() {
            // 如果条件满足,调用回调
            if (fn()) {
                callback();
            }
            //如果在结束时间内,继续进入循环判断
            else if (Number(new Date()) < endTime) {
                setTimeout(p, interval);
            }
            //超时,抛出异常
            else {
                errback(new Error('timed out for ' + fn + ': ' + arguments));
            }
        })();
    };

    /**
     * 返回一个min 和 max之间的随机整数。<br>
     * 如果你只传递一个参数,那么将返回0和这个参数之间的整数
     * @param min 随机数下限,没传默认为0
     * @param max 随机数上限
     * @returns {number}
     * @alias module:_.getRandom
     * @example
     * _.random(0, 100);
     * => 48
     */
    _.getRandom = function (min, max) {
        if (max == null) {
            max = min;
            min = 0;
        }
        return min + Math.floor(Math.random() * (max - min + 1));
    };

    /**
     * 获取表单数据
     * @param {Object}    frm   表单对象
     * @alias module:_.getFormJson
     * @example
     * 获得表单数据,自动拼接成json对象,提交给服务端
     * $.ajax({
     *     type: 'post',
     *     url: 'your url',
     *     data: _.getFormJson($('#formId')),
     *     success: function(data) {
     *       // your code
     *     }
     *   });
     */
    _.getFormJson = function (frm) {
        var o = {};
        var a = frm.serializeArray();
        _.each(a, function () {
            o[this.name] = this.value;
        });
        return o;
    };

    /**
     * 复制文本到剪切板(适用于Chrome、Firefox、Internet Explorer和Edge,以及Safari)
     * @alias module:_.copyToClipboard
     */
    _.copyToClipboard = function (text) {
        if (window.clipboardData && window.clipboardData.setData) {
            return clipboardData.setData("Text", text);
        } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
            var textarea = document.createElement("textarea");
            textarea.textContent = text;
            textarea.style.position = "fixed";
            document.body.appendChild(textarea);
            textarea.select();
            try {
                return document.execCommand("copy");
            } catch (ex) {
                console.warn("复制到剪贴板异常:", ex);
                return false;
            } finally {
                document.body.removeChild(textarea);
            }
        }
    };

    /**
     * 定义操作列
     * @param {Array} arr   一个数组
     * @param {Integer} value   对应行的id值
     * @returns {string} 返回html字符串
     * @alias module:_.defineOperate
     *
     * @example
     * var arr = [
     *      { text: "删除", fn: "detailDataGrid.Delete({0})" },
     *      {text: "修改", fn: "detailDataGrid.Edit({0})" }]
     * _.defineOperate(arr,3)
     * =><a style='cursor: pointer;' onclick='detailDataGrid.Delete(3)' href='javascript:;'>删除</a> | <a style='cursor: pointer;' onclick='detailDataGrid.Edit(3)' href='javascript:;'>修改</a>
     */
    _.defineOperate = function (arr, value) {
        var str = "";
        var len = arr.length, i = 0;
        while (i < len) {
            str += _.format("<a style='cursor: pointer;' onclick='{1}' href='javascript:;'>{0}</a>", arr[i].text, _.format(arr[i].fn, value));
            str += (i == len - 1 ? "" : " | ");
            i++;
        }
        return str;
    };

    /**
     * 获取当前网站根路径
     * @returns {string}
     * @alias module:_.getRootPath
     * @example
     * http://localhost:8083/tqlin
     */
    _.getRootPath = function () {
        var curPath = window.document.location.href;
        var pathName = window.document.location.pathname;
        var pos = curPath.indexOf(pathName);
        var localhostPaht = curPath.substring(0, pos);
        var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
        return (localhostPaht + projectName);
    };

    /**
     * 格式化字符串
     * @param {String} format   要格式化的字符串
     * @returns {string | void}
     * @returns {string}
     * @alias module:_.format
     * @example
     * _.format("Hello, {0}!","World")=>Hello, World
     * _.format("Hello, {0}, My {1}!","World","Love You")=>Hello, World, My Love You
     */
    _.format = function (format) {
        var args = Array.prototype.slice.call(arguments, 1);
        return format.replace(/{(\d+)}/g, function (match, number) {
            return typeof args[number] != 'undefined' ? args[number] : match;
        });
    };

    /**
     * 去左右空格
     * @param {String} str 字符串
     * @param {String} chars 要移除的字符(默认为空白字符)
     * @returns {string}
     * @alias module:_.trim
     * @example
     * _.trim(" Hello ")=>"Hello"
     * _.trim("_Hello_","_")=>"Hello"
     */
    _.trim = function (str, chars) {
        return this.ltrim(this.rtrim(str, chars), chars);
    };

    /**
     * 去左空格
     * @param {String} str 字符串
     * @param {String} chars 要移除的字符(默认为空白字符)
     * @returns {string}
     * @alias module:_.ltrim
     * @example
     * _.ltrim(" Hello ")=>"Hello "
     * _.ltrim("_Hello_","_")=>"Hello_"
     */
    _.ltrim = function (str, chars) {
        chars = chars || "\\s";
        return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
    };

    /**
     * 去右空格
     * @param str 字符串
     * @param chars 要移除的字符(默认为空白字符)
     * @returns {string}
     * @alias module:_.rtrim
     * @example
     * _.ltrim(" Hello ")=>" Hello"
     * _.ltrim("_Hello_","_")=>"_Hello"
     */
    _.rtrim = function (str, chars) {
        chars = chars || "\\s";
        return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
    };

    /**
     * 如果obj是一个函数(Function),返回true。
     * @param {Object} obj 要检查的对象
     * @returns {boolean}
     * @alias module:_.isFunction
     * @example
     * _.isFunction(alert);
     * => true
     */
    _.isFunction = function (obj) {
        return typeof obj == 'function' || false;
    };

    /**
     * 如果object是一个对象,返回true。需要注意的是JavaScript数组和函数是对象,字符串和数字不是。
     * @param {Object} obj  要检查的对象
     * @returns {boolean}
     * @alias module:_.isObject
     * @example
     * _.isObject({});
     * => true
     * _.isObject(1);
     * => false
     */
    _.isObject = function (obj) {
        var type = typeof obj;
        return type === 'function' || type === 'object' && !!obj;
    };

    /**
     * 是否为日期对象,参数支持Date和String类型
     * @param {Object} date 要检查的对象
     * @returns {boolean}
     * @alias module:_.isValidDate
     * @version 1.8.3
     * @example
     * _.isValidDate("2019-5-5a");
     * =>false
     * _.isValidDate("2019-5-5");
     * =>true
     *_.isValidDate(new Date("2019-5-5a"));
     * =>false
     */
    _.isValidDate = function (date) {
        return !!(Object.prototype.toString.call(date) === "[object Date]" && +date);
    }

    /**
     * 是否为空字符串
     * @param {String} str 要检查的字符串
     * @returns {boolean}
     * @alias module:_.isNullOrEmpty
     * @example
     * _.isNullOrEmpty("   ");
     * =>true
     *
     * .isNullOrEmpty(' ');
     * =>true
     *
     * var student = {className: "测试班", name: "我是张三", age: 18};
     * _.isNullOrEmpty(student.skill);
     * =>true
     *
     * _.isNullOrEmpty(undefined);
     * =>true
     *
     * _.isNullOrEmpty(null);
     * =>true
     *
     * _.isNullOrEmpty("");
     * =>true
     *
     * _.isNullOrEmpty('')
     * =>true
     */
    _.isNullOrEmpty = function (str) {
        if (!str || _.trim(str) === '') {
            return true;
        }
        return false;
    };

    /**
     * 如果object 不包含任何值(没有可枚举的属性),返回true。
     * 对于字符串和类数组(array-like)对象,如果length属性为 0,那么_.isEmpty检查返回true。
     * @param {Object} obj  要检查的对象
     * @returns {boolean}
     * @example
     * _.isEmpty([1, 2, 3]);
     * => false
     * _.isEmpty({});
     * => true
     */
    _.isEmpty = function (obj) {
        if (obj == null) return true;
        if (isArrayLike(obj) && (_.isArray(obj) || _.isArguments(obj))) return obj.length === 0;
        if (_.isString(obj)) return _.isNullOrEmpty(obj);
        return _.keys(obj).length === 0;
    };

    /**
     * 如果obj是一个数组,返回true。
     * @param {Object}  obj 要检查的对象
     * @returns {boolean}
     * @alias module:_.isArray
     * @example
     * (function(){ return _.isArray(arguments); })();
     * => false
     * _.isArray([1,2,3]);
     * => true
     */
    _.isArray = function (obj) {
        return toString.call(obj) === '[object Array]';
    };

    /**
     * 是否数值
     * @param {Object} value    要检查的对象
     * @returns {boolean}
     * @alias module:_.isNumeric
     * @example
     * // true
     * _.isNumeric( "-10" )
     * _.isNumeric( "0" )
     * _.isNumeric( 0xFF )
     * _.isNumeric( "0xFF" )
     * _.isNumeric( "8e5" )
     * _.isNumeric( "3.1415" )
     * _.isNumeric( +10 )
     * _.isNumeric( 0144 )

     // false
     * _.isNumeric( "-0x42" )
     * _.isNumeric( "7.2acdgs" )
     * _.isNumeric( "" )
     * _.isNumeric( {} )
     * _.isNumeric( NaN )
     * _.isNumeric( null )
     * _.isNumeric( true )
     * _.isNumeric( Infinity )
     * _.isNumeric( undefined )
     */
    _.isNumeric = function (value) {
        return !isNaN(parseFloat(value)) && isFinite(value);
    };

    /**
     * 如果object是一个字符串,返回true。
     * @param {String} value 要检查的值
     * @returns {boolean}
     * @alias module:_.isString
     * @example
     * _.isString("moe");
     * => true
     */
    _.isString = function (value) {
        return typeof value === 'string';
    };

    /**
     * 如果object是一个参数对象,返回true。
     * @param {Object} obj  要检查的对象
     * @example
     * (function(){ return _.isArguments(arguments); })(1, 2, 3);
     * => true
     * _.isArguments([1,2,3]);
     * => false
     * @returns {*}
     */
    _.isArguments = function (obj) {
        return has(obj, 'callee');
    };

    /**
     * 截取字符串
     * @param {String}  str     原始字符串
     * @param {Integer} limit   长度限制(默认限制长度100)
     * @param {String}  suffix  超过替换字符(默认用'...'替代)
     * @returns {string|*}
     * @alias module:_.truncate
     * @example
     * _.truncate('We are doing JS string exercises.')
     * =>We are doing JS string exercises.
     *
     * _.truncate('We are doing JS string exercises.',19)
     * =>We are doing JS ...
     *
     * _.truncate('We are doing JS string exercises.',15,'!!')
     * =>We are doing !!
     */
    _.truncate = function (str, limit, suffix) {
        limit = limit || 100;
        if (_.isString(str)) {
            if (typeof suffix !== 'string') {
                suffix = '...';
            }
            if (str.length > limit) {
                return str.slice(0, limit - suffix.length) + suffix;
            }
            return str;
        }
    };

    /**
     * 金额格式化
     * @param {Number}  value   原始金额数值
     * @param {Integer} digit   保留小数位置(默认2位)
     * @returns {string}
     * @alias module:_.fmoney
     * @example
     * _.fmoney(100000000)
     * =>100,000,000.00
     *
     * _.fmoney(100000000.3434343, 3)
     * =>100,000,000.343
     */
    _.fmoney = function (value, digit) {
        digit = digit > 0 && digit <= 20 ? digit : 2;
        if (typeof (value) == "undefined" || (!value && value != 0)) {
            return '';
        }
        value = parseFloat((value + "").replace(/[^\d\.-]/g, "")).toFixed(digit) + "";
        var ss = value.split(".");
        var r = ss[1];
        ss[0] = ss[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
        return ss[0] + "." + r.substring(0, digit);
    };

    /**
     * 定义文字颜色
     * @param {String}  value   原始文字
     * @param {String}  color   要定义的颜色(默认红色)
     * @returns {string}
     * @alias module:_.defineColor
     * @example
     * _.defineColor(“Hello”)
     * =><span style="color:#FF0000">Hello</span>
     */
    _.defineColor = function (value, color) {
        return '<span style="color:' + (color || "#FF0000") + '">' + value + "</span>";
    };

    /**
     * 字节格式化
     * @param {Number}  bytes       字节值
     * @param {Integer} decimals    小数位数
     * @returns {string}
     * @alias module:_.formatBytes
     * @example
     * formatBytes(1024);
     * =>1 KB
     *
     * formatBytes('1024');
     * =>1 KB
     *
     * formatBytes(1234);
     * =>1.21 KB
     *
     * formatBytes(1234, 3);
     * =>1.205 KB
     */
    _.formatBytes = function (bytes, decimals) {
        if (bytes == 0) return '0 Bytes';
        var k = 1024,
            dm = decimals <= 0 ? 0 : decimals || 2,
            sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    }

    /**
     * 如果list中的所有元素都通过predicate的真值检测就返回true。<br>
     * (注:如果存在原生的every方法,就使用原生的every。)<br>
     * predicate 通过 iteratee 进行转换,以简化速记语法。
     * @param {Array}       list        要检查的列表
     * @param {Function}    predicate   检测函数
     * @param {Object}      context     上下文
     * @returns {boolean}
     * @alias module:_.every
     * @example
     * _.every([2, 4, 5], function(num) { return num % 2 == 0; });
     * => false
     */
    _.every = function (list, predicate, context) {
        predicate = cb(predicate, context);
        var keys = !isArrayLike(list) && _.keys(list),
            length = (keys || list).length;
        for (var index = 0; index < length; index++) {
            var currentKey = keys ? keys[index] : index;
            if (!predicate(list[currentKey], currentKey, list)) return false;
        }
        return true;
    };

    /**
     * 如果list中有任何一个元素通过 predicate 的真值检测就返回true。<br>
     * 一旦找到了符合条件的元素, 就直接中断对list的遍历。<br>
     * predicate 通过 iteratee 进行转换,以简化速记语法。
     * @param {Array}       list        要检查的列表
     * @param {Function}    predicate   检测函数
     * @param {Object}      context     上下文
     * @returns {boolean}
     * @alias module:_.some
     * @example
     * _.some([null, 0, 'yes', false]);
     * => true
     */
    _.some = function (list, predicate, context) {
        predicate = cb(predicate, context);
        var keys = !isArrayLike(list) && _.keys(list),
            length = (keys || list).length;
        for (var index = 0; index < length; index++) {
            var currentKey = keys ? keys[index] : index;
            if (predicate(list[currentKey], currentKey, list)) return true;
        }
        return false;
    };

    /**
     * 一个用来创建整数灵活编号的列表的函数,便于each 和 map循环。<br>
     * 如果省略start则默认为 0;<br>
     * step 默认为 1.返回一个从start 到stop的整数的列表,用step来增加 (或减少)独占。<br>
     * 值得注意的是,如果stop值在start前面(也就是stop值小于start值),那么值域会被认为是零长度,而不是负增长。-如果你要一个负数的值域 ,请使用负数step.
     * @param {Integer} start   开始位置
     * @param {Integer} stop    结束位置
     * @param {Integer} step    步长
     * @returns {any[]}
     * @alias module:_.range
     * @example
     * _.range(10);
     * => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     * _.range(1, 11);
     * => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
     * _.range(0, 30, 5);
     * => [0, 5, 10, 15, 20, 25]
     * _.range(0, -10, -1);
     * => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
     * _.range(0);
     * => []
     */
    _.range = function (start, stop, step) {
        if (stop == null) {
            stop = start || 0;
            start = 0;
        }
        if (!step) {
            step = stop < start ? -1 : 1;
        }
        var length = Math.max(Math.ceil((stop - start) / step), 0);
        var range = Array(length);

        for (var idx = 0; idx < length; idx++, start += step) {
            range[idx] = start;
        }
        return range;
    };

    /**
     * 检索object拥有的所有可枚举属性的名称。
     * @param {Object}  obj 要检索的对象
     * @returns {Array|*}
     * @alias module:_.keys
     * @example
     * _.keys({one: 1, two: 2, three: 3});
     * => ["one", "two", "three"]
     */
    _.keys = function (obj) {
        if (!_.isObject(obj)) return [];
        if (nativeKeys) return nativeKeys(obj);
        var keys = [];
        for (var key in obj) if (has(obj, key)) keys.push(key);
        // Ahem, IE < 9.
        if (hasEnumBug) collectNonEnumProps(obj, keys);
        return keys;
    };

    /**
     * 创建 一个浅复制(浅拷贝)的克隆object。任何嵌套的对象或数组都通过引用拷贝,不会复制。
     * @param {Object}  obj 要克隆的对象
     * @returns {*|_|*}
     * @alias module:_.clone
     * @example
     * _.clone({name: 'moe'});
     * => {name: 'moe'};
     */
    _.clone = function (obj) {
        if (!_.isObject(obj)) return obj;
        return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    };

    _.templateSettings = {
        evaluate: /<%([\s\S]+?)%>/g,
        interpolate: /<%=([\s\S]+?)%>/g,
        escape: /<%-([\s\S]+?)%>/g
    };

    //通用函数结束

    //日期相关开始
    //-----------------------
    /**
     * 获取当前时间戳,兼容旧环境(毫秒)
     * @method
     * @alias module:_.now
     * @example
     * _.now()
     * =>521557891109615
     */
    _.now = Date.now || function () {
        return new Date().valueOf();
    };

    /**
     * 获取当前服务器时间(Date)
     * @returns {Date}
     * @alias module:_.serverTime
     * @example
     * _.serverTime()
     * =>Wed May 15 2019 11:33:22 GMT+0800 (中国标准时间)
     */
    _.serverTime = function () {
        var xmlHttp = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP");
        xmlHttp.open("HEAD", location.href, false);
        xmlHttp.send();
        return new Date(xmlHttp.getResponseHeader("Date"));
    };

    /**
     * 随机获取一个日期
     * @param {Date} begin 开始时间(默认1970-1-1)
     * @param {Date} end   结束时间(默认当前时间)
     * @returns {Date}
     * @version 1.8.3
     * @alias module:_.getRandomDate
     * @example
     * _.dateFormat(_.getRandomDate());
     * =>2019-05-01 12:34:54
     */
    _.getRandomDate = function (begin, end) {
        begin = begin || new Date(1970, 1, 1);
        end = end || new Date();
        return new Date(_.getRandom(begin.getTime(), end.getTime()));
    };

    /**
     * 获取月份第一天
     * @param {Date}    date    日期(默认当前日期)
     * @returns {Date}
     * @alias module:_.firstDay
     * _.dateFormat(_.firstDay());
     * =>2019-05-01 00:00:00
     */
    _.firstDay = function (date) {
        date || (date = new Date());
        return new Date(date.getFullYear(), date.getMonth(), 1);
    };

    /**
     * 获取月份最后一天
     * @param {Date}    date    日期(默认当前日期)
     * @returns {Date}
     * @alias module:_.lastDay
     * @example
     * _.dateFormat(_.lastDay());
     * =>2019-05-31 00:00:00
     */
    _.lastDay = function (date) {
        date || (date = new Date());
        return new Date(date.getFullYear(), date.getMonth() + 1, 0);
    };

    /**
     * 获得本周的开始日期
     * @param {Date}    date    日期(默认当前日期)
     * @returns {Date}
     * @alias module:_.getWeekStartDate
     * @example
     * _.dateFormat(_.getWeekStartDate());
     * =>2019-05-13 00:00:00
     */
    _.getWeekStartDate = function (date) {
        date || (date = new Date());
        return new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 1);
    };

    /**
     * 获得本周的结束日期
     * @param {Date}    date    日期(默认当前日期)
     * @returns {Date}
     * @alias module:_.getWeekEndDate
     * @example
     * _.dateFormat(_.getWeekEndDate());
     * =>2019-05-13 00:00:00
     */
    _.getWeekEndDate = function (date) {
        date || (date = new Date());
        return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 7 - date.getDay());
    };

    /**
     * 将 Date 转化为指定格式的String 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q)<br>
     * 可以用 1-2 个占位符 年(y)可以用 1-4 个占位符 毫秒(S)只能用 1 个占位符(是 1-3 位的数字)<br>
     * @param {Date}    date    日期(默认当前时间)
     * @param {String}  format  格式化字符串(默认yyyy-MM-dd hh:mm:ss)
     * @returns {*}
     * @alias module:_.dateFormat
     * @example
     * _.dateFormat(date,"yyyy-MM-dd hh:mm:ss.S") ==> 2016-05-04 08:09:04.423
     * _.dateFormat(date,"yyyy-M-d h:m:s.S") ==>2016-05-04 8:9:4.18
     */
    _.dateFormat = function (date, format) {
        date instanceof Date || (date = new Date(date));
        if (!_.isValidDate(date)) {
            throw "parameter is not a date type!";
        }

        format = format || "yyyy-MM-dd hh:mm:ss";
        var o = {
            "M+": date.getMonth() + 1,
            "d+": date.getDate(),
            "h+": date.getHours(),
            "m+": date.getMinutes(),
            "s+": date.getSeconds(),
            "q+": Math.floor((date.getMonth() + 3) / 3),
            "S": date.getMilliseconds()
        };
        if (/(y+)/.test(format)) {
            format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
        }
        for (var k in o) {
            if (new RegExp("(" + k + ")").test(format)) {
                format = format.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
            }
        }
        return format;
    };

    /**
     * 返回日期的yyyy-MM-dd格式
     * @param date 日期
     * @alias module:_.shortDateFormat
     * @example
     * _.shortDateFormat(_.now());
     * =>2019-05-15
     */
    _.shortDateFormat = function (date) {
        if (!date) {
            return "";
        }
        return this.dateFormat(date, 'yyyy-MM-dd');
    };

    /**
     * 将时间格式化为指定格式的String
     * @param {Number}  n   时间(单位秒)
     * @returns {string}
     * @alias module:_.formatTime
     * @example
     * _.formatTime(25);
     * => '0:00:25'
     * _.formatTime(63846);
     * => '17:44:06'
     */
    _.formatTime = function (n) {
        if (n < 0) {
            throw "parameter cannot be less than 0!";
        }
        var hours = Math.floor(n / 60 / 60);
        var minutes = Math.floor((n - (hours * 60 * 60)) / 60);
        var seconds = Math.round(n - (hours * 60 * 60) - (minutes * 60));
        return hours + ':' + ((minutes < 10) ? '0' + minutes : minutes) + ':' + ((seconds < 10) ? '0' + seconds : seconds);
    };

    /**
     * 反格式化,与formatTime函数相反
     * @param {String}  string  要格式化的时间字符串
     * @returns {number}
     * @alias module:_.unformatTime
     * @example
     * _.unformatTime('0:00:25');
     * => 25
     * _.unformatTime('17:44:06');
     * => 63846
     */
    _.unformatTime = function (string) {
        var timeArray = string.split(':'), seconds = 0;
        if (timeArray.length === 3) {
            // hours
            seconds = seconds + (Number(timeArray[0]) * 60 * 60);
            // minutes
            seconds = seconds + (Number(timeArray[1]) * 60);
            // seconds
            seconds = seconds + Number(timeArray[2]);
        } else if (timeArray.length === 2) {
            // minutes
            seconds = seconds + (Number(timeArray[0]) * 60);
            // seconds
            seconds = seconds + Number(timeArray[1]);
        }
        return Number(seconds);
    };
    //日期相关结束

    //数组工具相关开始
    //-------------
    /**
     * 数组差集,以第一个数组为主
     * @param {Array}   a   主数组
     * @param {Array}   b   次数组
     * @returns {*}
     * @alias module:_.difference
     * @example
     * var a = [1,2,3,4,5]
     * var b = [2,4,6,8,10]
     * _.difference(a, b)
     * =>[1,3,5]
     */
    _.difference = function (a, b) {
        var filtered = a.filter(function (e) {
            return b.indexOf(e) === -1;
        });
        return filtered;
    };

    /**
     * 洗牌数组
     * @param {String}  array   要洗牌的数组
     * @returns {Array}
     * @alias module:_.shuffle
     * @example
     * var a = [1,2,3,4,5]
     * _.shuffle(a)
     * => [3, 2, 4, 5, 1]
     */
    _.shuffle = function (array) {
        var copy = [], n = array.length, i;
        while (n) {
            i = Math.floor(Math.random() * n--);
            copy.push(array.splice(i, 1)[0]);
        }
        return copy;
    };

    /**
     * 数组元素是否重复
     * @param {Array}   arr 要检查的数组对象
     * @returns {boolean}
     * @alias module:_.isRepeat
     * @example
     * var a = [1, 2, 3, 4, 5];
     * var b = [1, 2, 3, 5, 5];
     * _.isRepeat(a);
     * =>false
     *
     * _.isRepeat(b);
     * =>true
     */
    _.isRepeat = function (arr) {
        var hash = {};
        for (var i in arr) {
            if (hash[arr[i]]) {
                return true;
            }
            hash[arr[i]] = true;
        }
        return false;
    };

    /**
     * 数组去重
     * @param {Array}   arr 要去重的数组
     * @returns {Array}
     * @alias module:_.unique
     * @example
     * var b = [1, 2, 3, 5, 5];
     * _.unique(b);
     * => [1, 2, 3, 5]
     */
    _.unique = function (arr) {
        var a = [];
        for (var i = 0, l = arr.length; i < l; i++) {
            if (a.indexOf(arr[i]) === -1) {
                a.push(arr[i]);
            }
        }
        return a;
    };

    /**
     * 判断数组是否相等,默认为严格模式比较
     * @param {Array}   a       第一个数组
     * @param {Array}   b       第二个数组
     * @param {Boolean} strict  是否严格比较(默认为true)
     * @returns {boolean}
     * @alias module:_.equals
     * @example
     * var arr1 = [1, 2, 3, 4];
     * var arr2 = [2, 1, 4, 3];
     * var arr3 = [2, 2, 3, 4];
     * var arr4 = [1, 2, 3, 4];
     * _.equals(arr1,arr2);
     * => false
     *
     * _.equals(arr1,arr2, false);
     * =>true
     *
     * _.equals(arr1,arr3);
     * =>false
     *
     * _.equals(arr1,arr3, false);
     * =>false
     *
     * _.equals(arr1,arr4);
     * =>true
     *
     * _.equals(arr1,arr4, false);
     * =>true
     */
    _.equals = function (a, b, strict) {
        if (!b) return false;
        if (arguments.length == 2) strict = true;
        if (a.length != b.length) return false;
        for (var i = 0; i < a.length; i++) {
            if (a[i] instanceof Array && b[i] instanceof Array) {
                if (!a[i].equals(b[i], strict)) return false;
            } else if (strict && a[i] != b[i]) {
                return false;
            } else if (!strict) {
                return _.equals(a.sort(), b.sort(), true);
            }
        }
        return true;
    };
    //数组工具相关结束

    /**
     * 精度范围
     * @readonly
     * @alias module:_.EPSILON
     */
    _.EPSILON = Math.pow(2, -52);

    /**
     * 默认数值保留小数位数
     * @readonly
     * @alias module:_.DEFAULT_SCALE
     */
    _.DEFAULT_SCALE = 2;

    /**
     * 加
     * @param {*}  x   第一个数值
     * @param {*}  y   第二个数组
     * @returns {number}
     * @alias module:_.numAdd
     * @example
     * _.numAdd('3',5);
     * =>8
     */
    _.numAdd = function (x, y) {
        var result = Number(x) + Number(y);
        return result;
    };

    /**
     * 减
     * @param {*}   x   第一个数值
     * @param {*}   y   第二个数值
     * @returns {number}
     * @alias module:_.numSubtract
     * @example
     * _.numSubtract('7',5);
     * =>2
     */
    _.numSubtract = function (x, y) {
        var result = Number(x) - Number(y);
        return result;
    };

    /**
     * 乘
     * @param {*}   x    第一个数值
     * @param {*}   y    第二个数值
     * @returns {number}
     * @alias module:_.numMultiply
     * @example
     * _.numMultiply('2',3);
     * =>6
     */
    _.numMultiply = function (x, y) {
        var result = Number(x) * Number(y);
        return result;
    };

    /**
     * 除
     * @param {*}   x    第一个数值
     * @param {*}   y    第二个数值
     * @returns {number}
     * @alias module:_.numDivide
     * @example
     * _.numDivide(9,'3');
     * =>3
     */
    _.numDivide = function (x, y) {
        var result = Number(x) / Number(y);
        return result;
    };

    /**
     * 精度加法函数,用来得到精确的加法结果
     * javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
     * @param arg1
     * @param arg2
     * @returns {number}
     */
    _.accAdd = function (arg1, arg2) {
        var r1, r2, m, c;
        try {
            r1 = arg1.toString().split(".")[1].length;
        } catch (e) {
            r1 = 0;
        }
        try {
            r2 = arg2.toString().split(".")[1].length;
        } catch (e) {
            r2 = 0;
        }
        c = Math.abs(r1 - r2);
        m = Math.pow(10, Math.max(r1, r2));
        if (c > 0) {
            var cm = Math.pow(10, c);
            if (r1 > r2) {
                arg1 = Number(arg1.toString().replace(".", ""));
                arg2 = Number(arg2.toString().replace(".", "")) * cm;
            } else {
                arg1 = Number(arg1.toString().replace(".", "")) * cm;
                arg2 = Number(arg2.toString().replace(".", ""));
            }
        } else {
            arg1 = Number(arg1.toString().replace(".", ""));
            arg2 = Number(arg2.toString().replace(".", ""));
        }
        return (arg1 + arg2) / m;
    };

    /**
     * 精度减法函数,用来得到精确的减法结果
     * javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。
     * @param arg1
     * @param arg2
     * @returns {string}
     */
    _.accSub = function (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)); //动态控制精度长度
        n = (r1 >= r2) ? r1 : r2;
        return ((arg1 * m - arg2 * m) / m).toFixed(n);
    };

    /**
     * 数度乘法函数,用来得到精确的乘法结果
     * javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
     * @param arg1
     * @param arg2
     * @returns {number} arg1乘以 arg2的精确结果
     */
    _.accMul = function (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);
    };

    /**
     * 除法函数,用来得到精确的除法结果
     * javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
     * @param arg1
     * @param arg2
     * @returns {number} arg1除以arg2的精确结果
     */
    _.accDiv = function (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) {
        }
        r1 = Number(arg1.toString().replace(".", ""));
        r2 = Number(arg2.toString().replace(".", ""));
        return (r1 / r2) * Math.pow(10, t2 - t1);
    };

    /**
     * 固定小数位数
     * @param {Number}  x       要处理的数值
     * @param {Number}  digits  小数位置,默认保留2位
     * @returns {string|*}
     * @alias module:_.toFixed
     * @example
     * _.toFixed(548.6512)
     * =>548.65
     */
    _.toFixed = function (x, digits) {
        if (typeof (x) == "undefined" || x === '') {
            return '';
        } else if (!_.isNumeric(x)) {
            return x;
        }
        digits = digits || this.DEFAULT_SCALE;
        x = (x + '').replace("E", "e");
        if ((x + '').indexOf('e') != -1) {
            return (+(Math.round(+(x)) + 'e' + -digits)).toFixed(digits);
        }
        return (+(Math.round(+(x + 'e' + digits)) + 'e' + -digits)).toFixed(digits);
    };

    /**
     * 移除数值指数表示
     * @param {Number}  x   要处理的数值
     * @returns {string}
     * @alias module:_.toFixed
     * @example
     *         var numbers = [
     *             1.1234567890123456789e+30,
     *             1.1234567890123456789e-30,
     *             -1.1234567890123456789e+30,
     *             -1.1234567890123456789e-30]
     * var i;
     * for (i=0;i<numbers.length;i++) {
     *        console.log(_.removeExponent(numbers[i]));
     *   }
     * =>1123456789012345700000000000000
     * =>0.0000000000000000000000000000011234567890123458
     * =>-1123456789012345700000000000000
     * =>0.00000000000000000000000000000.11234567890123458
     */
    _.removeExponent = function (x) {
        if (Math.abs(x) < 1.0) {
            var e = parseInt(x.toString().split('e-')[1]);
            if (e) {
                x *= Math.pow(10, e - 1);
                x = '0.' + (new Array(e)).join('0') + x.toString().substring(2);
            }
        } else {
            var e = parseInt(x.toString().split('+')[1]);
            if (e > 20) {
                e -= 20;
                x /= Math.pow(10, e);
                x += (new Array(e + 1)).join('0');
            }
        }
        return x;
    };

    /**
     * 判断两个小数数值是否相等
     * @param {Number}  x   第一个数值
     * @param {Number}  y   第二个数值
     * @returns {boolean}
     * @alias module:_.numEqual
     * @example
     * _.numEqual(0.1+0.2, 0.3);
     * =>true
     */
    _.numEqual = function (x, y) {
        return Math.abs(x - y) < this.EPSILON;
    };

    /**
     * 比较两数大小,x>y返回1,x==y返回0,否则返回-1
     * @param {Number}  x   第一个数值
     * @param {Number}  y   第二个数值
     * @returns {number}
     * @alias module:_.numCompare
     * @example
     * _.numCompare(3,5);
     * =>-4
     *
     * -.numCompare('aaa',3);
     * =>抛出异常Parameter is not a number!
     */
    _.numCompare = function (x, y) {
        if (!_.isNumeric(x) || !_.isNumeric(y)) {
            throw "Parameter is not a number!";
        }
        if (_.numEqual(x, y)) return 0;
        else if (x > y) return 1;
        else return -1;
    };

    var REG_NUMBER = /^([+-])?0*(\d+)(\.(\d+))?$/;
    var REG_E = /^([+-])?0*(\d+)(\.(\d+))?e(([+-])?(\d+))$/i;

    /**
     * 分析数字字符串
     *
     * @param {string} num NumberString
     * @returns {object}
     */
    _.getNumbResult = function (num) {
        var result = REG_NUMBER.exec(num.toString());
        if (!result && REG_E.test(num.toString())) {
            result = REG_NUMBER.exec(_.e2ten(num.toString()))
        }
        if (result) {
            return {
                int: result[2],
                decimal: result[4],
                minus: result[1] == "-",
                num: result.slice(1, 3).join('')
            }
        }
    };

    /**
     * 科学计数法转十进制
     *
     * @param {string} num 科学记数法字符串
     * @returns {string}
     */
    _.e2ten = function (num) {
        var result = REG_E.exec(num.toString());
        if (!result) return num;
        var zs = result[2]
            , xs = result[4] || ""
            , e = result[5] ? +result[5] : 0;
        if (e > 0) {
            var _zs = xs.substr(0, e);
            _zs = _zs.length < e ? _zs + new Array(e - _zs.length + 1).join("0") : _zs;
            xs = xs.substr(e);
            zs += _zs;
        } else {
            e = -e;
            var s_start = zs.length - e;
            s_start = s_start < 0 ? 0 : s_start;
            var _xs = zs.substr(s_start, e);
            _xs = _xs.length < e ? new Array(e - _xs.length + 1).join("0") + _xs : _xs;
            zs = zs.substring(0, s_start);
            xs = _xs + xs;
        }
        zs = zs == "" ? "0" : zs;
        return (result[1] == "-" ? "-" : "") + zs + (xs ? "." + xs : "");
    };

    /**
     * 清理多余"零"
     *
     * @param {any} str
     * @param {any} zero "零"字符
     * @param {any} type 清理模式 ^ - 开头, $ - 结尾, nto1 - 多个连续变一个
     * @returns {string}
     */
    _.clearZero = function (str, zero, type) {
        if (str == null) return "";
        var reg0 = ~"*.?+$^[](){}|\\/".indexOf(zero) ? "\\" + zero : zero;
        var arg_s = new RegExp("^" + reg0 + "+")
            , arg_e = new RegExp(reg0 + "+$")
            , arg_d = new RegExp(reg0 + "{2}", "g")
        str = str.toString();
        if (type == "^") {
            str = str.replace(arg_s, "");
        }
        if (!type || type == "$") {
            str = str.replace(arg_e, "");
        }
        if (!type || type == "nto1") {
            str = str.replace(arg_d, zero);
        }
        return str;
    };

    /**
     * 阿拉伯数字转中文数字
     * (源码:https://github.com/cnwhy/nzh.git)
     * @param {String} num 阿拉伯数字/字符串 , 科学记数法字符串
     * @param {Object} opration 转换配置<br>
     *                          {<br>
     *                              ww: {万万化单位 | false}<br>
     *                              tenMin: {十的口语化 | false}<br>
     *                              nzh:{中文语言|简体}<br>
     *                          }<br>
     * @returns {string}
     * @alias module:_.cl
     * @version 1.8.3
     * @example
     * _.cl("100111",{tenMin:true})
     * => "十万零一百一十一"
     *
     * _.cl("100111")
     *=> "一十万零一百一十一"
     *
     * _.cl("13.5",{tenMin:true})
     * => "十三点五"
     *
     * _.cl(1e16)
     * => "一亿亿"
     */
    _.cl = function (num, options) {
        var result = _.getNumbResult(num)
        if (!result) {
            return num;
        }
        options = options ? options : {};

        var nzh = options.nzh || {
            ch: '零一二三四五六七八九'
            , ch_u: '个十百千万亿'
            , ch_f: '负'
            , ch_d: '点'
        };

        var ch = nzh.ch             //数字
            , ch_u = nzh.ch_u       //单位
            , ch_f = nzh.ch_f || "" //负
            , ch_d = nzh.ch_d || "." //点
            , n0 = ch.charAt(0); //零
        var _int = result.int             //整数部分
            , _decimal = result.decimal   //小数部分
            , _minus = result.minus;      //负数标识
        var int = ""
            , dicimal = ""
            , minus = _minus ? ch_f : ''; //符号位
        var encodeInt = function encodeInt(_int, _m, _dg) {
            _int = _.getNumbResult(_int).int;
            var int = ""
                , tenm = arguments.length > 1 ? arguments[1] : options.tenMin
                , _length = _int.length;
            //一位整数
            if (_length == 1) return ch.charAt(+_int);
            if (_length <= 4) { //四位及以下
                for (var i = 0, n = _length; n--;) {
                    var _num = +_int.charAt(i);
                    int += (tenm && _length == 2 && i == 0 && _num == 1) ? "" : ch.charAt(_num);
                    int += (_num && n ? ch_u.charAt(n) : '')
                    i++;
                }
            } else {  //大数递归
                var d = _int.length / 4 >> 0
                    , y = _int.length % 4;
                //"兆","京"等单位处理
                while (y == 0 || !ch_u.charAt(3 + d)) {
                    y += 4;
                    d--;
                }
                var _maxLeft = _int.substr(0, y), //最大单位前的数字
                    _other = _int.substr(y); //剩余数字

                int = encodeInt(_maxLeft, tenm) + ch_u.charAt(3 + d)
                    + (_other.charAt(0) == '0' ? n0 : '') //单位后有0则加零
                    + encodeInt(_other, tenm)
            }
            int = _.clearZero(int, n0); //修整零
            return int;
        }

        //转换小数部分
        if (_decimal) {
            _decimal = _.clearZero(_decimal, "0", "$"); //去除尾部0
            for (var x = 0; x < _decimal.length; x++) {
                dicimal += ch.charAt(+_decimal.charAt(x));
            }
            dicimal = dicimal ? ch_d + dicimal : "";
        }

        //转换整数部分
        int = encodeInt(_int);  //转换整数

        //超级大数的万万化
        if (options.ww && ch_u.length > 5) {
            var dw_w = ch_u.charAt(4)
                , dw_y = ch_u.charAt(5);
            var lasty = int.lastIndexOf(dw_y);
            if (~lasty) {
                int = int.substring(0, lasty).replace(new RegExp(dw_y, 'g'), dw_w + dw_w) + int.substring(lasty);
            }
        }
        return minus + int + dicimal;
    };

    /**
     * 计算地图上两个点的距离
     * @param {float} lng1 经度1
     * @param {float} lat1 纬度1
     * @param {float} lng2 经度2
     * @param {float} lat2 纬度2
     * @returns {float} 返回单位千米
     * @version 1.8.5
     */
    _.getMapDistance = function (lng1, lat1, lng2, lat2) {
        var f = getRad((lat1 + lat2) / 2);
        var g = getRad((lat1 - lat2) / 2);
        var l = getRad((lng1 - lng2) / 2);
        if (g == 0 && l == 0) { //两点重叠,直接返回0
            return g;
        }
        var sg = Math.sin(g);
        var sl = Math.sin(l);
        var sf = Math.sin(f);
        var s, c, w, r, d, h1, h2;
        var a = 6378137.0;
        var fl = 1 / 298.257;
        sg = sg * sg;
        sl = sl * sl;
        sf = sf * sf;
        s = sg * (1 - sl) + (1 - sf) * sl;
        c = (1 - sg) * (1 - sl) + sf * sl;
        w = Math.atan(Math.sqrt(s / c));
        r = Math.sqrt(s * c) / w;
        d = 2 * w * a;
        h1 = (3 * r - 1) / 2 / c;
        h2 = (3 * r + 1) / 2 / s;
        s = d * (1 + fl * (h1 * sf * (1 - sg) - h2 * (1 - sf) * sg));
        s = s / 1000;
        s = s.toFixed(2);
        return s;
    }

    function getRad(d) {
        var PI = Math.PI;
        return d * PI / 180.0;
    }

    /**
     * 判断地图上的点是否在多边形内
     * @param {Object} point 点
     * @param {Array} pts   面
     * @returns {boolean}
     * @version 1.8.5
     */
    _.isMapPointInPolygon = function (point, pts) {
        var N = pts.length;  //pts [{lat:xxx,lng:xxx},{lat:xxx,lng:xxx}]
        var boundOrVertex = true; //如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
        var intersectCount = 0;//cross points count of x
        var precision = 2e-10; //浮点类型计算时候与0比较时候的容差
        var p1, p2;//neighbour bound vertices
        var p = point; //point {lat:xxx,lng:xxx}

        p1 = pts[0];//left vertex
        for (var i = 1; i <= N; ++i) {//check all rays
            if ((p.lat == p1.lat) && (p.lng == p1.lng)) {
                return boundOrVertex;//p is an vertex
            }
            p2 = pts[i % N];//right vertex
            if (p.lat < Math.min(p1.lat, p2.lat) || p.lat > Math.max(p1.lat, p2.lat)) {//ray is outside of our interests
                p1 = p2;
                continue;//next ray left point
            }
            if (p.lat > Math.min(p1.lat, p2.lat) && p.lat < Math.max(p1.lat, p2.lat)) {//ray is crossing over by the algorithm (common part of)
                if (p.lng <= Math.max(p1.lng, p2.lng)) {//x is before of ray
                    if (p1.lat == p2.lat && p.lng >= Math.min(p1.lng, p2.lng)) {//overlies on a horizontal ray
                        return boundOrVertex;
                    }
                    if (p1.lng == p2.lng) {//ray is vertical
                        if (p1.lng == p.lng) {//overlies on a vertical ray
                            return boundOrVertex;
                        } else {//before ray
                            ++intersectCount;
                        }
                    } else {//cross point on the left side
                        var xinters = (p.lat - p1.lat) * (p2.lng - p1.lng) / (p2.lat - p1.lat) + p1.lng;//cross point of lng
                        if (Math.abs(p.lng - xinters) < precision) {//overlies on a ray
                            return boundOrVertex;
                        }
                        if (p.lng < xinters) {//before ray
                            ++intersectCount;
                        }
                    }
                }
            } else {//special case when ray is crossing through the vertex
                if (p.lat == p2.lat && p.lng <= p2.lng) {//p crossing over p2
                    var p3 = pts[(i + 1) % N]; //next vertex
                    if (p.lat >= Math.min(p1.lat, p3.lat) && p.lat <= Math.max(p1.lat, p3.lat)) {//p.lat lies between p1.lat & p3.lat
                        ++intersectCount;
                    } else {
                        intersectCount += 2;
                    }
                }
            }
            p1 = p2;//next ray left point
        }
        if (intersectCount % 2 == 0) {//偶数在多边形外
            return false;
        } else { //奇数在多边形内
            return true;
        }
    };

    //这里继续扩展函数
    //示例:_.a1=function(){};

    /**
     * 允许自己的实用程序函数扩展jPublic。传递一个 {name: function}定义的哈希添加到jPublic对象,以及面向对象封装。
     * @param obj
     * @returns {_}
     * @alias module:_.extend
     * @example
     * _.extend({
     *  abc: function(str) {
     *      return str;
     *  }
     * });
     * _.abc("Hello");
     * =>"Hello"
     */
    _.extend = function (obj) {
        _.each(_.functions(obj), function (name) {
            var func = _[name] = obj[name];
            _.prototype[name] = function () {
                var args = [this._wrapped];
                push.apply(args, arguments);
                return chainResult(this, func.apply(_, args));
            };
        });
        return _;
    };
    _.extend(_);
}());