На этот раз освойте глубокую копию

JavaScript
На этот раз освойте глубокую копию

написать впереди

В ежедневном процессе разработки мы часто задействуем копирование данных. При его использовании вы часто путаетесь, нужна ли вам глубокая копия, а не используете ли вы стороннюю библиотеку или метод, написанный самостоятельно. Если вы используете стороннюю библиотеку, такую ​​как lodash, вы можете ошибиться. Однако, если вы используете свой собственный метод, легко сделать ошибки.Очевидно, что иногда копию можно успешно скопировать, почему иногда она не может быть успешно скопирована? Это вызвано плохим пониманием глубокого копирования.
И в процессе ежедневного собеседования интервьюер часто просит: Напишите подробный текст. В это время не исключено, что перед интервью может быть запомнена глубокая копия рога Будды, но она также двусмысленна и неясна, что сказывается на впечатлении от интервью.Поэтому основная задача этой статьи — дать вам полное представление о глубоком копировании в Javascript. Я не просто дам вам окончательный код, но проведу вас шаг за шагом и пойму, почему. Ведь только то, что вы понимаете, может произвести глубокое впечатление, и это будет нелегко забыть в будущем.

1. Базовые знания

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

1.1 Типы данных

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

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol
  • Object

Первые 6 типов являются примитивными типами данных, аObjectявляется ссылочным типом данных. Я предпочитаю называть первые 6 простых типов данных, аObjectНазовите это сложным типом данных. Поскольку простые типы данных не имеют подтипов, их больше нельзя разделить, а сложные типы данных имеют подтипы, такие какArray,Function,RegExp,Dateи других объектов, именно из-за этих различий в подтипах и возникают различные проблемы с глубоким копированием. Вот почему многие люди отвечают «Массив» и «Функция», отвечая на вопрос, что такое основные типы данных. На самом деле это просто подтипы Object, а не примитивные типы данных.
Различные типы данных приведут к различным методам хранения в памяти: если это простой тип данных, он хранится в пространстве стека, а значение сохраняется, если это сложный тип данных, он хранится в куче. и сохраняется значение. Именно эта разница в хранении приводит к разнице между поверхностной и глубокой копией.数据类型

1.2 Мелкие и глубокие копии

Давайте сначала проясним, что такое поверхностная копия и что такое глубокая копия.
Мелкая копия:Если свойство является базовым типом, значение базового типа копируется.Если свойство является ссылочным типом, копируется адрес памяти, поэтому изменение вновь скопированного объекта повлияет на исходный объект.
Это некоторые официальные определения.Они любят описывать их неинтуитивным способом адресов памяти.Я надеюсь, что смогу ясно описать их с помощью простой диаграммы.
Неглубокая копия, насколько я понимаю, то, что с перечеркнутыми линиями, является поверхностной копией. 浅拷贝Как показано на фиг.1:Так называемая поверхностная копия означает, что независимо от того, сколько объектов вы копируете, свойства скопированных объектов по-прежнему указывают на свойства исходного объекта..Судя по линии на графике, это означает, что линия между двумя объектами пересекается.Именно из-за пересечения линий они влияют друг на друга, поэтому, пока один объект изменяет атрибут, соответствующие атрибуты других объектов будут изменены. Пример:

let obj = {
    id:1,
    info:{
        name:"hello",
        age:24
    }
}
// 实现浅拷贝
let obj2 = {};
for(let key in obj){
  obj2[key] = obj[key];
}
obj2.id = 3;
console.log(obj.id);   // 3

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

深拷贝Мы видим, что глубокая копия объекта — это создание объекта, совершенно не связанного с предыдущим объектом.Линия на графике означает, что линии между двумя объектами не пересекаются.. Поскольку два объекта совершенно не пересекаются (говоря по-простому, мы две параллельные линии, и они никогда не будут связаны между собой), поскольку нет связи, нет и вопроса о том, кто на кого влияет.

let obj = {
    id:1,
    info:{
        name:"hello",
        age:24
    }
}
let obj2 = JSON.parse(JSON.stringify(obj)); // 这里实现深拷贝  暂时记住就好
obj2.id = 3;
obj2.info.name = "刘亦菲";
console.log(obj.id);   // 1
console.log(obj.info.name);   // hello

в коде вышеobj2через глубокую копиюobj1Получено, измените свойства obj2 и обнаружите, что свойства Obj1 не будут изменены. Это глубокая копия.

Во-вторых, реализация глубокого копирования

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

