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;

    //获取正则表达式的开关
    function __getRegExpFlags(re) {
        var flags = '';
        if (re.global) flags += 'g';
        if (re.ignoreCase) flags += 'i';
        if (re.multiline) flags += 'm';
        return flags;
    }
    clone.__getRegExpFlags = __getRegExpFlags;

    return clone;
})();

//使用如下结构进行判断,如果module变量存在,那么说明是node;
//如果module变量不存在,说明是JS,无需进行module.exports
if (typeof module === 'object' && module.exports) {
    module.exports = clone;
}

例子

var clone = require('clone');

var a, b;

a = { foo: { bar: 'baz' } };  // initial value of a

b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';            // change a

console.log(a);               // show a
console.log(b);               // show b

This will print:

{ foo: { bar: 'foo' } }
{ foo: { bar: 'baz' } }

API

clone(val, circular, depth)

*val -- the value that you want to clone, any type allowed

*circular -- boolean

Call clone with circular set to false if you are certain that obj contains no circular references. This will give better performance if needed. There is no error if undefined or null is passed as obj.

*depth -- depth to which the object is to be cloned (optional, defaults to infinity)

*prototype -- sets the prototype to be used when cloning an object. (optional, defaults to parent prototype).

*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, defaults to false)

clone.clonePrototype(obj)

*obj -- the object that you want to clone

循环引用问题

var a, b;

a = { hello: 'world' };

a.myself = a;
b = clone(a);

console.log(b);

This will print:

{ hello: "world", myself: [Circular] }
So, b.myself points to b, not a. Neat!
原文地址:https://www.cnblogs.com/weiyinfu/p/9929899.html