Перед глубоким копированием и поверхностным копированием рассмотрим два простых случая:
//案例1
var num1 = 1, num2 = num1;
console.log(num1) //1
console.log(num2) //1
num2 = 2; //修改num2
console.log(num1) //1
console.log(num2) //2
//案例2
var obj1 = {x: 1, y: 2}, obj2 = obj1;
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}
obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 2, y: 2}
console.log(obj2) //{x: 2, y: 2}
Согласно общепринятому мышлению,obj1
должен иnum1
То же самое, оно не изменится из-за изменения другого значения, и здесьobj1
но сobj2
изменено изменением. Одна и та же переменная, почему она ведет себя по-разному? Это должно быть введено в JSбазовый типа такжетип ссылкиконцепция.
Примитивные типы и ссылочные типы
Переменные ECMAScript могут содержать значения двух разных типов данных: значения примитивного типа и значения ссылочного типа. Примитивные значения — это те простые сегменты данных, которые хранятся в памяти стека, т. е. такие значения хранятся целиком в одном месте в памяти. Значение ссылочного типа относится к тем объектам, которые содержат память кучи, а это означает, что то, что хранится в переменной, на самом деле является просто указателем, и этот указатель указывает на другое место в памяти, которое содержит объект.
Например, разницу в присвоении базовых типов и эталонных типов можно понять в терминах «сетевой магазин» и «отдельный магазин»: присвоение базовых типов равносильно установке нормативного стандарта сетевого магазина на новом месте, открытие нового филиала и открытие нового магазина.Он не связан с другими старыми магазинами и работает независимо, а присвоение ссылочного типа эквивалентно наличию двух ключей в одном магазине, которые передаются двум начальникам для управления одновременно время, и поведение двух боссов может повлиять на работу магазина.
Определения и различия между базовыми типами и ссылочными типами ясно представлены выше. В настоящее времябазовый типЕсть: Boolean, Null, Undefined, Number, String, Symbol,тип ссылкиЕсть: Объект, Массив, Функция. Причина, по которой я говорю «в настоящее время», заключается в том, что Symbol появился только в ES6, и позже могут появиться новые типы.
Возвращаясь к предыдущему случаю, значение в случае 1 является примитивным типом, а значение в случае 2 является ссылочным типом. Присваивание в случае 2 — это типичная неглубокая копия, иКонцепции глубокого копирования и мелкого копирования существуют только для ссылочных типов..
глубокая копия против поверхностной копии
Теперь, когда вы знаете происхождение глубокого копирования и мелкого копирования, как реализовать глубокое копирование? Давайте сначала посмотрим, поддерживаются ли собственные методы Array и Object:
Array
var arr1 = [1, 2], arr2 = arr1.slice();
console.log(arr1); //[1, 2]
console.log(arr2); //[1, 2]
arr2[0] = 3; //修改arr2
console.log(arr1); //[1, 2]
console.log(arr2); //[3, 2]
В настоящее время,arr2
Модификация не коснуласьarr1
, кажется, реализовать глубокую копию не так уж и сложно. Давайте изменим arr1 на двумерный массив и посмотрим:
var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice();
console.log(arr1); //[1, 2, [3, 4]]
console.log(arr2); //[1, 2, [3, 4]]
arr2[2][1] = 5;
console.log(arr1); //[1, 2, [3, 5]]
console.log(arr2); //[1, 2, [3, 5]]
какие,arr2
снова изменилсяarr1
, похоже на тоslice() может реализовывать только глубокие копии одномерных массивов..
Есть также эквивалентные функции:concat,Array.from().
Object
- Object.assign()
var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}
obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 2, y: 2}
var obj1 = {
x: 1,
y: {
m: 1
}
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 2}}
console.log(obj2) //{x: 2, y: {m: 2}}
проверено,Object.assign() может реализовывать только глубокие копии одномерных объектов..
- JSON.parse(JSON.stringify(obj))
var obj1 = {
x: 1,
y: {
m: 1
}
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 2, y: {m: 2}}
JSON.parse(JSON.stringify(obj))
выглядит хорошо, ноДокументация MDNВ описании есть предложение, написанное очень четко:
undefined、
Произвольные функции и значения символов игнорируются при сериализации (при наличии в значениях свойств объектов, не являющихся массивами) или преобразуются вnull
(при наличии в массиве).
давайте положимobj1
В процессе трансформации:
var obj1 = {
x: 1,
y: undefined,
z: function add(z1, z2) {
return z1 + z2
},
a: Symbol("foo")
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(JSON.stringify(obj1)); //{"x":1}
console.log(obj2) //{x: 1}
обнаружил, что вobj1进行JSON.stringify()
В процессе сериализации значения y, z и a игнорируются, что подтверждает описание документа MDN. Если так, тоJSON.parse(JSON.stringify(obj))
использование также ограничено.Объекты с неопределенными значениями, функциями и символами не могут быть глубоко скопированы,ноJSON.parse(JSON.stringify(obj))
Простой и грубый, он соответствует 90% сценариев использования.
После проверки мы обнаружили, что собственные методы, предоставляемые JS, не могут полностью решить проблему глубокого копирования Array и Object. В жертву можно принести только большого убийцу:рекурсия
function deepCopy(obj) {
// 创建一个新对象
let result = {}
let keys = Object.keys(obj),
key = null,
temp = null;
for (let i = 0; i < keys.length; i++) {
key = keys[i];
temp = obj[key];
// 如果字段的值也是一个对象则递归操作
if (temp && typeof temp === 'object') {
result[key] = deepCopy(temp);
} else {
// 否则直接赋值给新对象
result[key] = temp;
}
}
return result;
}
var obj1 = {
x: {
m: 1
},
y: undefined,
z: function add(z1, z2) {
return z1 + z2
},
a: Symbol("foo")
};
var obj2 = deepCopy(obj1);
obj2.x.m = 2;
console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}
Как видите, рекурсия прекрасно решает все проблемы, оставшиеся до этого, а также мы можем использовать стороннюю библиотеку: jquery's$.extend
и ладаш_.cloneDeep
решить глубокую копию. Хотя вышеизложенное проверяется Объектом, оно применимо и к Массиву, поскольку Массив также является особым Объектом.
На этом проблема глубокого копирования может быть в основном решена. Однако есть и очень особенный сценарий:
Циркулярная копия ссылки
var obj1 = {
x: 1,
y: 2
};
obj1.z = obj1;
var obj2 = deepCopy(obj1);
В этот момент, если вы вызовете функцию deepCopy прямо сейчас, вы попадете в рекурсивный процесс циклов, что приведет к взрыву стека. jquery$.extend
И это не было решено. Решить эту проблему тоже очень просто, нужно только определить, относится ли поле объекта к этому объекту или к какому-либо родителю этого объекта. Модифицируем код:
function deepCopy(obj, parent = null) {
// 创建一个新对象
let result = {};
let keys = Object.keys(obj),
key = null,
temp= null,
_parent = parent;
// 该字段有父级则需要追溯该字段的父级
while (_parent) {
// 如果该字段引用了它的父级则为循环引用
if (_parent.originalParent === obj) {
// 循环引用直接返回同级的新对象
return _parent.currentParent;
}
_parent = _parent.parent;
}
for (let i = 0; i < keys.length; i++) {
key = keys[i];
temp= obj[key];
// 如果字段的值也是一个对象
if (temp && typeof temp=== 'object') {
// 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用
result[key] = DeepCopy(temp, {
originalParent: obj,
currentParent: result,
parent: parent
});
} else {
result[key] = temp;
}
}
return result;
}
var obj1 = {
x: 1,
y: 2
};
obj1.z = obj1;
var obj2 = deepCopy(obj1);
console.log(obj1); //太长了去浏览器试一下吧~
console.log(obj2); //太长了去浏览器试一下吧~
На данный момент завершена функция глубокого копирования, поддерживающая циклические ссылки. Конечно, вы также можете использовать lodash_.cloneDeep
О~.
Добро пожаловать в обсуждение, ставьте лайк и поехали~
Статьи синхронизируются со следующими сообществами, вы можете выбрать одно, чтобы подписаться на меня 。◕‿◕。
simbawu | github | segmentfault | Знай почти | короткая книга | Наггетс