2.1 Сериализация и десериализация

В приведенном выше примере кода для глубокого копирования я использовалJSON.parse(JSON.stringify)Реализовано глубокое копирование. Это метод глубокого копирования, который часто используется в повседневной разработке и может реализовывать глубокие копии некоторых менее сложных типов данных. Пример:

let num = 24;
let bool = true;
let obj = {
  id:1
  info:{
    name:"hello",
    age:24
  }
}

let num1 = JSON.parse(JSON.stringify(num))// num1就是num的深拷贝   虽然简单的数据类型这种拷贝没啥意义
let bool1 = JSON.parse(JSON.stringify(bool))// num1就是num的深拷贝   虽然简单的数据类型这种拷贝没啥意义
let obj2 = JSON.parse(JSON.stringify(obj))// 复杂数据类型也可以使用JSON.parse(JSON.stringify(obj))

Но у этого метода есть некоторые недостатки, потому что он зависит от JSON, он не поддерживает другие форматы, которые не поддерживает JSON, черезJSONОфициальный сайт показывает, что JSON поддерживает толькоobject,array,string,number,true,false,nullЭти типы данных или значений не поддерживаются другими типами данных, такими как функции, неопределенные, дата и регулярное выражение. Это свойство просто игнорируется для данных, которые оно не поддерживает.

1. У объектов не может быть функций, иначе их нельзя сериализовать
函数问题

2. В объекте не может быть undefined, иначе его нельзя сериализовать

undefined问题

3. В объекте не должно быть RegExp, иначе его нельзя сериализовать
Если в свойстве объекта есть регулярное выражение, оно будет проигнорировано после клонирования с использованием JSON.parse(JSON.stringify)), и в конечном итоге оно станет пустым.正则

4. Данные типа даты будут преобразованы в строковый тип.
Если в объекте есть данные типа Date, они будут преобразованы в строку, при этом будут потеряны некоторые возможности Date, такие как форматирование времени и другие методы.日期格式

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

let obj = {name:'hello'}
obj.self = obj   // self属性又指向了obj对象,形成了一个换

Объекты этой кольцевой структуры будут сообщать об ошибке при глубоком копировании с использованием JSON.parse (JSON.stringify).环状问题

Резюме: Из приведенного выше анализа мы видим, чтоJSON.parse(JSON.stringify())Хотя глубокое копирование объекта возможно, оно имеет большие ограничения и не подходит для сложных объектов. Следовательно, нам нужно использовать другой способ реализации глубокого копирования, то есть вручную рекурсивно реализовать глубокое копирование.

2.2 Рекурсивное клонирование

Об основных типах данных мы говорили в первой части.Любые данные состоят из этих типов, как раз из-за различий между этими типами, такими как простые типы и сложные типы (Объект), и подтипы сложных типов (Массив, Функция , Дата) Различия приводят к различным проблемам с глубокими копиями. Следовательно, нам нужно только последовательно реализовать копирование следующих типов данных, и мы можем хорошо реализовать глубокую копию всех данных.数据类型Следующим шагом будет шаг за шагом реализовать копию каждого типа данных, а конечным результатом будет получение полной глубокой копии.

2.2.1 Копирование простых типов данных

Если это простой тип данных, так как значение сохраняется, необходимо вернуть только это значение, и нет проблемы взаимного влияния. Реализация выглядит следующим образом:

function deepClone(target){
  return target
}

2.2.2 Копирование простых объектов

Так называемые простые объекты означают, что эти объекты состоят из простых типов данных, которые отображаются на рынке, и нет данных подтипов, таких как Массив, Функция и Дата. Например это:

let obj1 = {
  name:"hello",
  child:{
    name:"小明"
  }
}

Идея состоит в том, чтобы создать новый объект, а затем скопировать свойства каждого объекта в новый объект. Если свойство имеет простой тип, возвращайте значение свойства напрямую. еслиObjectвведите, затем пройтиfor...inПеребирайте каждое свойство объекта и добавляйте его к новому объекту одно за другим. Потому что невозможно различить уровень объектов, используется рекурсия, и каждое присваивание вызывает себя.В любом случае, если это простой тип, он рекурсирует и вернет значение напрямую.Если этоObjectвведите, затем рекурсивно ищите назначения.

function deepClone(target){
  if(target instanceof Object){
      let dist = {};
      for(let key in target){
        // 递归调用自己获取到每个值
          dist[key] = deepClone(target[key]);
      }
      return dist;
  }else{
      return target;
  }
}

