Мутация — это изменение. Изменение формы или изменение сути. То, что подвержено мутациям, может меняться. Для того чтобы лучше осознать природу мутации — подумайте о героях фильма «Люди Икс». Они могли внезапно получать потрясающие возможности. Однако проблема заключается в том, что неизвестно, когда именно эти возможности проявятся. Представьте себе, что ваш товарищ ни с того ни с сего посинел и оброс шерстью. Страшновато, правда? В JavaScript существуют те же проблемы. Если ваш код подвержен мутациям, это значит, что вы можете, совершенно неожиданно, что-то изменить и поломать.



Объекты в JavaScript и мутация


В JavaScript-объекты можно добавлять свойства. Когда это делают после создания экземпляра объекта, объект необратимо изменяется. Он мутирует, как один из персонажей «Людей Икс».

В следующем примере константа egg, объект, мутирует после того, как к ней добавляют свойство isBroken. Такие объекты (вроде egg) мы называем мутабельными (то есть, имеющими возможность мутировать, изменяться).

const egg = { name: "Humpty Dumpty" };
egg.isBroken = false;

console.log(egg);
// {
//   name: "Humpty Dumpty",
//   isBroken: false
// }

Мутации — вполне обычное явление в JavaScript. Столкнуться с ними можно буквально всегда и везде.

Об опасности мутаций


Предположим, создана константа с именем newEgg, в которую записан объект egg. Затем понадобилось изменить свойство name у newEgg:

const egg = { name: "Humpty Dumpty" };

const newEgg = egg;
newEgg.name = "Errr ... Not Humpty Dumpty";

Когда мы меняем newEgg (подвергаем объект мутации), автоматически меняется и egg. Вы знали об этом?

console.log(egg);
// {
//   name: "Errr ... Not Humpty Dumpty"
// }

Вышеприведённый пример иллюстрирует опасность мутаций. Она сводится к тому, что когда вы меняете что-то в коде, то нечто, находящееся где-то в другом месте, тоже может поменяться, причём так, что вы об этом и знать не будете. В результате — ошибки, которые сложно находить и исправлять.

Все эти странности являются следствием того, что объекты в JavaScript передаются по ссылке.

Объекты в JavaScript и ссылки на них


Для того чтобы осознать смысл утверждения «объекты передаются по ссылке», сначала нужно понять то, что у каждого объекта в JavaScript есть уникальный идентификатор. Когда вы назначаете объект переменной, вы связываете переменную с идентификатором этого объекта (то есть, переменная теперь ссылается на объект) вместо того, чтобы записывать в переменную значение объекта, копировать его. Именно поэтому, сравнивая два разных объекта, даже содержащих одни и те же значения (или не содержащих их вовсе), мы получаем false.

console.log({} === {}); // false

Когда, в примере выше, константа egg была присвоена константе newEgg, в newEgg была записана ссылка на тот же объект, на который ссылалась константа egg. Так как egg и newEgg ссылаются на один и тот же объект, то, когда меняется newEgg, egg меняется автоматически.

console.log(egg === newEgg); // true

К сожалению, в ситуациях, схожих с описанной, обычно не нужно, чтобы то, что записано в одну переменную, менялось при воздействии на другую, так как это приводит к неправильному поведению кода, которое проявляется тогда, когда этого ждут меньше всего. Итак, как же предотвратить мутации объектов? Прежде чем найти ответ на этот вопрос, хорошо бы сначала узнать, что в JS является иммутабельным, то есть — неизменным.

Иммутабельные примитивы


В JavaScript примитивы (речь идёт о типах данных String, Number, Boolean, Null, Undefined, и Symbol) иммутабельны. То есть, нельзя изменить структуру примитива, нельзя добавить к нему свойства или методы. Например, при попытке добавить к примитиву новое свойство не произойдёт абсолютно ничего.

const egg = "Humpty Dumpty";
egg.isBroken = false;

console.log(egg); // Humpty Dumpty
console.log(egg.isBroken); // undefined

Ключевое слово const и иммутабельность


Многие думают, что переменные (константы), объявленные с использованием ключевого слова const, иммутабельны. Однако, это не так.

