Резюме глубокого копирования JS

JavaScript опрос
JS изначально не поддерживает глубокое копирование,Object.assignа также{...obj}Все относится к мелкому копированию, давайте объясним, как использовать JS для достижения глубокого копирования.

JSON.sringify и JSON.parse

Это самый простой способ для JS реализовать глубокое копирование, принцип заключается в том, чтобы сначала преобразовать объект в строку, а затем заново создать объект через JSON.parse. Но этот подход имеет много ограничений:

  • Невозможно скопировать функцию, обычную, символ
  • ошибка циклической ссылки
  • Одна и та же ссылка будет многократно скопирована

Давайте рассмотрим эти три пункта по очереди, давайте протестируем этот код:

let obj = {         
    reg : /^asd$/,
    fun: function(){},
    syb:Symbol('foo'),
    asd:'asd'
}; 
let cp = JSON.parse(JSON.stringify(obj));
console.log(cp);

результат:


Видно, что функции, регуляры и символы копируются некорректно.

если вJSON.stringifyЕсли передан объект циклической ссылки, об ошибке будет сообщено напрямую:


Прежде чем говорить о третьем пункте, давайте посмотрим на этот код:

let obj = {  asd:'asd' }; 
let obj2 = {name:'aaaaa'};
obj.ttt1 = obj2;
obj.ttt2 = obj2;
let cp = JSON.parse(JSON.stringify(obj)); 
obj.ttt1.name = 'change'; 
cp.ttt1.name  = 'change';
console.log(obj,cp);

в исходном объектеobj серединаttt1а такжеttt2указывает на один и тот же объектobj2, то при глубоком копировании я должен копировать только один разobj2 , давайте посмотрим на текущие результаты:


Мы можем видеть (исходный объект выше, скопированный объект ниже), что изменение ttt1.name исходного объекта также изменит ttt2.name , потому что они указывают на один и тот же объект.

Однако в скопированном объекте ttt1 и ttt2 соответственно указывают на два объекта. Скопированный объект не поддерживает ту же структуру, что и исходный объект. следовательно,Реализация глубокого копирования JSON не может обрабатывать случай указания на одну и ту же ссылку, и одна и та же ссылка будет копироваться повторно.

рекурсивная реализация

Нативный метод JS не может хорошо реализовать глубокое копирование, поэтому мы реализуем его.

Идея очень проста: для простых типов просто скопируйте. Для ссылочных типов рекурсивно скопируйте каждое из его свойств.

Проблемы, которые нам необходимо решить:

  • циклическая ссылка
  • та же ссылка
  • Разные типы (я реализовал только различие между массивами и объектами)

Код реализации:

function deepCopy(target){ 
let copyed_objs = [];//此数组解决了循环引用和相同引用的问题,它存放已经递归到的目标对象 
    function _deepCopy(target){ 
        if((typeof target !== 'object')||!target){return target;}
        for(let i= 0 ;i<copyed_objs.length;i++){
            if(copyed_objs[i].target === target){
                return copyed_objs[i].copyTarget;
            }
        }
        let obj = {};
        if(Array.isArray(target)){
            obj = [];//处理target是数组的情况 
        }
        copyed_objs.push({target:target,copyTarget:obj}) 
        Object.keys(target).forEach(key=>{ 
            if(obj[key]){ return;} 
            obj[key] = _deepCopy(target[key]);
        }); 
        return obj;
    } 
    return _deepCopy(target);
}

copyed_objsВ этом массиве хранятся рекурсивные целевые объекты. Перед рекурсией на целевом объекте мы должны проверить этот массив, если текущий целевой объект иcopyed_objs равен объекту в , то не рекурсивно на нем.

Это решает проблему циклических ссылок и идентичных ссылок.

Протестируйте код:

var a = {
    arr:[1,2,3,{key:'123'}],//数组测试
};
a.self = a;//循环引用测试
a.common1 = {name:'ccc'};
a.common2 = a.common1;//相同引用测试
var c = deepCopy(a);
c.common1.name = 'changed';
console.log(c);

результат:


Как видно, указанные выше проблемы решены.

Наконец добавьте:

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