javascript中的复制

javascript中的复制

github

深度复制这个问题看似简单,实际上要想完美实现需要很多知识。

需要考虑Set,Map,Promise等类型的对象

这个库简洁明了,是一个小巧玲珑的JS库

Promise简单例子

function f1() {
    console.log("f1")
}

function f2() {
    console.log("f2")
}

function wait(ms) {
    return new Promise(
        function(resolve, reject) {
            setTimeout(function() {
                resolve();
            }, ms)
        });
}
wait(3000).then(f1)
Promise.all([wait(3000), wait(4000)]).then(f1)

源码

/**
 * 使用如下结构来进行变量隐藏,防止暴露变量
 * var clone=(function(){
 *  function f(){
 *  }  
 *  return f
 * })()
 */
var clone = (function() {
    'use strict';

    //工具函数:判断obj是否是type类型
    function _instanceof(obj, type) {
        return type != null && obj instanceof type;
    }

    /**
     * JS中有Map、Set等集合
     * Promise也是一种数据结构,也是可以复制的
     */
    var nativeMap;
    try {
        nativeMap = Map;
    } catch (_) {
        // maybe a reference error because no `Map`. Give it a dummy value that no
        // value will ever be an instanceof.
        nativeMap = function() {};
    }

    var nativeSet;
    try {
        nativeSet = Set;
    } catch (_) {
        nativeSet = function() {};
    }

    var nativePromise;
    try {
        nativePromise = Promise;
    } catch (_) {
        nativePromise = function() {};
    }

    /**
     * Clones (copies) an Object using deep copying.
     *
     * This function supports circular references by default, but if you are certain
     * there are no circular references in your object, you can save some CPU time
     * by calling clone(obj, false).
     *
     * Caution: if `circular` is false and `parent` contains circular references,
     * your program may enter an infinite loop and crash.
     * 注意:如果禁用circular选项,那么出现循环包含的地方程序就会进入子循环。
     *
     * @param `parent` - the object to be cloned
     * @param `circular` - set to true if the object to be cloned may contain
     *    circular references. (optional - true by default)
     * @param `depth` - set to a number if the object is only to be cloned to
     *    a particular depth. (optional - defaults to Infinity)
     * @param `prototype` - sets the prototype to be used when cloning an object.
     *    (optional - defaults to parent prototype).
     * @param `includeNonEnumerable` - set to true if the non-enumerable properties
     *    should be cloned as well. Non-enumerable properties on the prototype
     *    chain will be ignored. (optional - false by default)
     */
    //定义了工具函数、一些变量之后,大boss终于登场了
    function clone(parent, circular, depth, prototype, includeNonEnumerable) {
        //支持第二种传参方式:把参数打包成json传到clone的第二个参数  
        if (typeof circular === 'object') {
            depth = circular.depth;
            prototype = circular.prototype;
            includeNonEnumerable = circular.includeNonEnumerable;
            circular = circular.circular;
        }
        // maintain two arrays for circular references, where corresponding parents
        // and children have the same index
        var allParents = [];
        var allChildren = [];

        var useBuffer = typeof Buffer != 'undefined';

        if (typeof circular == 'undefined') //默认行为:执行循环
            circular = true;

        if (typeof depth == 'undefined') //默认行为:有一定深度
            depth = Infinity;

        // recurse this function so we don't reset allParents and allChildren
        //克隆的过程是一个递归的过程,上面的全局变量allParents和allChildren就是为这个函数服务的
        function _clone(parent, depth) {
            // cloning null always returns null
            if (parent === null)
                return null;

            if (depth === 0)
                return parent;

            var child;
            var proto;
            if (typeof parent != 'object') {
                return parent;
            }

            if (_instanceof(parent, nativeMap)) {
                child = new nativeMap();
            } else if (_instanceof(parent, nativeSet)) {
                child = new nativeSet();
            } else if (_instanceof(parent, nativePromise)) {
                child = new nativePromise(function(resolve, reject) {
                    parent.then(function(value) {
                        resolve(_clone(value, depth - 1));
                    }, function(err) {
                        reject(_clone(err, depth - 1));
                    });
                });
            } else if (clone.__isArray(parent)) {
                child = [];
            } else if (clone.__isRegExp(parent)) {
                child = new RegExp(parent.source, __getRegExpFlags(parent));
                if (parent.lastIndex) child.lastIndex = parent.lastIndex;
            } else if (clone.__isDate(parent)) {
                child = new Date(parent.getTime());
            } else if (useBuffer && Buffer.isBuffer(parent)) {
                child = new Buffer(parent.length);
                parent.copy(child);
                return child;
            } else if (_instanceof(parent, Error)) {
                child = Object.create(parent);
            } else {
                if (typeof prototype == 'undefined') {
                    proto = Object.getPrototypeOf(parent);
                    child = Object.create(proto);
                } else {
                    child = Object.create(prototype);
                    proto = prototype;
                }
            }

            if (circular) {
                var index = allParents.indexOf(parent);

                if (index != -1) {
                    return allChildren[index];
                }
                allParents.push(parent);
                allChildren.push(child);
            }

            if (_instanceof(parent, nativeMap)) {
                parent.forEach(function(value, key) {
                    var keyChild = _clone(key, depth - 1);
                    var valueChild = _clone(value, depth - 1);
                    child.set(keyChild, valueChild);
                });
            }
            if (_instanceof(parent, nativeSet)) {
                parent.forEach(function(value) {
                    var entryChild = _clone(value, depth - 1);
                    child.add(entryChild);
                });
            }

            for (var i in parent) {
                var attrs;
                if (proto) {
                    attrs = Object.getOwnPropertyDescriptor(proto, i);
                }

                if (attrs && attrs.set == null) {
                    continue;
                }
                child[i] = _clone(parent[i], depth - 1);
            }

            if (Object.getOwnPropertySymbols) {
                var symbols = Object.getOwnPropertySymbols(parent);
                for (var i = 0; i < symbols.length; i++) {
                    // Don't need to worry about cloning a symbol because it is a primitive,
                    // like a number or string.
                    var symbol = symbols[i];
                    var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
                    if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
                        continue;
                    }
                    child[symbol] = _clone(parent[symbol], depth - 1);
                    if (!descriptor.enumerable) {
                        Object.defineProperty(child, symbol, {
                            enumerable: false
                        });
                    }
                }
            }

            if (includeNonEnumerable) {
                var allPropertyNames = Object.getOwnPropertyNames(parent);
                for (var i = 0; i < allPropertyNames.length; i++) {
                    var propertyName = allPropertyNames[i];
                    var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
                    if (descriptor && descriptor.enumerable) {
                        continue;
                    }
                    child[propertyName] = _clone(parent[propertyName], depth - 1);
                    Object.defineProperty(child, propertyName, {
                        enumerable: false
                    });
                }
            }

            return child;
        }

        return _clone(parent, depth);
    }

    /**
     * Simple flat clone using prototype, accepts only objects, usefull for property
     * override on FLAT configuration object (no nested props).
     *
     * USE WITH CAUTION! This may not behave as you wish if you do not know how this
     * works.
     */
    clone.clonePrototype = function clonePrototype(parent) {
       if (parent === null)
            return null;

        var c = function() {};
        c.prototype = parent;
        return new c();
    };

    // private utility functions

    function __objToStr(o) {
        return Object.prototype.toString.call(o);
    }
    clone.__objToStr = __objToStr;

    function __isDate(o) {
        return typeof o === 'object' && __objToStr(o) === '[object Date]';
    }
    clone.__isDate = __isDate;

    function __isArray(o) {
        return typeof o === 'object' && __objToStr(o) === '[object Array]';
    }
    clone.__isArray = __isArray;

    function __isRegExp(o) {
        return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
    }
    clone.__isRegExp = __isRegExp;

    

github

深度复制这个问题看似简单,实际上要想完美实现需要很多知识。

需要考虑Set,Map,Promise等类型的对象

这个库简洁明了,是一个小巧玲珑的JS库

Promise简单例子

function f1() {
    console.log("f1")
}

function f2() {
    console.log("f2")
}

function wait(ms) {
    return new Promise(
        function(resolve, reject) {
            setTimeout(function() {
                resolve();
            }, ms)
        });
}
wait(3000).then(f1)
Promise.all([wait(3000), wait(4000)]).then(f1)

源码

/**
 * 使用如下结构来进行变量隐藏,防止暴露变量
 * var clone=(function(){
 *  function f(){
 *  }  
 *  return f
 * })()
 */
var clone = (function() {
    'use strict';

    //工具函数:判断obj是否是type类型
    function _instanceof(obj, type) {
        return type != null && obj instanceof type;
    }

    /**
     * JS中有Map、Set等集合
     * Promise也是一种数据结构,也是可以复制的
     */
    var nativeMap;
    try {
        nativeMap = Map;
    } catch (_) {
        // maybe a reference error because no `Map`. Give it a dummy value that no
        // value will ever be an instanceof.
        nativeMap = function() {};
    }

    var nativeSet;
    try {
        nativeSet = Set;
    } catch (_) {
        nativeSet = function() {};
    }

    var nativePromise;
    try {
        nativePromise = Promise;
    } catch (_) {
        nativePromise = function() {};
    }

    /**
     * Clones (copies) an Object using deep copying.
     *
     * This function supports circular references by default, but if you are certain
     * there are no circular references in your object, you can save some CPU time
     * by calling clone(obj, false).
     *
     * Caution: if `circular` is false and `parent` contains circular references,
     * your program may enter an infinite loop and crash.
     * 注意:如果禁用circular选项,那么出现循环包含的地方程序就会进入子循环。
     *
     * @param `parent` - the object to be cloned
     * @param `circular` - set to true if the object to be cloned may contain
     *    circular references. (optional - true by default)
     * @param `depth` - set to a number if the object is only to be cloned to
     *    a particular depth. (optional - defaults to Infinity)
     * @param `prototype` - sets the prototype to be used when cloning an object.
     *    (optional - defaults to parent prototype).
     * @param `includeNonEnumerable` - set to true if the non-enumerable properties
     *    should be cloned as well. Non-enumerable properties on the prototype
     *    chain will be ignored. (optional - false by default)
     */
    //定义了工具函数、一些变量之后,大boss终于登场了
    function clone(parent, circular, depth, prototype, includeNonEnumerable) {
        //支持第二种传参方式:把参数打包成json传到clone的第二个参数  
        if (typeof circular === 'object') {
            depth = circular.depth;
            prototype = circular.prototype;
            includeNonEnumerable = circular.includeNonEnumerable;
            circular = circular.circular;
        }
        // maintain two arrays for circular references, where corresponding parents
        // and children have the same index
        var allParents = [];
        var allChildren = [];

        var useBuffer = typeof Buffer != 'undefined';

        if (typeof circular == 'undefined') //默认行为:执行循环
            circular = true;

        if (typeof depth == 'undefined') //默认行为:有一定深度
            depth = Infinity;

        // recurse this function so we don't reset allParents and allChildren
        //克隆的过程是一个递归的过程,上面的全局变量allParents和allChildren就是为这个函数服务的
        function _clone(parent, depth) {
            // cloning null always returns null
            if (parent === null)
                return null;

            if (depth === 0)
                return parent;

            var child;
            var proto;
            if (typeof parent != 'object') {
                return parent;
            }

            if (_instanceof(parent, nativeMap)) {
                child = new nativeMap();
            } else if (_instanceof(parent, nativeSet)) {
                child = new nativeSet();
            } else if (_instanceof(parent, nativePromise)) {
                child = new nativePromise(function(resolve, reject) {
                    parent.then(function(value) {
                        resolve(_clone(value, depth - 1));
                    }, function(err) {
                        reject(_clone(err, depth - 1));
                    });
                });
            } else if (clone.__isArray(parent)) {
                child = [];
            } else if (clone.__isRegExp(parent)) {
                child = new RegExp(parent.source, __getRegExpFlags(parent));
                if (parent.lastIndex) child.lastIndex = parent.lastIndex;
            } else if (clone.__isDate(parent)) {
                child = new Date(parent.getTime());
            } else if (useBuffer && Buffer.isBuffer(parent)) {
                child = new Buffer(parent.length);
                parent.copy(child);
                return child;
            } else if (_instanceof(parent, Error)) {
                child = Object.create(parent);
            } else {
                if (typeof prototype == 'undefined') {
                    proto = Object.getPrototypeOf(parent);
                    child = Object.create(proto);
                } else {
                    child = Object.create(prototype);
                    proto = prototype;
                }
            }

            if (circular) {
                var index = allParents.indexOf(parent);

                if (index != -1) {
                    return allChildren[index];
                }
                allParents.push(parent);
                allChildren.push(child);
            }

            if (_instanceof(parent, nativeMap)) {
                parent.forEach(function(value, key) {
                    var keyChild = _clone(key, depth - 1);
                    var valueChild = _clone(value, depth - 1);
                    child.set(keyChild, valueChild);
                });
            }
            if (_instanceof(parent, nativeSet)) {
                parent.forEach(function(value) {
                    var entryChild = _clone(value, depth - 1);
                    child.add(entryChild);
                });
            }

            for (var i in parent) {
                var attrs;
                if (proto) {
                    attrs = Object.getOwnPropertyDescriptor(proto, i);
                }

                if (attrs && attrs.set == null) {
                    continue;
                }
                child[i] = _clone(parent[i], depth - 1);
            }

            if (Object.getOwnPropertySymbols) {
                var symbols = Object.getOwnPropertySymbols(parent);
                for (var i = 0; i < symbols.length; i++) {
                    // Don't need to worry about cloning a symbol because it is a primitive,
                    // like a number or string.
                    var symbol = symbols[i];
                    var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
                    if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
                        continue;
                    }
                    child[symbol] = _clone(parent[symbol], depth - 1);
                    if (!descriptor.enumerable) {
                        Object.defineProperty(child, symbol, {
                            enumerable: false
                        });
                    }
                }
            }

            if (includeNonEnumerable) {
                var allPropertyNames = Object.getOwnPropertyNames(parent);
                for (var i = 0; i < allPropertyNames.length; i++) {
                    var propertyName = allPropertyNames[i];
                    var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
                    if (descriptor && descriptor.enumerable) {
                        continue;
                    }
                    child[propertyName] = _clone(parent[propertyName], depth - 1);
                    Object.defineProperty(child, propertyName, {
                        enumerable: false
                    });
                }
            }

            return child;
        }

        return _clone(parent, depth);
    }

    /**
     * Simple flat clone using prototype, accepts only objects, usefull for property
     * override on FLAT configuration object (no nested props).
     *
     * USE WITH CAUTION! This may not behave as you wish if you do not know how this
     * works.
     */
    clone.clonePrototype = function clonePrototype(parent) {
       if (parent === null)
            return null;

        var c = function() {};
        c.prototype = parent;
        return new c();
    };

    // private utility functions

    function __objToStr(o) {
        return Object.prototype.toString.call(o);
    }
    clone.__objToStr = __objToStr;

    function __isDate(o) {
        return typeof o === 'object' && __objToStr(o) === '[object Date]';
    }
    clone.__isDate = __isDate;

    function __isArray(o) {
        return typeof o === 'object' && __objToStr(o) === '[object Array]';
    }
    clone.__isArray = __isArray;

    function __isRegExp(o) {
        return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
    }
    clone.__isRegExp = __isRegExp;