предисловие
Существуют разные способы копирования объектов в javascript, если вы не знакомы с языком, легко попасть в ловушку при копировании объектов, так как же мы можем правильно скопировать объект?
Надеюсь, после прочтения этой статьи вы поймете:
- Что такое глубокие/поверхностные копии и чем они отличаются от присваивания?
- Сколько существует способов реализовать глубокое/поверхностное копирование?
Поверхностная копия и глубокая копия
-
Неглубокая копия создает новый объект с точной копией значений свойств исходного объекта. Если свойство является базовым типом, копируется значение базового типа, а если свойство является ссылочным типом, копируется адрес памяти, поэтомуЕсли один из объектов изменит этот адрес, это повлияет на другой объект..
-
Глубокая копия — это полная копия объекта из памяти, при этом из динамической памяти открывается новая область для хранения нового объекта.Изменение нового объекта не влияет на исходный объект.
var a1 = {b: {c: {}};
var a2 = shallowClone(a1); // 浅拷贝方法
a2.b.c === a1.b.c // true 新旧对象还是共享同一块内存
var a3 = deepClone(a3); // 深拷贝方法
a3.b.c === a1.b.c // false 新对象跟原对象不共享内存
с помощьюБосс КонардЛиСледующие две картинки помогают нам лучше понять их значение:
Таким образом, только неглубокая копия относится к указателю объекта без копирования самого объекта.Старые и новые объекты по-прежнему используют одну и ту же память. Но глубокая копия создает другой идентичный объект,Новый объект не разделяет память с исходным объектом, изменение нового объекта не изменит исходный объект.
Разница между назначением и глубоким/поверхностным копированием
Различия между этими тремя заключаются в следующем, но предпосылка сравнения заключается в следующем.Для ссылочных типов:
-
Когда мы присваиваем объект новой переменной,Назначение на самом деле является адресом объекта в стеке, а не данных в куче.. То есть два объекта указывают на одно и то же пространство хранения.Независимо от того, какой объект изменяется, это фактически содержимое измененного пространства хранения.Поэтому два объекта связаны.
-
Поверхностное копирование: повторное создание памяти в куче Базовые типы данных объектов до и после копирования не влияют друг на друга, но ссылочные типы объектов до и после копирования будут влиять друг на друга, поскольку они используют одну и ту же память.
-
Глубокая копия: откройте новую область в куче памяти для хранения нового объекта, рекурсивно скопируйте подобъекты в объекте, и два объекта до и после копирования не влияют друг на друга.
Давайте сначала посмотрим на следующий пример, сравнивая эффект присваивания и глубокого/поверхностного копирования исходного объекта после модификации:
// 对象赋值
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "阿浪";
obj2.arr[1] =[5,6,7] ;
console.log('obj1',obj1) // obj1 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// 浅拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj3=shallowClone(obj1)
obj3.name = "阿浪";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存
// 这是个浅拷贝的方法
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
// 深拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj4=deepClone(obj1)
obj4.name = "阿浪";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
// 这是个深拷贝的方法
function deepClone(obj) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
В приведенном выше примере obj1 — исходный объект, obj2 — объект, полученный операцией присваивания, объект, полученный поверхностной копией obj3, и объект, полученный глубокой копией obj4, с помощью следующей таблицы мы можем ясно увидеть их влияние на исходные данные:
Неглубокая реализация копирования
1.Object.assign()
Метод Object.assign() может копировать любое количество перечисляемых свойств самого исходного объекта в целевой объект, а затем возвращать целевой объект.
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
2. Метод _.clone библиотеки функций lodash
Библиотека также предоставляет _.clone для поверхностного копирования, и позже мы представим использование этой библиотеки для реализации глубокого копирования.
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true
3. Оператор распространения...
Оператор распространения — это функция es6/es2015, которая обеспечивает очень удобный способ выполнения неглубоких копий, что является той же функцией, что и Object.assign().
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
4.Array.prototype.concat()
let arr = [1, 3, {
username: 'kobe'
}];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
5.Array.prototype.slice()
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
Реализация глубокого копирования
1.JSON.parse(JSON.stringify())
let arr = [1, 3, {
username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
Это также использовать JSON.stringify для преобразования объекта в строку JSON, а затем использовать JSON.parse для анализа строки в объект.Один за другим генерируется новый объект, и объект открывает новый стек для достижения глубокого копирования.
Хотя этот метод может реализовать глубокое копирование массивов или объектов, он не может обрабатывать функции и регулярные выражения., потому что после обработки двух на основе JSON.stringify и JSON.parse полученная закономерность перестает быть регулярной (становится пустым объектом), а полученная функция перестает быть функцией (становится нулевой).
Например следующий пример:
let arr = [1, 3, {
username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
2. Метод _.cloneeP функциональной библиотеки Lodash
Библиотека также предоставляет _.cloneDeep для Deep Copy.
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
3. Метод jQuery.extend()
jquery предоставляет$.extend
Может использоваться для глубокого копирования
$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
4. Рукописный рекурсивный метод
Рекурсивный метод реализует принцип глубокого клонирования:Обход объектов и массивов до тех пор, пока они не станут базовыми типами данных, а затем их копирование — это глубокая копия..
Существует особый случай, чтобы отметить, что объект существуетциклическая ссылкаВ случае, когда атрибут объекта напрямую ссылается на самого себя, для решения проблемы циклической ссылки мы можем открыть дополнительное пространство для хранения соответствующей связи между текущим объектом и скопированным объектом.Когда текущий объект необходимо скопировать, перейдите сначала в место для хранения.Узнайте, копировали ли вы когда-либо этот объект, если есть, верните его напрямую, если нет, продолжайте копировать, чтобы гениально решить проблему циклической ссылки. Если у вас есть какие-либо сомнения по этому поводу, пожалуйста, внимательно прочитайтеConardLi大佬
Как написать глубокий текст, который удивит интервьюера?Эта статья.
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
Добро пожаловать на официальный аккаунт: мастера фронтенда, вместе станем свидетелями вашего роста!