公共方法库

202 阅读1分钟

(function () {
    "use strict";
    const class2type = {};
    const getProto = Object.getPrototypeOf;
    const toString = class2type.toString; //->Object.prototype.toString
    const hasOwn = class2type.hasOwnProperty; //->Object.prototype.hasOwnProperty
    const fnToString = hasOwn.toString; //->Function.prototype.toString
    const ObjectFunctionString = fnToString.call(Object);
    //->Object.toString()  //->"function Object() { [native code] }"

    /*
    // 建立数据类型检测的映射表
    const arr_type = ["Boolean", "Number", "String", "Function", 
    "Array", "Date", "RegExp", "Object", "Error", "Symbol", "BigInt"];
    arr_type.forEach(function (name) {
        class2type["[object " + name + "]"] = name.toLowerCase();
    });
    // 通用检测方法
    const toType = function toType(obj) {
        if (obj == null) return obj + "";
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[toString.call(obj)] || "object" :
            typeof obj;
    };
    */
    
    // 检测数据类型
    const toType = function toType(obj) {
        if (obj == null) return obj + "";
        const reg = /^\[object ([a-zA-Z0-9]+)\]$/i;
        return typeof obj === "object" || typeof obj === "function" ?
            reg.exec(toString.call(obj))[1].toLowerCase() :
            typeof obj;
    };


    // 检测是否为函数
    const isFunction = function isFunction(obj) {
        // In some browsers, typeof returns "function" for HTML <object> elements
        // (i.e., `typeof document.createElement( "object" ) === "function"`).
        return typeof obj === "function" && typeof obj.nodeType !== "number";
    };

    // 检测是否为window
    const isWindow = function isWindow(obj) {
        return obj != null && obj === obj.window;
    };

    // 检测是否为数组或者类数组
    const isArrayLike = function isArrayLike(obj) {
        const length = !!obj && "length" in obj && obj.length,
            type = toType(obj);
        if (isFunction(obj) || isWindow(obj)) return false;
        return type === "array" || length === 0 ||
            typeof length === "number" && length > 0 && (length - 1) in obj;
    };

    // 检测是否为纯粹的对象(obj.__proto__===Object.prototype || Object.create(null))
    const isPlainObject = function isPlainObject(obj) {
        const proto, Ctor;
        if (!obj || toString.call(obj) !== "[object Object]") return false;
        proto = getProto(obj);
        if (!proto) return true; // Object.create(null)
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
    };

    // 检测当前对象是否为空对象
    const isEmptyObject = function isEmptyObject(obj) {
        if (obj == null) return false;
        const keys = Object.keys(obj);
        if (typeof Symbol !== "undefined") {
            keys = keys.concat(Object.getOwnPropertySymbols(obj));
        }
        return keys.length === 0;
    };

    // 检测是否为有效数字,认为:10和"10"都是有效数字,但是true/null这些都不是
    const isNumeric = function isNumeric(obj) {
        const type = toType(obj);
        return (type === "number" || type === "string") && !isNaN(obj);
    };

    //遍历数组/类数组/对象中的每一项
    const each = function each(obj, callback) {
        if (typeof callback !== "function") callback = Function.prototype;
        let i = 0,
            len,
            item,
            keys,
            key;
        if (isArrayLike(obj)) {
            len = obj.length;
            for (; i < len; i++) {
                item = obj[i];
                // 我们处理了FOR-EACH不支持的“循环结束的控制方式”:回调函数返回false
                if (callback.call(item, item, i) === false) break;
            }
        } else {
            keys = Object.keys(obj);
            // 我们考虑FOR IN的BUG
            if (typeof Symbol !== "undefined") {
                keys = keys.concat(Object.getOwnPropertySymbols(obj))
            };
            for (; i < keys.length; i++) {
                key = keys[i];
                item = obj[key];
                if (callback.call(item, item, key) === false) break;
            }
        }
        return obj;
    };

    // 实现「数组/纯粹对象」深浅合并
    思路1const merge = function merge() {
        const options, src, copyIsArray,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;
        if (typeof target === "boolean") {
            deep = target;
            target = arguments[i] || {};
            i++;
        }
        if (typeof target !== "object" && !isFunction(target)) target = {};
        for (; i < length; i++) {
            if ((options = arguments[i]) != null) {
                each(options, function (copy, name) {
                    // 防止死递归
                    if (target === copy) return;
                    copyIsArray = Array.isArray(copy);
                    if (deep && copy && (isPlainObject(copy) || copyIsArray)) {
                        src = target[name];
                        if (copyIsArray && !Array.isArray(src)) {
                            src = [];
                        } else if (!copyIsArray && !isPlainObject(src)) {
                            src = {};
                        }
                        target[name] = merge(deep, src, copy);
                        return;
                    }
                    target[name] = copy;
                });
            }
        }
        return target;
    };
    思路2const merge = function merge() {
        let options,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false,
            treated = arguments[length - 1];
        // 第一次执行merge,最后一项是用来替换TARGET的,不是用来记录谁处理过、谁没处理过的,
        // 所以我们要为其赋值一个新数组;
        // 等到后期每次递归,我们都会在最末尾把存放哪些处理过的数组传递过来,
        // 此时最后一项这个数组才不是用来替换TARGET的!! 
        //treated.treated有这个属性的数组是专门存放哪些处理过的
        if (Array.isArray(treated) && treated.treated) {
            length--;
        } else {
            treated = [];
            treated.treated = true;
        }
        // 如果第一个值是布尔,要把这个值给DEEP,让TARGET存储的第二个传递的参数
        // (也就是第一个对象),也就是要被替换的对象
        if (typeof target === "boolean") {
            deep = target;
            target = arguments[i] || {};
            i++;
        }
        // 确保TARGET是个对象
        if (typeof target !== "object" && !isFunction(target)) target = {};
        // 循环除第一个传递的对象外,剩下的每个传递的对象
        for (; i < length; i++) {
            options = arguments[i];
            if (options == null) continue;
            // 之前已经处理过这个对象的,就没必要在处理了;没处理过的,加入到treated处理列表中!
            if (treated.includes(options)) return options;
            treated.push(options);
            // 循环这个对象中的每一项,用每一项的值替换TARGET中对应项的值
            each(options, function (copy, name) {
                let copyIsArray = Array.isArray(copy),
                    copyIsObject = isPlainObject(copy),
                    src = target[name],
                    clone = src;
                // 如果某一项的值是纯粹对象或者数组,并且DEEP是TURE,我们开启深度合并
                if (deep && copy && (copyIsArray || copyIsObject)) {
                    if (copyIsArray && !Array.isArray(clone)) clone = [];
                    if (copyIsObject && !isPlainObject(clone)) clone = {};
                    target[name] = merge(deep, clone, copy, treated);
                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            });
        }
        return target;
    };

    // 实现「数组/纯粹对象,其余类型的值,直接浅克隆即可」深浅克隆
    const clone = function clone(deep, obj, cache) {
        const type, Ctor, copy;
        if (typeof deep !== "boolean") {
            obj = deep;
            deep = false;
        }

        // 死递归的优化处理
        !Array.isArray(cache) ? cache = [] : null;
        if (cache.indexOf(obj) > -1) return obj;
        cache.push(obj);

        // 原始值类型直接返回
        if (obj == null) return obj;
        if (!/^(object|function)$/.test(typeof obj)) return obj;

        // 特殊值的处理
        type = toType(obj);
        Ctor = obj.constructor;
        if (/^(number|string|boolean|regexp|date)$/.test(type)) return new Ctor(obj);
        if (type === "error") return new Ctor(obj.message);
        if (type === "function") {
            return function proxy() {
                const params = [].slice.call(arguments);
                return obj.apply(this, params);
            };
        }
        if (!isPlainObject(obj) && !Array.isArray(obj)) return obj;

        // 纯粹对象和数组
        copy = new Ctor();
        each(obj, function (value, key) {
            if (deep) {
                // 深拷贝
                copy[key] = clone(deep, value, cache);
                return;
            }
            // 浅拷贝
            copy[key] = value;
        });
        return copy;
    };

    /* 暴露API */
    const utils = {
        toType,
        isFunction,
        isWindow,
        isArrayLike,
        isPlainObject,
        isEmptyObject,
        isNumeric,
        each,
        merge,
        clone,
    };

    // 转让使用权
    const _$ = window._;
    utils.noConflict = function noConflict() {
        if (window._ === utils) window._ = _$;
        return utils;
    };

    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = utils;
    }
    if (typeof window !== "undefined") {
        window._ = window.utils = utils;
    }
})();