我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。

如何正确克隆JavaScript对象?


当前回答

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};

其他回答

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

我认为有一个简单有效的答案。在深度复制中,有两个问题:

保持财产相互独立。并在克隆的对象上保持方法的有效性。

所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行赋值以复制函数。

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

虽然这个问题有很多答案,但我希望这个问题也能有所帮助。

Jan Turo的上述答案非常接近,由于兼容性问题,可能是在浏览器中使用的最佳选择,但这可能会导致一些奇怪的枚举问题。例如,执行:

for ( var i in someArray ) { ... }

在遍历数组元素后,将clone()方法赋给i。下面是一个避免枚举并适用于node.js的改编:

Object.defineProperty( Object.prototype, "clone", {
    value: function() {
        if ( this.cloneNode )
        {
            return this.cloneNode( true );
        }

        var copy = this instanceof Array ? [] : {};
        for( var attr in this )
        {
            if ( typeof this[ attr ] == "function" || this[ attr ] == null || !this[ attr ].clone )
            {
                copy[ attr ] = this[ attr ];
            }
            else if ( this[ attr ] == this )
            {
                copy[ attr ] = copy;
            }
            else
            {
                copy[ attr ] = this[ attr ].clone();
            }
        }
        return copy;
    }
});

Object.defineProperty( Date.prototype, "clone", {
    value: function() {
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    }
});

Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

这避免了使clone()方法可枚举,因为defineProperty()默认为false。

//
// creates 'clone' method on context object
//
//  var 
//     clon = Object.clone( anyValue );
//
!((function (propertyName, definition) {
    this[propertyName] = definition();
}).call(
    Object,
    "clone",
    function () {
        function isfn(fn) {
            return typeof fn === "function";
        }

        function isobj(o) {
            return o === Object(o);
        }

        function isarray(o) {
            return Object.prototype.toString.call(o) === "[object Array]";
        }

        function fnclon(fn) {
            return function () {
                fn.apply(this, arguments);
            };
        }

        function owns(obj, p) {
            return obj.hasOwnProperty(p);
        }

        function isemptyobj(obj) {
            for (var p in obj) {
                return false;
            }
            return true;
        }

        function isObject(o) {
            return Object.prototype.toString.call(o) === "[object Object]";
        }
        return function (input) {
            if (isfn(input)) {
                return fnclon(input);
            } else if (isobj(input)) {
                var cloned = {};
                for (var p in input) {
                    owns(Object.prototype, p)
                    || (
                        isfn(input[p])
                        && ( cloned[p] = function () { return input[p].apply(input, arguments); } )
                        || ( cloned[p] = input[p] )
                    );
                }
                if (isarray(input)) {
                    cloned.length = input.length;
                    "concat every filter forEach indexOf join lastIndexOf map pop push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift"
                    .split(" ")
                    .forEach(
                      function (methodName) {
                        isfn( Array.prototype[methodName] )
                        && (
                            cloned[methodName] =
                            function () {
                                return Array.prototype[methodName].apply(cloned, arguments);
                            }
                        );
                      }
                    );
                }
                return isemptyobj(cloned)
                       ? (
                          isObject(input)
                          ? cloned
                          : input
                        )
                       : cloned;
            } else {
                return input;
            }
        };
    }
));
//

如果您有一个带有函数的对象,可以使用JSONfn来实现,请参见http://www.eslinstructor.net/jsonfn/.

var obj= {
    name:'Marvin',
    getName :  function(){
      return this.name;
    }
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));