Мы используем функцию глубокого копирования выше для простого теста. Для копирования простого объекта все ссылочные типы в копируемом объекте должны быть разными, но значения всех простых типов данных одинаковы (но они не одинаковы), например:

let obj1 = {
  name:"hello",
  child:{
    name:"小明"
  }
}
let obj2 = deepClone(obj1);
console.log(obj2 !== obj1);                         // true
console.log(obj2.name === obj1.name);               // true
console.log(obj2.child !== obj1.child);             // true
console.log(obj2.child.name === obj1.child.name);   // true
obj2.name = "World";
console.log(obj1.name === 'hello');                 // true

2.2.3 Копирование сложных объектов — массивов

Используя описанный выше метод, мы можем добиться копирования простых объектов, но для некоторых объектов, содержащих подтипы, таких как массивы, это невозможно. Давайте посмотрим на код:

    const a = [[11,12],[21,22]];
    const a2 = deepClone(a);
    console.log('........:',a2); //{ '0': { '0': 11, '1': 12 }, '1': { '0': 21, '1': 22 } }

Мы обнаруживаем, что скопированный массив получает специальный объект. Этот объект использует нижний индекс массива в качестве значения ключа и каждый элемент массива в качестве значения значения, потому что для обхода массива из-за того, что значение ключа не может быть найдено, следующая таблица массива используется в качестве значения ключа. по умолчанию элемент как значение. В этом случае тип данных, полученный после окончательного клонирования, несовместим с массивом (на самом деле это вызвано особой природой самого массива). Наконец, он становится объектом после копирования из массива.
Мы обнаружили, что проблема в том, что мы определяем все как {}, а массивы не могут быть описаны {}, поэтому нам нужно различать окончательный возвращаемый тип данных в соответствии с типом объекта.. Код реализации выглядит следующим образом:

// 先不优化代码
function deepClone(target){
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        // 如果是数组,就创建一个[]
        dist = []
      }else{
        dist = {};
      }
      for(let key in target){
          dist[key] = deepClone(target[key]);
      }
      return dist;
  }else{
      return target;
  }
}

Поскольку массив также может быть пройден через for in , фактически нам нужно изменить, чтобы определить, является ли клонируемый объект массивом или нет при клонировании.

2.2.4 Копирование сложных объектов - функции

Копирование функций на самом деле немного спорно, потому что в глазах многих людей функции нельзя скопировать. На мой взгляд у функции фактически не должно быть глубокой копии.Если она есть, то это реализация функции функции.При этом объект функции также должен соответствовать логике глубокой копии( ссылочные атрибуты не равны, атрибуты простого типа равны):

  1. Функция реализует ту же функцию - возвращаемое значение такое же
  2. Свойства ссылочного типа у функции должны быть разными, а значение свойства прямого типа должно быть одинаковым.

Как показано в следующем коде:

    const fn = function(){return 1};
    fn.xxx = {yyy:{zzz:1}};
    const fn2 = deepClone(fn);
    console.log(fn !== fn2);                 // 函数不相同
    console.log(fn.xxx!== fn2.xxx);          // 函数引用类型的属性不相同
    console.log(fn.xxx.yyy!== fn2.xxx.yyy);  // 函数引用类型的属性不相同
    console.log(fn.xxx.yyy.zzz === fn2.xxx.yyy.zzz);// 函数简单类型的属性值相同
    console.log(fn() === fn2());            //  函数执行后相等

Так как же реализовать копию функции?

  1. Сначала вам нужно вернуть новую функцию
  2. Результат выполнения новой функции должен быть таким же, как исходная функция.
function deepClone(target){
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        dist = []
      }else if(target instanceof Function){
        dist = function(){
            // 在函数中去执行原来的函数,确保返回的值相同
            return target.call(this, ...arguments);
        }
      }else{
        dist = {};
      }
      for(let key in target){
          dist[key] = deepClone(target[key]);
      }
      return dist;
  }else{
      return target;
  }
}

2.2.5 Копирование сложных объектов — регулярные выражения

Как скопировать обычный? В качестве примера возьмем простое регулярное выражение:

const a = /hi\d/ig;

Обычный, по сути, состоит из двух частей: обычный шаблон (содержимое между слэшами)hi\d, а параметрыig. Следовательно, пока вы можете получить эти две части, вы можете получить регулярное выражение. Чтобы добиться клона этого регулярного. регулярнымsourceАтрибут может получить обычный шаблон через обычныйflagsАтрибуты могут получать обычные параметры.

