Как написать удовлетворительный глубокий текст на собеседовании (подходит для младшего фронтенда)

JavaScript
Как написать удовлетворительный глубокий текст на собеседовании (подходит для младшего фронтенда)

предисловие

Там уже много о深拷贝与浅拷贝, почему ты должен написать это снова 💯

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

Поделитесь хорошей ментальной картой👇

深拷贝
глубокая копия

Через краткое изложение текста, я надеюсь понять:

  • Что такое глубокое/поверхностное копирование и чем они отличаются от присваивания
  • Каковы методы реализации глубокого копирования/поверхностного копирования

Эта глава начинается непосредственно с копирования.Для основных типов данных различия перед ссылками на типы данных можно увидеть на карте ума выше 👆

Или взгляните на мои предыдущие главы, чтобы восполнить основы.Если что-то не так, пожалуйста, укажите!


копия ссылочного типа данных

Для справочных типов данных подразделение можно разделить на следующие три аспекта.

  • назначать
  • мелкая копия
  • глубокая копия

назначать

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

var a = {}; // a保存了一个空对象的实例
var b = a;  // a和b都指向了这个空对象

a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'

b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22

console.log(a == b);// true
拷贝2
копия 2

В такой ситуации a и b будут указывать на одни и те же данные. Если один из данных будет изменен, это повлияет на другой. В реальной разработке это не тот результат, которого мы ожидали. В какой-то степени это будет ошибкой.

Так как же нам не позволить друг другу влиять друг на друга? Простой способ - скопировать копию данных переменной, поэтому"В соответствии с различными уровнями копирования его можно разделить на поверхностное копирование и глубокое копирование.", если используется неглубокая копия, знания будут скопированы в один слой, а если сделана глубокая копия, то это будет бесконечный уровень копирования!

Давайте сначала реализуем неглубокую копию

  let shallowClone = source => {
            let target = {}
            for(let i in source) {
                if( source.hasOwnProperty(i) ) 
                    target[i] = source[i];
            }
            return target
        }
        let demo = {
            b:{
                c : {
                }
            }
        }
        let demo2 = shallowClone(demo)
        let demo3 = demo;
        console.log(demo3 === demo )             // true
        console.log(demo2.b.c === demo.b.c )    // true
        console.log(demo2.b === demo.b )       // true
        console.log(demo2 === demo )          // false

Если demo3 = demo назначает присваивание, это присваивание адреса, то есть он указывает на тот же объект, поэтому это не тот результат, который нам нужен. неглубокого копирования, поэтому переменная demo2 должна быть реализацией. Один слой копирования, точно так же, как эффект строки 20, переменная demo2 открывает новую память в куче, так что два указывают на разные объекты, demo2.b === demo.b верно, указывая на то, что это эффект поверхностного копирования, простое копирование одного слоя, поэтому можем ли мы рекурсивно думать о завершении глубокого копирования?


Неглубокая реализация копирования

Object.assign()

Object.assign()Метод используется для копирования значений всех перечисляемых свойств из одного или нескольких исходных объектов в целевой объект. Он вернет целевой объект.

  let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            }
        }
        let clone_demo = Object.assign({}, demo)
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        console.log(clone_demo.name,clone_demo.book.price);  
        // dayday 100

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

Оператор распространения...

  let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            }
        }
        let clone_demo = {...demo}
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        console.log(clone_demo.name,clone_demo.book.price);  
        // dayday 100

Мы видим, что операция распространения... имеет тот же эффект, что и Object.assign().

Array.prototype.slice()

slice()метод возвращает новый объект массива, который являетсяbeginа такжеend(исключаяend) исходного массива, определяемого"мелкая копия". Исходный массив не будет изменен.

let a = [0, "1", [2, 3]];
let b = a.slice(1);
console.log(b);
// ["1", [2, 3]]

a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]

console.log(b);
//  ["1", [4, 3]]

Видно, что изменениеa[1]Позжеb[0]Значение не изменилось, но изменилосьa[2][0]После этого соответствующийb[1][0]Значение также меняется. иллюстрироватьslice()Метод является поверхностной копией, и соответствующийconcatИ т. д., обратите особое внимание на сложные структуры массивов в работе.


Реализация глубокого копирования

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

JSON.parse(JSON.stringify(obj))

  let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            }
        }
        let clone_demo = JSON.parse(JSON.stringify(demo))
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        console.log(clone_demo.name,clone_demo.book.price);  
        // dayday 45

Полное изменение переменной demo не влияет на clone_demo , это магия глубокого копирования.

Тот же метод можно использовать для глубокого копирования массивов.

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

дляundefined symbol 函数Три случая будут игнорироваться напрямую

  let demo = {
            name : 'dayday',
            h1 : undefined,
            h2 : Symbol('dayday'),
            h3 : function () {},
        }
        let clone_demo = JSON.parse(JSON.stringify(demo))
        console.dir(clone_demo)
        // { name : 'dayday' }

В случае циклической ссылки будет сообщено об ошибке.

