如何在javascript中进行深度克隆

如何在javascript中进行深度克隆,javascript,Javascript,如何深度克隆JavaScript对象 我知道有很多基于框架的函数,比如JSON.parse(JSON.stringify(o))和$.extend(true,{},o),但我不想使用这样的框架 创建深度克隆的最优雅或最有效的方法是什么 我们确实关心像克隆阵列这样的边缘情况。不打破原型链,处理自我参照 我们不关心是否支持复制DOM对象之类的东西,因为.cloneNode就是出于这个原因而存在的 因为我主要想在node.js中使用深度克隆,所以使用V8引擎的ES5功能是可以接受的 [编辑] 在任何人

如何深度克隆JavaScript对象

我知道有很多基于框架的函数,比如
JSON.parse(JSON.stringify(o))
$.extend(true,{},o)
,但我不想使用这样的框架

创建深度克隆的最优雅或最有效的方法是什么

我们确实关心像克隆阵列这样的边缘情况。不打破原型链,处理自我参照

我们不关心是否支持复制DOM对象之类的东西,因为
.cloneNode
就是出于这个原因而存在的

因为我主要想在
node.js
中使用深度克隆,所以使用V8引擎的ES5功能是可以接受的

[编辑]

在任何人提出建议之前,让我先提一下,通过原型继承对象创建副本和克隆对象之间有着明显的区别。前者将原型链弄得一团糟

[进一步编辑]

在阅读了你的答案后,我发现克隆整个对象是一个非常危险和困难的游戏。以以下基于闭包的对象为例

var o = (function() {
     var magic = 42;

     var magicContainer = function() {
          this.get = function() { return magic; };
          this.set = function(i) { magic = i; };
     }

      return new magicContainer;
}());

var n = clone(o); // how to implement clone to support closures
是否有任何方法可以编写克隆对象的克隆函数,该函数在克隆时具有相同的状态,但如果不在JS中编写JS解析器,则无法更改
o
的状态


现实世界中应该不再需要这样的函数了。这只是学术兴趣。

这取决于你想克隆什么。这是一个真正的JSON对象还是JavaScript中的任何对象?如果你想做任何克隆,它可能会给你带来一些麻烦。哪个麻烦?我将在下面解释它,但首先是一个克隆对象文本、任何原语、数组和DOM节点的代码示例

function clone(item) {
    if (!item) { return item; } // null, undefined values check

    var types = [ Number, String, Boolean ], 
        result;

    // normalizing primitives if someone did new String('aaa'), or new Number('444');
    types.forEach(function(type) {
        if (item instanceof type) {
            result = type( item );
        }
    });

    if (typeof result == "undefined") {
        if (Object.prototype.toString.call( item ) === "[object Array]") {
            result = [];
            item.forEach(function(child, index, array) { 
                result[index] = clone( child );
            });
        } else if (typeof item == "object") {
            // testing that this is DOM
            if (item.nodeType && typeof item.cloneNode == "function") {
                result = item.cloneNode( true );    
            } else if (!item.prototype) { // check that this is a literal
                if (item instanceof Date) {
                    result = new Date(item);
                } else {
                    // it is an object literal
                    result = {};
                    for (var i in item) {
                        result[i] = clone( item[i] );
                    }
                }
            } else {
                // depending what you would like here,
                // just keep the reference, or create new object
                if (false && item.constructor) {
                    // would not advice to do that, reason? Read below
                    result = new item.constructor();
                } else {
                    result = item;
                }
            }
        } else {
            result = item;
        }
    }

    return result;
}

var copy = clone({
    one : {
        'one-one' : new String("hello"),
        'one-two' : [
            "one", "two", true, "four"
        ]
    },
    two : document.createElement("div"),
    three : [
        {
            name : "three-one",
            number : new Number("100"),
            obj : new function() {
                this.name = "Object test";
            }   
        }
    ]
})
现在,让我们谈谈当你开始克隆真实对象时可能遇到的问题。我现在说的是,你通过这样做来创建的对象

var User = function(){}
var newuser = new User();
当然,您可以克隆它们,这不是问题,每个对象都公开构造函数属性,您可以使用它来克隆对象,但它并不总是有效的。您也可以对该对象执行简单的for in,但它的方向是相同的—麻烦。我也在代码中包含了克隆功能,但是
if(false)
语句排除了克隆功能

那么,为什么克隆会是一种痛苦呢?首先,每个对象/实例可能都有一些状态。例如,您永远无法确定您的对象没有私有变量,如果是这种情况,通过克隆对象,您只需破坏状态

想象没有国家,那很好。那我们还有另一个问题。通过“构造函数”方法克隆将给我们带来另一个障碍。这是一个参数依赖项。你永远不能确定,创造这个物体的人,没有,某种程度上

new User({
   bike : someBikeInstance
});
如果是这种情况,您就走运了,someBikeInstance可能是在某个上下文中创建的,而该上下文对于clone方法来说是未知的