const a = /hi\d/ig;
console.log(a.source);   //   hi\d
console.log(a.flags)    // ig

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

function deepClone(target){
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        // 拷贝数组
        dist = [];
      }else if(target instanceof Function){
        // 拷贝函数
        dist = function () {
          return target.call(this, ...arguments);
        };
      }else if(target instanceof RegExp){
        // 拷贝正则表达式
       dist = new RegExp(target.source,target.flags);
      }else{
        // 拷贝普通对象
        dist = {};
      }
      for(let key in target){
          dist[key] = deepClone(target[key]);
      }
      return dist;
  }else{
      return target;
  }
}

2.2.6 Копирование сложных объектов — дат

Если копия является датой, после копирования с помощью нашего метода выше она вернет строку. Эта строка не относится к типу Дата, Он не может вызывать какой-либо метод Date. Поэтому нам нужно поддерживать копирование в формате даты. Фактически, с помощью копии сложных типов объектов Array, Function и RexExp выше мы можем обнаружить, что на самом деле все эти копии передаются через new XXX(), что эквивалентно созданию нового объекта и его возврату. То же самое касается копии даты:

dist = new Date(source);

Возьмите дату для копирования в качестве параметра и сгенерируйте новую дату. Окончательная реализация выглядит следующим образом:


function deepClone(target){
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        // 拷贝数组
        dist = [];
      }else if(target instanceof Function){
        // 拷贝函数
        dist = function () {
          return target.call(this, ...arguments);
        };
      }else if(target instanceof RegExp){
        // 拷贝正则表达式
       dist = new RegExp(target.source,target.flags);
      }else if(target instanceof Date){
          dist = new Date(target);
      }else{
        // 拷贝普通对象
        dist = {};
      }
      for(let key in target){
          dist[key] = deepClone(target[key]);
      }
      return dist;
  }else{
      return target;
  }
}

Ну, до сих пор наша глубокая копия поддерживала наиболее распространенные типы данных, такие как типы данных с коротким ответом, обычные объекты, массивы, функции, регуляризация и даты. Хотя в нашем коде их многоif elseструктуру, но я думаю, что это самый простой способ написать ее, чтобы все поняли.

3. Дальнейшая оптимизация

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

3.1 Игнорировать свойства прототипов

Когда мы просматриваем свойства объекта, мы используемfor in,for inПеребирает все итерируемые свойства, включая прототип. Например:

let a = Object.create({name:'hello'});
a.age = 14;

Затем при использовании обхода он будет проходитьnameиageАтрибуты. не только на себяageАтрибуты. Однако на самом деле мы не должны перебирать свойства прототипа, потому что это привело бы к очень глубоким свойствам объекта. Поэтому используйтеfor inПри обходе лучше отличать свойства прототипа от его собственных свойств, черезhasOwnPropertyОтфильтруйте его собственные свойства для обхода.

    for (let key in source) {
      // 只遍历本身的属性
      if(source.hasOwnProperty(key)){
        dist[key] = deepClone(source[key]);
      }
    }

Таким образом, оптимизированный код выглядит следующим образом:

function deepClone(target){
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        // 拷贝数组
        dist = [];
      }else if(target instanceof Function){
        // 拷贝函数
        dist = function () {
          return target.call(this, ...arguments);
        };
      }else if(target instanceof RegExp){
        // 拷贝正则表达式
       dist = new RegExp(target.source,target.flags);
      }else if(target instanceof Date){
          dist = new Date(target);
      }else{
        // 拷贝普通对象
        dist = {};
      }
      for(let key in target){
          // 过滤掉原型身上的属性
        if (target.hasOwnProperty(key)) {
            dist[key] = deepClone(target[key]);
        }
      }
      return dist;
  }else{
      return target;
  }
}

3.2 Проблема взрыва стека кольцевых объектов

мы использовали раньшеJSON.parse(JSON.stringify())При копировании объектов я столкнулся с проблемой сообщения об ошибке, если есть кольцевой объект. Тогда использование нашей собственной функции глубокого копирования также столкнется с проблемами. Это потому, что мыdeepCloneВ функции используется рекурсия Само собой разумеется, что каждая рекурсия должна иметь условие завершения, но поскольку структура дерева объектов обычно имеет конечную точку, рекурсия автоматически завершится в конечной точке. Но если у объекта есть свойства, указывающие на себя, то образуется кольцо, например:

let a = {name:"小明"};
a.self = a;   // a的self属性指向a

В этом случае в процессе рекурсивного вызова возникнет бесконечный цикл, и в итоге стек лопнет. Поэтому нам нужно добавить рекурсивные условия завершения. Так называемое условие рекурсивного завершения заключается в том, чтобы определить, был ли объект клонирован.Если он был клонирован, клонированный объект используется напрямую без рекурсии. Поэтому нам нужно что-то для хранения возможно дублированного свойства вместе с его адресом клона. Лучший способ — составить карту.缓存Здесь всем может быть немного сложно понять, поэтому мы представим это в более интуитивно понятном графическом виде: На приведенном выше рисунке мы по очереди копируем атрибут a, а скопированные атрибуты, соответствующие атрибуту b и атрибуту c, — это a1, b1 и c1. Среди них атрибут c указывает на атрибут a, поэтому при копировании мы должны снова копировать атрибут a, так что цикл образуется непрерывно, и, наконец, рекурсия приводит к взрыву стека. Поэтому для свойства, которое уже было скопировано, мы можем использовать вещь, чтобы сохранить его и соответствующий адрес объекта копирования.Если мы встретим с, который указывает на а, нам нужно только присвоить сохраненный адрес объекта с. Вот и все . Для этого требуются два значения, и наиболее распространенной структурой данных для однозначного соответствия является объект или карта. Конечно, вы также можете использовать массивы. Здесь мы используем карту для сохранения.

let cache = new Map();
function deepClone(target){
  if(cache.get(target)){
      return cache.get(target)
  }
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        // 拷贝数组
        dist = [];
      }else if(target instanceof Function){
        // 拷贝函数
        dist = function () {
          return target.call(this, ...arguments);
        };
      }else if(target instanceof RegExp){
        // 拷贝正则表达式
       dist = new RegExp(target.source,target.flags);
      }else if(target instanceof Date){
          dist = new Date(target);
      }else{
        // 拷贝普通对象
        dist = {};
      }
      // 将属性和拷贝后的值作为一个map
      cache.set(target, dist);
      for(let key in target){
          // 过滤掉原型身上的属性
        if (target.hasOwnProperty(key)) {
            dist[key] = deepClone(target[key]);
        }
      }
      return dist;
  }else{
      return target;
  }
}

3.3 Проблемы взаимного влияния, вызванные общим кешем

В приведенной выше функции deepClone мы добавили кеш с помощьюcacheдля сохранения клонированного объекта и его соответствующего адреса клона. Но этот путь принесет новую проблему: поскольку каждый клон создает объект, он будет использовать этотcache, что приведет к тому, что новый клон будет затронут предыдущим клоном. Пример:

  let a = {
    name:"hello",
  }     
  let a1 = deepClone(a);
  console.log(map);  //{ name: 'hello' } => { name: 'hello' }
  let b = {
    age:24
  }
  let b1 = deepClone(b);
  console.log(map);  //   { name: 'hello' } => { name: 'hello' },{ age: 24 } => { age: 24 } }

Мы обнаружили, что когда объект b был глубоко скопирован, в карте уже было значение{ name: 'hello' }. На самом деле эти значения не являются свойствами, которые были скопированы на b. То есть на копию b влияет копия a, что может вызвать проблемы. Поэтому вместо того, чтобы все глубокие копии использовали один и тот же кеш, мы позволяем каждой глубокой копии использовать свои собственные свойства.Решение состоит в том, чтобы создавать новую карту (параметр по умолчанию) каждый раз при вызове функции, а затем передавать карту вниз, если требуется рекурсия.

function deepClone(target,cache = new Map()){
  if(cache.get(target)){
      return cache.get(target)
  }
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        // 拷贝数组
        dist = [];
      }else if(target instanceof Function){
        // 拷贝函数
        dist = function () {
          return target.call(this, ...arguments);
        };
      }else if(target instanceof RegExp){
        // 拷贝正则表达式
       dist = new RegExp(target.source,target.flags);
      }else if(target instanceof Date){
          dist = new Date(target);
      }else{
        // 拷贝普通对象
        dist = {};
      }
      // 将属性和拷贝后的值作为一个map
      cache.set(target, dist);
      for(let key in target){
          // 过滤掉原型身上的属性
        if (target.hasOwnProperty(key)) {
            dist[key] = deepClone(target[key], cache);
        }
      }
      return dist;
  }else{
      return target;
  }
}

