Мутация — это изменение. Изменение формы или изменение сути. То, что подвержено мутациям, может меняться. Для того чтобы лучше осознать природу мутации — подумайте о героях фильма «Люди Икс». Они могли внезапно получать потрясающие возможности. Однако проблема заключается в том, что неизвестно, когда именно эти возможности проявятся. Представьте себе, что ваш товарищ ни с того ни с сего посинел и оброс шерстью. Страшновато, правда? В JavaScript существуют те же проблемы. Если ваш код подвержен мутациям, это значит, что вы можете, совершенно неожиданно, что-то изменить и поломать.
egg
, объект, мутирует после того, как к ней добавляют свойство isBroken
. Такие объекты (вроде egg
) мы называем мутабельными (то есть, имеющими возможность мутировать, изменяться).const egg = { name: "Humpty Dumpty" };
egg.isBroken = false;
console.log(egg);
// {
// name: "Humpty Dumpty",
// isBroken: false
// }
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"
// }
false
.console.log({} === {}); // false
egg
была присвоена константе newEgg
, в newEgg
была записана ссылка на тот же объект, на который ссылалась константа egg
. Так как egg
и newEgg
ссылаются на один и тот же объект, то, когда меняется newEgg
, egg
меняется автоматически.console.log(egg === newEgg); // true
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 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
позволяет комбинировать два объекта (или большее число объектов), получая на выходе один новый объект. Пользоваться ей можно так: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
можно передать новый объект для того, чтобы предотвратить мутацию существующих объектов. Однако, первый объект (пустой) всё ещё подвергается изменениям, но тут нет ничего страшного, так как мутация больше ничего важного не затрагивает.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
заключается в том, что он выполняет поверхностное слияние объектов (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
// }
// }
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
выглядит так же, как и работа с 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.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
// }
// }
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;
Object.assign
и Object.freeze
.Object.assign
и Object.freeze
могут защитить от изменений только собственные свойства объектов. Если нужно защитить от мутаций и свойства, которые сами являются объектами, понадобятся библиотеки вроде assignment или deep-freeze.К сожалению, не доступен сервер mySQL