Как глубоко клонировать в javascript



как вы глубоко клонировать объект Javascript?



Я знаю, что существуют различные функции, основанные на фреймворках, таких как JSON.parse(JSON.stringify(o)) и $.extend(true, {}, o) но я не хочу использовать фреймворк такой.



каков самый элегантный или эффективный способ создания глубокого клона.



мы заботимся о пограничных случаях, как клонирование массива. Не нарушая цепочек прототипов, работа с собственной ссылкой.



мы не заботимся о поддержке копирования объектов DOM и подобных потому что .cloneNode существует по этой причине.



как я в основном хочу использовать глубокие клоны в node.js использование ES5 функций двигателя V8 является приемлемым.



[Edit]



прежде чем кто-либо предложит, позвольте мне упомянуть, что существует явная разница между созданием копии путем прототипического наследования от объекта и клонирование его. Первый делает беспорядок из прототипа цепи.



[Изменить]



после прочтения вашего ответа я дошло до досадного открытия, что клонирование целых объектов-это очень опасная и сложная игра. Возьмем для примера следующий объект на основе закрытия



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


есть ли способ написать функцию клонирования, которая клонирует объект, имеет то же состояние во время клонирования, но не может изменить состояние o без написания парсера JS в JS.



не должно быть никакой реальной потребности мира для такой функции больше. Это чисто академический интерес.

304   13  

13 ответов:

это действительно зависит от того, что вы хотите клонировать. Это действительно объект 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 in решение, и относиться к таким объектам, как обычные объектные литералы, но, может быть, это идея не клонировать такие объекты вообще, а просто передать ссылку на этот объект?

другое решение - вы можете установить соглашение о том, что все объекты, которые должны быть клонированы, должны реализовать эту часть сами по себе и предоставить соответствующий метод API (например, cloneObject ). Что-то какое cloneNode делает для дома.

решать Вам.

очень простой способ, может быть, слишком просто:

var cloned = JSON.parse(JSON.stringify(objectToClone));

The JSON.parse(JSON.stringify()) комбинация для глубокого копирования объектов Javascript является неэффективным взломом, потому что JSON не поддерживает значения undefined и function () {}, и поэтому JSON.stringify будет игнорировать эти разделы кода, когда "stringifying" (маршалинг) объект Javascript в JSON.

следующая функция будет глубоко копировать объекты и не требует сторонней библиотеки (jQuery, LoDash и т. д.).

function copy(aObject) {
  if (!aObject) {
    return aObject;
  }

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

The подчеркивания.JS-библиотека ВНО библиотека имеет функцию под названием снимок что глубоко клонирует объект

фрагмент из источника:

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);

вот функция ES6, которая также будет работать для объектов с циклическими ссылками:

function deepClone(obj, hash = new WeakMap()) {
    if (Object(obj) !== obj) return obj; // primitives
    if (hash.has(obj)) return hash.get(obj); // cyclic reference
    const result = obj instanceof Date ? new Date(obj)
                 : obj instanceof RegExp ? new RegExp(obj.source, obj.flags)
                 : obj.constructor ? new obj.constructor() 
                 : Object.create(null);
    hash.set(obj, result);
    if (obj instanceof Map)
        Array.from(obj, ([key, val]) => result.set(key, deepClone(val, hash)) );
    return Object.assign(result, ...Object.keys(obj).map (
        key => ({ [key]: deepClone(obj[key], hash) }) ));
}

// Sample data
var p = {
  data: 1,
  children: [{
    data: 2,
    parent: null
  }]
};
p.children[0].parent = p;

var q = deepClone(p);

console.log(q.children[0].parent.data); // 1

Это метод глубокого клонирования, который я использую, я думаю, что это Отлично, надеюсь, вы сделаете предложения

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;
}

как отмечали другие по этому и подобным вопросам, клонирование "объекта", в общем смысле, сомнительно в JavaScript.