3.4 Проблема взрыва стека, вызванная слишком длинным объектом

Мы знаем, что в нашей глубокой копии используется рекурсия, а у рекурсии есть стек рекурсии. Глубина стека рекурсии ограничена. Как только глубина рекурсии объекта превышает глубину стека рекурсии, стек может взорваться. Например, объект a ниже имеет глубину объекта 20 000 свойств. В этом случае, когда рекурсия достигнет 5000, стек взорвется, что приведет к ошибке.

      let a = {
        child:null 
      }
      let b = a;
      for(let i = 0;i < 20000;i++){
        b.child = {
          child:null
        }
        b = b.child;
      }
      console.log(a);

Не существует решения проблемы взрыва стека, вызванного слишком глубокими объектами, а такие глубокие объекты встречаются редко.

4. Тест

Что ж, пока мы в основном реализовали полнофункциональную глубокую копию. Окончательная функция реализации выглядит следующим образом:

function deepClone(target,cache = new Map()){
  if(cache.get(target)){
      return cache.get(target)
  }
  if(target instanceof Object){
      let dist ;
      if(target instanceof Array){
        // 拷贝数组
        dist = [];
      }else if(target instanceof Function){
        // 拷贝函数
        dist = function () {
          return target.call(this, ...arguments);
        };
      }else if(target instanceof RegExp){
        // 拷贝正则表达式
       dist = new RegExp(target.source,target.flags);
      }else if(target instanceof Date){
          dist = new Date(target);
      }else{
        // 拷贝普通对象
        dist = {};
      }
      // 将属性和拷贝后的值作为一个map
      cache.set(target, dist);
      for(let key in target){
          // 过滤掉原型身上的属性
        if (target.hasOwnProperty(key)) {
            dist[key] = deepClone(target[key], cache);
        }
      }
      return dist;
  }else{
      return target;
  }
}

Далее мы напишем сложный объект, используем этот объект для создания глубокой копии и проверим производительность нашей функции.

          const a = {
            i: Infinity,
            s: "",
            bool: false,
            n: null,
            u: undefined,
            sym: Symbol(),
            obj: {
              i: Infinity,
              s: "",
              bool: false,
              n: null,
              u: undefined,
              sym: Symbol(),
            },
            array: [
              {
                nan: NaN,
                i: Infinity,
                s: "",
                bool: false,
                n: null,
                u: undefined,
                sym: Symbol(),
              },
              123,
            ],
            fn: function () {
              return "fn";
            },
            date: new Date(),
            re: /hi\d/gi,
          };
          let a2 = deepClone(a);
          console.log(a2 !== a);
          console.log(a2.i === a.i);
          console.log(a2.s === a.s);
          console.log(a2.bool === a.bool);
          console.log(a2.n === a.n);
          console.log(a2.u === a.u);
          console.log(a2.sym === a.sym);
          console.log(a2.obj !== a.obj);
          console.log(a2.array !== a.array);
          console.log(a2.array[0] !== a.array[0]);
          console.log(a2.array[0].i === a.array[0].i);
          console.log(a2.array[0].s === a.array[0].s);
          console.log(a2.array[0].bool === a.array[0].bool);
          console.log(a2.array[0].n === a.array[0].n);
          console.log(a2.array[0].u === a.array[0].u);
          console.log(a2.array[0].sym === a.array[0].sym);
          console.log(a2.array[1] === a.array[1]);
          console.log(a2.fn !== a.fn);
          console.log(a2.date !== a.date);
          console.log(a2.re !== a.re);

Мы обнаружили, что в итоге все значения были верны.По сути, это написанный мной юнит-тест, но он выводится с помощью console.log. Если вы хотите увидеть полный процесс тестирования, вы можете проверить мойgithub

V. Резюме

Эта статья в основном включает:

  • Основные типы данных Javascript
  • Разница между поверхностной копией и глубокой копией
  • JSON.parse(JSON.stringify) реализует глубокую копию, и недостатки этого подхода
  • Как реализовать глубокую копию с помощью рекурсивного клонирования шаг за шагом от поверхностного к глубокому

Благодаря этой статье вы сможете в основном освоить большую часть соответствующих знаний о глубоком копировании, которых хватит на все интервью. Что еще более важно, после освоения этой статьи таким образом, она будет впечатляющей и не будет забыта.
Наконец: код этой статьи находится вdeepClone, приветствую всех, чтобы звездить.
Закончили цветение.