Использование ключевого слова const не делает то, что записано в константу, иммутабельным. Оно лишь не даёт назначить константе новое значение.

const myName = "Zell";
myName = "Triceratops";
// ERROR

Когда, с использованием ключевого слова const, определяют объект, его внутреннюю структуру вполне можно менять. В примере с объектом egg, даже хотя egg — константа, созданная с использованием ключевого слова const, от мутации это объект не защищает.

const egg = { name: "Humpty Dumpty" };
egg.isBroken = false;

console.log(egg);
// {
//   name: "Humpty Dumpty",
//   isBroken: false
// }

Предотвращение мутаций объектов


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

?Метод Object.assign


Конструкция Object.assign позволяет комбинировать два объекта (или большее число объектов), получая на выходе один новый объект. Пользоваться ей можно так:

const newObject = Object.assign(object1, object2, object3, object4);

Константа newObject будет содержать свойства из всех объектов, переданных Object.assign.

const papayaBlender = { canBlendPapaya: true };
const mangoBlender = { canBlendMango: true };

const fruitBlender = Object.assign(papayaBlender, mangoBlender);

console.log(fruitBlender);
// {
//   canBlendPapaya: true,
//   canBlendMango: true
// }

Если обнаружены два конфликтующих свойства, свойство объекта, который расположен правее в списке аргументов Object.assign, перезаписывает свойство объекта, расположенного в списке левее.

const smallCupWithEar = {
  volume: 300,
  hasEar: true
};

const largeCup = { volume: 500 };
// В этом случае свойство volume будет перезаписано, вместо 300 тут будет 500
const myIdealCup = Object.assign(smallCupWithEar, largeCup);

console.log(myIdealCup);
// {
//   volume: 500,
//   hasEar: true
// }

Однако, будьте внимательны! Когда вы комбинируете два объекта с помощью Object.assign, первый объект в списке аргументов подвержен мутациям. Другие — нет.

console.log(smallCupWithEar);
// {
//   volume: 500,
//   hasEar: true
// }

console.log(largeCup);
// {
//   volume: 500
// }

?Решение проблемы мутации при использовании Object.assign


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

const smallCupWithEar = {
  volume: 300,
  hasEar: true
};

const largeCup = {
  volume: 500
};

// Использование нового объекта в качестве первого аргумента
const myIdealCup = Object.assign({}, smallCupWithEar, largeCup);

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

myIdealCup.picture = "Mickey Mouse";
console.log(myIdealCup);
// {
//   volume: 500,
//   hasEar: true,
//   picture: "Mickey Mouse"
// }

// smallCupWithEar не мутирует
console.log(smallCupWithEar); // { volume: 300, hasEar: true }

// largeCup не мутирует
console.log(largeCup); // { volume: 500 }

?Object.assign и ссылки на объекты-свойства


Ещё одна проблема с Object.assign заключается в том, что он выполняет поверхностное слияние объектов (shallow merge) — он копирует свойства напрямую из одного объекта в другой. При этом он копирует и ссылки на объекты, являющиеся свойствами обрабатываемых объектов.

Рассмотрим это на примере.

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

const defaultSettings = {
  power: true,
  soundSettings: {
    volume: 50,
    bass: 20,
    // другие параметры
  }
};

Некоторые из ваших друзей любят громкую музыку, поэтому вы решили сделать предустановку, которая гарантированно поставит на уши весь дом.

const loudPreset = {
  soundSettings: {
    volume: 100
  }
};

Затем вы приглашаете друзей на вечеринку. Для того чтобы привести систему в рабочее состояние и при этом воспользоваться и стандартными настройками, и теми, где громкость выкручена на максимум, вы пытаетесь скомбинировать defaultSettings и loudPreset.

const partyPreset = Object.assign({}, defaultSettings, loudPreset);

Однако, включив музыку, вы понимаете, что система с partyPreset звучит странно. Громкость хороша, но совсем нет басов. Когда вы исследуете partyPreset, вы с удивлением обнаруживаете, что настроек баса тут нет!

console.log(partyPreset);
// {
//   power: true,
//   soundSettings: {
//     volume: 100
//   }
// }