однако, есть класс объектов, которые я называю" данные " объекты, то есть те, которые построены просто из { ... } литералы и / или простые назначения свойств или десериализованные из JSON, для которых разумно клонировать. Только сегодня я хотел искусственно раздуть данные, полученные с сервера в 5 раз, чтобы проверить, что происходит для большого набора данных, но объект (массив) и его дочерние элементы должны были быть различными объектами, чтобы вещи функционировали правильно. Клонирование позволило мне сделать это, чтобы умножить мои данные:

return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));

другое место, где я часто клонирую объекты данных, - это отправка данных обратно на Хост, где я хочу удалить поля состояния из объекта в модели данных перед его отправкой. Например, я могу захотеть удалить все поля, начинающиеся с"_", из объекта по мере его клонирования.

это код, который я в конечном итоге писать, чтобы сделать это в общем виде, включая поддержку массивов и селектор, чтобы выбрать, какие члены клонировать (который использует строку "путь" для определения контекста):

function clone(obj,sel) {
    return (obj ? _clone("",obj,sel) : obj);
    }

function _clone(pth,src,sel) {
    var ret=(src instanceof Array ? [] : {});

    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }

        var val=src[key], sub;

        if(sel) {
            sub+=pth+"/"+key;
            if(!sel(sub,key,val)) { continue; }
            }

        if(val && typeof(val)=='object') {
            if     (val instanceof Boolean) { val=Boolean(val);        }
            else if(val instanceof Number ) { val=Number (val);        }
            else if(val instanceof String ) { val=String (val);        }
            else                            { val=_clone(sub,val,sel); }
            }
        ret[key]=val;
        }
    return ret;
    }

самое простое разумное решение глубокого клонирования, предполагающее ненулевой корневой объект и без выбора члена:

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;
    }

Lo-Dash, теперь надмножество подчеркивания.js, имеет несколько глубоких функций клонирования:

С ответ автора:

lodash underscore сборка предоставляется для обеспечения совместимости с последней стабильной версией подчеркивания.

Я заметил, что карта должна требовать специального обращения, поэтому со всеми предложениями в этой теме код будет:

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;
}

это работает для массивов, объектов и примитивов. Дважды рекурсивный алгоритм, который переключается между двумя методами обхода:

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);
}

мы можем использовать рекурсию для создания deepCopy. Он может создать копию массива, объекта, массива объекта, объекта с функцией. если вы хотите, вы можете добавить функцию для другой тип структуры данных, такие как карты и т. д.

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;
    };

в реальном мире больше не должно быть необходимости в такой функции. Это чисто академический интерес.

как чисто упражнение, это более функциональный способ сделать это. Это расширение @tfmontague ответ как Я бы предложил добавление защитного блока там. Но, видя, как я чувствую себя вынужденным ES6 и функционализировать все вещи, вот моя сутенерская версия. Это усложняет логику, так как вам нужно сопоставить массив и уменьшить над объектом, но он избегает любых мутаций.

function cloner(x) {
    const recurseObj = x => typeof x === 'object' ? cloner(x) : x
    const cloneObj = (y, k) => {
        y[k] = recurseObj(x[k])
        return y
    }
    // Guard blocks
    // Add extra for Date / RegExp if you want
    if (!x) {
        return x
    }
    if (Array.isArray(x)) {
        return x.map(recurseObj)
    }
    return Object.keys(x).reduce(cloneObj, {})
}
const tests = [
    null,
    [],
    {},
    [1,2,3],
    [1,2,3, null],
    [1,2,3, null, {}],
    [new Date('2001-01-01')], // FAIL doesn't work with Date
    {x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]},
    {
        obj : new function() {
            this.name = "Object test";
        }
    } // FAIL doesn't handle functions
]
tests.map((x,i) => console.log(i, cloner(x)))

вот самое элегантное решение для глубокого копирования, которое я мог бы придумать без использования фреймворка (синтаксис ES6).

наслаждайтесь!

/**
* Deep copy an array or object
* @param {array|object} a
*/
const deepCopy = (a) => {
  if (Array.isArray(a)) return [...a];
  if (a !== null && typeof a === 'object') return Object.assign({}, a);
  return a;
}

Comments

    Ничего не найдено.