Один из моих частых вопросов на собеседованиях касается текста. Мы много слышали о мелком и глубоком копировании в экосистеме JavaScript. Мы использовали cloneDeep из lodash. Кроме того, некоторые люди, работавшие над angularjs, знают aboutangular.copy. Но очень немногие на самом деле объясняют это. Вот почему я пишу то, что знаю об этом.

Давайте посмотрим фрагмент кода —

#1 const userA = {name: "User A", address: {city: "Bangalore"}};
#2 const userB = userA;
#3      userB.name = "User B";
#4 console.log(userA.name); // ??

Я никогда не думал, что количество людей, которые думают, что line#4отдаст User A, будет настолько велико. Они тоже путаются с const. Я использовал тот же пример с let, они отвечают правильно, но когда я изменил let на const, все развалилось. Некоторые люди объясняют это поверхностной копией. Но так ли это? Это копия ссылки (местоположения памяти) от userA до userB. Копии значения вообще нет. userA и userB указывают на один и тот же объект. Любое изменение в одном из них отразится на другом.

Возможно, вы думаете, почему у меня есть ключ address, когда люди терпели неудачу с ключом name. Это для тех, кто работал над redux или подобными библиотеками. Они, как правило, знают об этом, а также знают об операторе распространения (…), который часто используется в редьюсерах для обновления состояния.

Давайте посмотрим фрагмент кода с оператором спреда —

#1 const userA = {name: "User A", address: {city: "Bangalore"}};
#2 const userB = {...userA};
#3      userB.name = "User B";
#4 console.log(userA.name); // User A
#5      userB.address.city = "Pune";
#6 console.log(userA.address.city); // ??

Плотина! Некоторых это смущает, и большинство из них были уверены, что line#6 покажет Bangalore, потому что они использовали оператор распространения, а он выполняет поверхностное копирование.

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

Когда меня попросили объяснить, что делает оператор распространения. Они говорят: «Он скопирован userA в новую ячейку памяти и назначен userB».

Они наполовину правильные, наполовину нет. Потому что объект внутри объекта будет просто прямым присвоением, как в первом примереuserA = userB. Итак, здесь userB.address.city будет указывать на ту же ячейку памяти, что и userA.address.city. Поэтому line#6 будет отображать Пуну вместо Бангалора. Аналогичное поведение и для Object.assign.

Насколько мне известно, в JavaScript нет метода глубокого копирования. Не волнуйтесь, я знаю о JSON.stringify и JSON.parse. Когда кто-то дает такой ответ, я обычно спрашиваю, почему он работает и когда не работает.

// This will works
#1 const userA = {name: "User A", address: {city: "Bangalore"}};
#2 const userB = JSON.parse(JSON.stringify(userA));

Это работает благодаря тому, как строки (примитивы) работают в JavaScript. Они неизменны, как неизменны все примитивы. Это не означает, что переменная, которой вы назначаете строку, неизменяема.

Давайте разберемся с фрагментом кода —

#1 let str = "Hello";
#2     str = "Hello world!";

Выше в line#2 оно изменится strсHello World!. str является изменчивым. Но значениеHelloв line#1 и line#2 было неизменным.

Что я имею в виду? Когда мы назначаем новое значение str в line#2, оно выделяет новую ячейку памяти. Он не будет обновлять область памяти, выделенную по адресу line#1. Также помните, что строка ведет себя как массив, но, поскольку она неизменяема, мы не можем ее обновить.

let str = "Hello World!";
str[1] = "A";
console.log(str); // "Hello World"

Поэтому, когда мы копируем с помощью JSON.stringify и JSON.parse, это фактически копирует в новую ячейку памяти. Вот почему это работает.

// This will not work as expected
const userA = {
    name: undefined, 
    address: {city: "Bangalore"}, 
    displayCity: function() {
        return this.address.city;
    }
};
const userB = JSON.parse(JSON.stringify(userA));
// {"address":{"city":"Bangalore"}}

Насколько я понимаю, JSON.stringify и JSON.parse не будут копировать undefined , Function и Symbol. Подробности смотрите в Документации MDN.

Также попробуйте создать собственную функцию cloneDeep. Я предпочту функцию custom больше, чем вариант JSON в интервью. В реальном мире просто используйте lodash 🤓. Также для фрагмента массива может быть создана глубокая копия. Проверьте, какой именно, я всегда путаюсь со срезом и сращиванием 🧐.