Это происходит из-за того, что JavaScript копирует объект-свойство soundSettings по ссылке. Так как и у defaultSettings, и у loudPreset есть объект soundSettings, тот объект, который стоит правее в аргументах Object.assign, оказывается скопированным в новый объект.

Если вы измените partyPreset, loudPreset мутирует соответствующим образом — как свидетельство того, что в него была скопирована ссылка на soundSettings из loudPreset.

partyPreset.soundSettings.bass = 50;

console.log(loudPreset);
// {
//   soundSettings: {
//     volume: 100,
//     bass: 50
//   }
// }

Так как Object.assign выполняет поверхностное слияние объектов, в подобных ситуациях, когда новый объект является комбинацией объектов, содержащих объекты-свойства, нужно использовать что-то другое. Что? Например — библиотеку assignment.

?Библиотека assignment


Assignment — это маленькая библиотека, которую создал Николя Бевакуа из Pony Foo (ценного источника информации по JS). Она помогает выполнять глубокое слияние объектов (deep merge) и при этом не беспокоиться о мутациях. Использование assignment выглядит так же, как и работа с Object.assign, за исключением того, что тут используется другое имя метода.

// Выполнение глубокого слияния объектов с помощью assignment
const partyPreset = assignment({}, defaultSettings, loudPreset);

console.log(partyPreset);
// {
//   power: true,
//   soundSettings: {
//     volume: 100,
//     bass: 20
//   }
// }

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

Если вы попытаетесь теперь изменить любое свойство в partyPreset.soundSettings, вы обнаружите, что loudPreset не меняется.

partyPreset.soundSettings.bass = 50;

// loudPreset не мутирует
console.log(loudPreset);
// {
//   soundSettings {
//     volume: 100
//   }
// }

Библиотека assignment — это лишь один из многих инструментов, позволяющих выполнять глубокое слияние объектов. Другие библиотеки, включая lodash.assign и merge-options, тоже могут вам в этом помочь. Можете спокойно выбрать ту, что вам больше понравится.

Всегда ли необходимо использовать глубокое слияние вместо Object.assign?


Так как теперь вы знаете, как защитить объекты от мутаций, вы можете осмысленно использовать Object.assign. Нет ничего плохого в этом стандартном методе, если знать, как пользоваться им правильно.

Однако, если вам нужно работать с объектами, которые имеют вложенные свойства, всегда старайтесь использовать глубокое слияние объектов вместо Object.assign.

Обеспечение иммутабельности объектов


Хотя те методы, о которых мы говорили выше, могут помочь защитить объекты от мутаций, они не гарантируют иммутабельность созданных с их помощью объектов. Если вы сделаете ошибку и используете Object.assign при работе с объектом, имеющим вложенные свойства-объекты, позже у вас могут быть серьёзные неприятности.

Для того чтобы от этого защититься, стоит обеспечить гарантию того, что объект не будет мутировать вообще. Для этого можно использовать библиотеку наподобие ImmutableJS. Эта библиотека выдаёт ошибку при попытке изменения обработанного с её помощью объекта.

Кроме того, можно использовать метод Object.freeze и библиотеку deep-freeze. Эти два средства не выдают ошибок, но и не позволяют объектам мутировать.

Метод Object.freeze и библиотека deep-freeze


Метод Object.freeze защищает собственные свойства объекта от изменений.

const egg = {
  name: "Humpty Dumpty",
  isBroken: false
};

// "Заморозим" объект egg
Object.freeze(egg);

// Попытка изменения свойства потерпит неудачу без сообщений об ошибках
egg.isBroken = true;

console.log(egg); // { name: "Humpty Dumpty", isBroken: false }

Однако этот метод не поможет, если попытаться изменить объект, являющийся свойством «замороженного» объекта, вроде defaultSettings.soundSettings.base.

const defaultSettings = {
  power: true,
  soundSettings: {
    volume: 50,
    bass: 20
  }
};
Object.freeze(defaultSettings);
defaultSettings.soundSettings.bass = 100;

// Несмотря на это soundSettings мутирует
console.log(defaultSettings);
// {
//   power: true,
//   soundSettings: {
//     volume: 50,
//     bass: 100
//   }
// }