那怎么办呢?您仍然可以在解决方案中执行for,并像对待普通对象文字一样对待此类对象,但可能根本不克隆此类对象,而只传递此对象的引用是一个好主意

另一个解决方案是——您可以设置一个约定,所有必须克隆的对象都应该自己实现这一部分,并提供适当的API方法(如cloneObject)。cloneNode对DOM所做的事情


你自己决定。

正如其他人在这个问题和类似问题上所指出的那样,克隆一个“对象”,在一般意义上,在JavaScript中是可疑的

然而,有一类对象,我称之为“数据”对象,也就是那些仅从
{…}
文本和/或简单属性赋值构造的对象,或者从JSON反序列化的对象,对于这些对象,克隆是合理的。就在今天,我想将从服务器接收到的数据人为膨胀5倍,以测试大型数据集会发生什么,但对象(数组)及其子对象必须是不同的对象才能正常工作。克隆允许我这样做,以增加我的数据集:

return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));
我经常克隆数据对象的另一个地方是将数据提交回主机,在发送数据之前,我希望从数据模型中的对象中删除状态字段。例如,我可能希望在克隆对象时从对象中删除所有以“u”开头的字段

这是我最后编写的一般性代码,包括支持数组和选择要克隆哪些成员的选择器(使用“路径”字符串确定上下文):

最简单合理的深度克隆解决方案(假设根对象为非空且无成员选择)是:

function clone(src) {
    var ret=(src instanceof Array ? [] : {});
    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }
        var val=src[key];
        if(val && typeof(val)=='object') { val=clone(val);  }
        ret[key]=val;
        }
    return ret;
    }

非常简单的方法,可能太简单了:

var cloned = JSON.parse(JSON.stringify(objectToClone));
该库有一个名为deep克隆对象的函数

源代码中的代码段:

snapshot: function(obj) {
  if(obj == null || typeof(obj) != 'object') {
    return obj;
  }

  var temp = new obj.constructor();

  for(var key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = _.snapshot(obj[key]);
    }
  }

  return temp;
}
一旦库链接到您的项目,只需使用

_.snapshot(object);
深度复制Javascript对象的
JSON.parse(JSON.stringify())
组合是一种无效的攻击,因为它是针对JSON数据的。它不支持
undefined
function(){}
的值,并且在将Javascript对象“字符串化”(编组)为JSON时将忽略它们(或
null
它们)

更好的解决方案是使用深度复制功能。下面的函数复制对象,不需要第三方库(jQuery、LoDash等)


我注意到Map应该需要特殊处理,因此在这个线程中的所有建议中,代码将是:

function deepClone( obj ) {
    if( !obj || true == obj ) //this also handles boolean as true and false
        return obj;
    var objType = typeof( obj );
    if( "number" == objType || "string" == objType ) // add your immutables here
        return obj;
    var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
    if( obj instanceof Map )
        for( var key of obj.keys() )
            result.set( key, deepClone( obj.get( key ) ) );
    for( var key in obj )
        if( obj.hasOwnProperty( key ) )
            result[key] = deepClone( obj[ key ] );
    return result;
}

以下是一个ES6函数,它也适用于具有循环引用的对象:

函数deepClone(obj,hash=new WeakMap()){
if(Object(obj)!==obj)返回obj;//原语
if(hash.has(obj))返回hash.get(obj);//循环引用
const result=obj instanceof Set?new Set(obj)//请参见此注释!
:obj instanceof Map?新映射(数组)从(obj([
function copy(aObject) {
  if (!aObject) {
    return aObject;
  }

  let v;
  let bObject = Array.isArray(aObject) ? [] : {};
  for (const k in aObject) {
    v = aObject[k];
    bObject[k] = (typeof v === "object") ? copy(v) : v;
  }

  return bObject;
}
function deepClone( obj ) {
    if( !obj || true == obj ) //this also handles boolean as true and false
        return obj;
    var objType = typeof( obj );
    if( "number" == objType || "string" == objType ) // add your immutables here
        return obj;
    var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
    if( obj instanceof Map )
        for( var key of obj.keys() )
            result.set( key, deepClone( obj.get( key ) ) );
    for( var key in obj )
        if( obj.hasOwnProperty( key ) )
            result[key] = deepClone( obj[ key ] );
    return result;
}
function deepClone (obj) {
    var _out = new obj.constructor;

    var getType = function (n) {
        return Object.prototype.toString.call(n).slice(8, -1);
    }

    for (var _key in obj) {
        if (obj.hasOwnProperty(_key)) {
            _out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
        }
    }
    return _out;
}
const deepClone = (objOrArray) => {

  const copyArray = (arr) => {
    let arrayResult = [];
    arr.forEach(el => {
        arrayResult.push(cloneObjOrArray(el));
    });
    return arrayResult;
  }

  const copyObj = (obj) => {
    let objResult = {};
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        objResult[key] = cloneObjOrArray(obj[key]);
      }
    }
    return objResult;
  }

  const cloneObjOrArray = (el) => {
    if (Array.isArray(el)) {
      return copyArray(el);
    } else if (typeof el === 'object') {
      return copyObj(el);
    } else {
      return el;
    }
  }

  return cloneObjOrArray(objOrArray);
}
function deepClone(obj) {
         var retObj;
        _assignProps = function(obj, keyIndex, retObj) {
               var subType = Object.prototype.toString.call(obj[keyIndex]);
               if(subType === "[object Object]" || subType === "[object Array]") {
                    retObj[keyIndex] = deepClone(obj[keyIndex]);
               }
               else {
                     retObj[keyIndex] = obj[keyIndex];
               }
        };

        if(Object.prototype.toString.call(obj) === "[object Object]") {
           retObj = {};
           for(key in obj) {
               this._assignProps(obj, key, retObj);
           }
        }
        else if(Object.prototype.toString.call(obj) == "[object Array]") {
           retObj = [];
           for(var i = 0; i< obj.length; i++) {
              this._assignProps(obj, i, retObj);
            }
        };

        return retObj;
    };
import { fromJS } from 'immutable';

// An object we want to clone
let objA = { 
   a: { deep: 'value1', moreDeep: {key: 'value2'} } 
};

let immB = fromJS(objA); // Create immutable Map
let objB = immB.toJS(); // Convert to plain JS object

console.log(objA); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
console.log(objB); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }

// objA and objB are equalent, but now they and their inner objects are undependent
console.log(objA === objB); // false
console.log(objA.a === objB.a); // false
console.log(objA.moreDeep === objB.moreDeep); // false
import merge from 'lodash/merge'

var objA = {
    a: [{ 'b': 2 }, { 'd': 4 }]
};
// New deeply cloned object:
merge({}, objA ); 

// We can also create new object from several objects by deep merge:
var objB = {
    a: [{ 'c': 3 }, { 'e': 5 }]
};
merge({}, objA , objB ); // Object { a: [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
 //a test-object with circular reference :
 var n1 = {   id:0,   text:"aaaaa",   parent:undefined} 
 var n2 = {  id:1,   text:"zzzzz",   parent:undefined } 
 var o = { arr:[n1,n2],   parent:undefined } 
 n1.parent = n2.parent = o;
 var obj = {   a:1,   b:2,   o:o }
 o.parent = obj;

 function deepClone(o,output){ 

     if(!output) output = {};  
     if(o.______clone) return o.______clone;
     o.______clone = output.______clone = output;

   for(var z in o){

     var obj = o[z];
     if(typeof(obj) == "object") output[z] = deepClone(obj)
     else output[z] = obj; 
    }

   return output;
}

console.log(deepClone(obj));
function deepCopy(obj){
    if (!obj || typeof obj !== "object") return obj;

    var retObj = {};

    for (var attr in obj){
        var type = obj[attr];

        switch(true){
            case (type instanceof Date):
                var _d = new Date();
                _d.setDate(type.getDate())
                retObj[attr]= _d;
                break;

            case (type instanceof Function):
                retObj[attr]= obj[attr];
                break;

            case (type instanceof Array):
                var _a =[];
                for (var e of type){
                    //_a.push(e);
                    _a.push(deepCopy(e));
                }
                retObj[attr]= _a;
                break;

            case (type instanceof Object):
                var _o ={};
                for (var e in type){
                    //_o[e] = type[e];
                    _o[e] = deepCopy(type[e]);
                }
                retObj[attr]= _o;
                break;

            default:
                retObj[attr]= obj[attr];
        }
    }
    return retObj;
}

var obj = {
    string: 'test',
    array: ['1'],
    date: new Date(),
    object:{c: 2, d:{e: 3}},
    function: function(){
        return this.date;
    }
};

var copyObj = deepCopy(obj);

console.log('object comparison', copyObj === obj); //false
console.log('string check', copyObj.string === obj.string); //true
console.log('array check', copyObj.array === obj.array); //false
console.log('date check', copyObj2.date === obj.date); //false
console.log('object check', copyObj.object === obj.object); //false
console.log('function check', copyObj.function() === obj.function()); //true
function shallowClone(target) {
  if (typeof a == 'array') return [...target]
  if (typeof a == 'object') return {...target}
  return target
}

/* set skipRecursion to avoid throwing an exception on recursive references */
/* no need to specify refs, or path -- they are used interally */
function deepClone(target, skipRecursion, refs, path) {
  if (!refs) refs = []
  if (!path) path = ''
  if (refs.indexOf(target) > -1) {
    if (skipRecursion) return null
    throw('Recursive reference at ' + path)
  }
  refs.push(target)
  let clone = shallowCopy(target)
  for (i in target) target[i] = deepClone(target, refs, path + '.' + i)
  return clone
}
let cloned = JSON.parse(JSON.stringify(objectToClone));
const myObj = [undefined, null, function () {}, {}, '', true, false, 0, Symbol];

const IsDeepClone = JSON.parse(JSON.stringify(myObj));

console.log(IsDeepClone); //[null, null, null, {…}, "", true, false, 0, null]