Один из моих частых вопросов на собеседованиях касается текста. Мы много слышали о мелком и глубоком копировании в экосистеме 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 🤓. Также для фрагмента массива может быть создана глубокая копия. Проверьте, какой именно, я всегда путаюсь со срезом и сращиванием 🧐.