Для предотвращения мутации объектов-свойств, можно использовать библиотеку deep-freeze, которая рекурсивно вызывает Object.freeze для всех свойств «замораживаемого» объекта, являющихся объектами.

const defaultSettings = {
  power: true,
  soundSettings: {
    volume: 50,
    bass: 20
  }
};

// Выполнение "глубокой заморозки" (после подключения библиотеки deep-freeze)
deepFreeze(defaultSettings);

// Попытка изменения вложенных свойств не удастся, сообщений об ошибках не возникнет
defaultSettings.soundSettings.bass = 100;

// soundSettings больше не мутирует
console.log(defaultSettings);
// {
//   power: true,
//   soundSettings: {
//     volume: 50,
//     bass: 20
//   }
// }

О перезаписи значений и мутации


Не стоит путать запись в переменные и в свойства объектов новых значений с мутацией.
Когда в переменную записывают новое значение, фактически, изменяют то, на что она указывает. В следующем примере значение переменной a меняется с 11 на 100.

let a = 11;
a = 100;

При мутации же меняется сам объект. Ссылка на объект, записанная в переменную или константу, остаётся той же самой.

const egg = { name: "Humpty Dumpty" };
egg.isBroken = false;

Итоги


Мутации опасны потому, что они могут нарушить работу кода, причём, сделать это совершенно незаметно и непредсказуемо. Если даже вы подозреваете, что причина проблемы в мутации, поиск проблемного места — та ещё задачка. Поэтому лучший способ защитить код от неприятных неожиданностей — это обеспечить, с момента создания объектов, их защиту от мутаций.

Для того, чтобы защитить объекты от мутаций, можно использовать библиотеки вроде ImmutableJS и Mori.js, или применять стандартные методы JS Object.assign и Object.freeze.

Обратите внимание на то, что методы Object.assign и Object.freeze могут защитить от изменений только собственные свойства объектов. Если нужно защитить от мутаций и свойства, которые сами являются объектами, понадобятся библиотеки вроде assignment или deep-freeze.

Уважаемые читатели! Сталкивались ли вы с неожиданными ошибками в JS-приложениях, вызванными мутациями объектов?