let obj = {
    a: 1,
    b: {
        c: 2,
     d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

new Dateслучае результат преобразования неверен.

new Date();
// Wed Jul 01 2020 16:19:07 GMT+0800 (中国标准时间) {}

JSON.stringify(new Date());
// ""2020-07-01T08:19:19.860Z""

JSON.parse(JSON.stringify(new Date()));
// "2020-07-01T08:19:35.569Z"

Решение состоит в том, чтобы преобразовать его в строку или метку времени.

let date = (new Date()).valueOf();
// 1593591638596

JSON.stringify(date);
// "1593591638596"

JSON.parse(JSON.stringify(date));
// 1593591638596

при нормальных обстоятельствах

let demo = {
    name: "daydaylee",
    a: /'123'/
}
console.log(demo);
// {name: "daydaylee", a: /'123'/}

let clone_demo = JSON.parse(JSON.stringify(obj));
console.log(clone_demo);
// {name: "daydaylee", a: {}}

"PS: Почему существуют эти проблемы?Вы можете изучить JSON"

В дополнение к методу глубокого копирования, описанному выше, обычно используютсяjQuery.extend()а такжеlodash.cloneDeep(), Из-за длины статьи я не буду ее здесь представлять, если вам интересно, вы можете ознакомиться с ней самостоятельно.

Как реализовать глубокий текст на собеседовании

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

Простая неглубокая копия до:

let shallowClone = source => {
            let target = {}
            for(let key in source) {
                if(Object.prototype.hasOwnProperty.call(source, key)){
                    target[key] = typeof source[key] === 'object' ? shallowClone(source[key]) : source[key];
                }
            }
            return target
        }
        let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            }
        }
        let clone_demo = shallowClone(demo);
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        console.log(clone_demo.name,clone_demo.book.price) 
        // dayday 45

Здесь написано, хоть простой глубокий клон реализован, но еще есть нерешенные проблемы!

  • Не учитывает способ записи массива
  • Логика суждения об объекте не является строгой, потому чтоtypeof null === object
  • Нет проверки входящих параметров, например, входящий нуль должен возвращать ноль вместо {}

Сначала напишите функцию, совместимую с массивом и определяющую нулевой метод.

let isObject = obj => typeof obj === 'object' && obj !== null ;

Затем улучшите метод глубокого копирования

  // 保留数组 并且判断是不是null
        let isObject = obj => typeof obj === 'object' && obj !== null ;
        let shallowClone2 = source => {
            
            if(!isObject(source))  return source      // 非对象返回自身
            let target = Array.isArray(source) ? [] : {}
            for(let key in source) {
                if(Object.prototype.hasOwnProperty.call(source, key)){
                    target[key] = isObject(source[key]) ? shallowClone2(source[key]) : source[key];
                }
            }
            return target
        }
        let demo = {
            name : 'dayday',
            book : {
                title : 'Do you really Know JS',
                price : "45"
            },
            h1 : null,
            h2 : [1,2,3],
            h3 : undefined
        }
        let clone_demo = shallowClone2(demo);
        console.log(clone_demo);
        demo.name = 'new name'
        demo.book.price = '100'
        demo.h2[1] = 'new data'
        console.log(clone_demo.name,clone_demo.book.price) 
        // dayday 45
        console.log(clone_demo);   
        // 修改demo值为能影响clone_demo

Эта статья хорошо написана:Окончательный поиск глубокого копирования (99% людей не знают)

В нем также есть новые оптимизации для глубокого копирования, такие как проблема, связанная с тем, что циклические ссылки JSON.parse(JSON.stringify(obj)) вызывают исключения, и были сделаны оптимизации, после чего мы пытаемся оптимизировать эту небольшую проблему.

  • хеш-таблица

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

let isObject = obj => typeof obj === 'object' && obj !== null;
        let shallowClone3 = (source, hash = new WeakMap()) => {

            if (!isObject(source)) return source // 非对象返回自身
            if (hash.has(source)) return hash.get(source) // 新增检测, 查哈希表
            let target = Array.isArray(source) ? [] : {}
            hash.set(source, target) // 设置哈希表值
            for (let key in source) {
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    target[key] = isObject(source[key]) ? shallowClone3(source[key], hash) : source[key]; // 传入哈希表
                }
            }
            return target
        }
        let obj = {
            a: 1,
            b: {
                c: 2,
                d: 3
            }
        }
        obj.a = obj.b;
        obj.b.c = obj.a;
        let clone_obj = shallowClone3(obj)
        console.log(clone_obj)

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

  • Например, копирование значения типа Symbol должно решаться так
  • Это решает проблему рекурсивного взрыва стека.

Разумеется, заинтересованные читатели могут узнать об этом подробнее 🚀

Суммировать

-- Указывает ли он на тот же объект, что и исходные данные Первый уровень данных — это базовый тип данных. Исходные данные содержат дочерние объекты
назначать да Изменение изменит исходные данные Изменение изменит исходные данные
мелкая копия нет Изменять"Не делайте"изменит исходные данные Изменение изменит исходные данные
глубокая копия нет Изменять"Не делайте"изменит исходные данные Изменять"Не делайте"изменит исходные данные

Ссылаться на

Некоторые подводные камни глубокого копирования JavaScript

Как реализовать глубокое копирование вопросов на собеседовании

Глубокое погружение в JavaScript Deep Copy

MDN Расширить синтаксис

Object.assign() из MDN

Окончательный поиск глубокого копирования (99% людей не знают)

В этой статье используетсяmdniceнабор текста