Комментарии (35)


  1. vasIvas
    19.01.2018 12:30

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


    1. zxxc
      19.01.2018 13:09

      const egg = { name: "Humpty Dumpty" };
      egg.isBroken = false;

      Такой подход в целом и общем делает код медленнее и часто может приводить к неожиданным последствиям в будущем
      Избегая такого код разработка станет дешевле в долгосрочной перспективе


      1. vasIvas
        19.01.2018 13:38

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


        1. vanxant
          19.01.2018 14:11

          Или авторы плагинов


          1. vasIvas
            19.01.2018 15:11

            Тогда стоит добавить что авторов плагинов для плохо продуманных библиотек.
            Зачем делать объекты динамическими, когда можно создать основу конфигурации в виде класса, наделив его базовым состояние, а авторам плагинов позволить его расширять?


            1. vanxant
              19.01.2018 15:29

              Думать сейчас некогда. Херак херак и в продакшн Везде скрам cd аджайл


  1. Mycolaos
    19.01.2018 12:43

    Когда мы меняем newEgg (подвергаем объект мутации), автоматически меняется и egg. Вы знали об этом?

    Я думал, каждый кто изучает джаваскрипт это узнает в первую очередь. Не?


  1. dmitry_pacification
    19.01.2018 13:37

    Что если использовать… (spread) оператор? В Redux рекомендуют изменять данные в редьюсерах с помощью Object.assign или этого оператора. Он иммутабельный или нет?


    1. khmm12
      19.01.2018 13:47

      Spread оператор это синтаксический сахар над Object.assign

      const newEntity = { ...original, ...mutation }
      

      Эквивалентно
      const newEntity = Object.assign({}, original, mutation)
      


      1. shadek
        21.01.2018 17:21

        MDN c вами не согласен, все таки спектр применения Spread несколько шире.
        developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Spread_operator


  1. Farxial2
    19.01.2018 14:04

    Или можно просто помнить о том, что объекты в JavaScript передаются по ссылке.
    Вообще, на мой взгляд, это лучший подход в данной области. Представьте, что будет, если веб-разработчики, в целях лучшей стабильности, будут копировать все объекты. Никто не говорит о том, что все будут следить за тем, что копировать, а что нет, т.к. проще получить «по шапке» за некорректную работу программы, чем за её прожорливость. А ведь это всего лишь сценарий на веб-странице, которых у пользователя может быть открыт не один десяток. W3C мог бы создать нативные методы глубокого копирования, если бы счёл нужным.
    [UPD] Упс, я забыл о рекурсиях и объектах типа элементов DOM. > +2 сложности


    1. mayorovp
      19.01.2018 15:22

      Фразу "объекты в JavaScript передаются по ссылке" способен произнести только тот программист, который никогда не работал с другими языками, где и правда существует передача по ссылке...


      1. Zenitchik
        19.01.2018 15:52

        Вы про передачу аргументов по ссылке?
        Да, согласен, в функцию даже ссылка на объект передаётся по значению (значением переменной объектного типа является ссылка).
        А как правильно сформулировать процитированную мысль?


        1. mayorovp
          19.01.2018 16:07

          «Объекты относятся к ссылочному типу данных» или как-то так. Ну или «свойства объектов-параметров доступны по ссылке».


  1. inoyakaigor
    19.01.2018 14:07

    del


  1. Lain_13
    19.01.2018 17:20

    Боже, эта любовь к зависимостям…
    deepFreeze:


    const deepFreeze = o => {
        Object.freeze(o);
        return Object.getOwnPropertyNames(o).every(
            p => o.hasOwnProperty(p) && o[p] instanceof Object && !Object.isFrozen(o[p]) ? deepFreeze(o[p]) : true
        );
    };

    Можно ещё сразу же: deepFreeze(deepFreeze);


    1. mayorovp
      19.01.2018 18:58

      Зачем вы проверяете hasOwnProperty?


      Кстати, проверка o[p] instanceof Object слишком опасная. Можно случайно вмешаться во внутреннюю структуру сложного класса и все поломать. Лучше проверять на Object.getPrototypeOf(o[p]) === Object.prototype.


      Кстати, o[p] может оказаться вычисляемым свойством. Лучше получать дескриптор через Object.getOwnPropertyDescriptor и проверять его value.


      1. Lain_13
        19.01.2018 21:03

        Зачем вы проверяете hasOwnProperty?

        А и правда, в сочетании с getOwnPropertyNames оно явно лишнее.


        А вот про instanceof можно поподробнее? Впрочем, на сколько я понимаю проверять прототип тоже не самая здравая идея:
        http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
        Если в том примере с xArray добавить Object.getPrototypeOf(arr) === Array.prototype, то тоже будет false. Тогда уже лучше o !== null && typeof o === 'object', наверное?


        Т.е. полный варианту будет выглядеть как-то так:


        const deepFreeze = o => {
            Object.freeze(o);
            Object.getOwnPropertyNames(o).forEach(
                (p, i, o) => {
                    let d = Object.getOwnPropertyDescriptor(o, p);
                    if (d && d.value !== null &&
                        typeof d.value === 'object' &&
                        !Object.isFrozen(d.value))
                        deepFreeze(d.value);
                }
            );
            return o;
        };


        1. mayorovp
          19.01.2018 21:05

          Попробуйте применить ваш код к объекту Date или какому-нибудь HTMLElement… Или к модели mobx, компоненту React или еще чему-нибудь подобному.

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


          1. Lain_13
            19.01.2018 22:05

            Если учесть, что с прототипами легко намухлевать:
            medium.com/javascript-scene/common-misconceptions-about-inheritance-in-javascript-d5d9bab29b0a
            То лучше сразу сказать, что идея универсального deepFreeze заранее обречена на провал.


  1. k12th
    19.01.2018 18:29

    Камон, если бы не React, который несмотря на название, совсем не реактивный, про иммутабельность никто бы и не вспомнил. И даже React с Mobx не нуждается в иммутабельных структурах.


    Не мутации страшны, а неконтролируемые и неотслеживаемые мутации. vuex предупреждает разработчика — «не меняй состояние вручную», а в redux можно запросто это сделать и полдня потом дебажить, «какая сволочь стреляла».


  1. Eika
    19.01.2018 19:06

    Еще есть Object.seal.
    Он такой же как freeze, только позволяет изменения существующим ключам.


  1. MikailBag
    19.01.2018 20:58

    Для полноты картины отмечу, что персистентные структуры данных сильно сложнее в написании и отладке, чем свои "рядовые" "братья". В лучшем случае они работают медленнее лишь в константу раз. Но проигрыш в скорости (например я не слышал о персистентном списке работающем за O(1)) может быть на больших объемах данных может быть очень серьезным.
    Таким образом, не стоит пихать такие структуры туда, где идет много вычислений.


    1. mayorovp
      19.01.2018 21:08

      Вот список как раз — классика персистентных структур. { head: ..., tail: ...}


      За O(1) у него работают операции "добавить элемент в начало" и "удалить первый элемент". Итерация по списку делается удалением всех элементов.


      1. MikailBag
        19.01.2018 21:24

        Вы привели пример персистентного стека, и он действительно работает за O(1).
        Я же имею в виду обычный список типа массива, т.е. чтение/запись в произвольные места.
        Лучшее, что мне известно в этой области — персистентные деревья, с запросами за O(log N) и большой константой.
        А плюшки, которые дает персистентность (много версий струкутры с возможностью одновременной работы с ними), в типичном веб-приложении вряд ли нужны (например в Redux хранилище единственно, т.о. версия нужна одна).


  1. vintage
    20.01.2018 09:28

    1. Object.freeze, Object.assign, ImmutableJS и прочие такие штуки серьёзно так замедляют работу приложения. Я бы рекомендовал воспользоваться TypeScript, который не позволит вам динамически изменить сигнатуру объекта или изменить readonly свойство. При этом ещё на стадии написания кода, а не в рантайме и соответственно без замедления исполнения.


    2. Объекты можно условно разделить на два типа: значения и контейнеры. Беда JS в том, что, например, один и тот же Array выступает и в роли контейнера (push, pop, ...) и в качестве значения (map, filter, ...).


    3. Никаких опасностей мутыций вы не продемонстрировали. Только описали азы языка и назвали их "опасными мутантами". Кстати, тот самый синий и волосатый монстр, о котором вы говорили, — весьма душевный человек и проницательный собеседник :-)


    1. abyrkov
      20.01.2018 15:35
      -1

      1. Решать проблемы JS перейдя на TS это так же как решать проблемы C++ перейдя на Java. Не всегда это оправданно


      1. Zenitchik
        20.01.2018 15:43

        Согласен. Лучше просто знать язык и писать нормально. Хорошему программисту мутабельность не мешала никогда. Не хочешь изменять — не изменяй.


      1. mayorovp
        20.01.2018 16:39

        Нет, это как решать проблемы Си перейдя на С++.


        1. abyrkov
          20.01.2018 20:39

          Вот это хороший пример, потому, что у С++ есть некоторые Сишные проблемы


      1. k12th
        20.01.2018 18:27

        Java не компилируется в C++.


        1. abyrkov
          20.01.2018 20:33

          А это разве влияет на мое утверждение, что переход ради решения одной (зачастую несущественной) проблемы конкретным путем, не всегда целесообразно переходить на другой язык?


          1. abyrkov
            20.01.2018 22:17

            Очень интересно узнать, за что минусуют то? Я же не предлагаю писать все и всия на JS, а просто не писать на TS там, где он избыточен


  1. PYXRU
    20.01.2018 18:58

    На этой вашей мутабельность держиться большинство фраемворков(angular), тот же Vue биндит реактивные свойства через замыкание по сути вообще костыль для избежания рекурсии, и таких приемов очень много. Проблема кажется более надуманная, поскольку большинство знают что не примитивные типы передаются по ссылке. Если для вас это действительно проблема Object.defineProperty или typescript вам в помощь.


  1. rockon404
    22.01.2018 13:29

    Странно, что в статье не упомянули spread оператор:

    const obj = { foo: 'bar' };
    const objCopy = { ...obj };
    

    Он код с его использованием понятен и лаконичен. Пусть он и является синтаксическим сахаром над Object.assign.