Разбираемся в сути и предназначении
На собеседовании фронтедеров частенько можно услышать вопрос, в чем разница между var, let и const. А далее вопрос могут раскрутить до вопроса: а покажите, как сделать неизменяемый объект. Вопрос вполне хороший и имеет право на жизнь.
Но в сети так же периодически можно услышать фразы и увидеть статьи о том, что const в JS не работает и что не нужно его использовать. И приводится пример, наподобие этого:
const obj = { foo: 123 }
obj.foo = 456; // все прекрасно меняется
Ну вообще-то да, поле объекта меняется. И должно меняться. Не меняется ссылка на значение. В данном случае ссылка на объект. Поля объекта меняются и должны меняться иначе это было бы странно. Это не бага JS! Все работает правильно.
Константа — способ адресации данных, изменение которых не предполагается или запрещается.
Для чего используется слово const в JS?
Usecase 1# Для описания констант.
Значения констант должны быть примитивы. Если вы хотите зафиксировать некоторые величины, которые не должны меняться на протяжении программы — вы описываете их как константы.
Такими величинами могут быть числа и строки. Т.е. примитивы. Есть исключения, но о них чуть позже. В JS уже есть встроенные константы. Например число Пи(Math.PI). Хотите записать тау (2PI)? Запишите:
const PI = Math.PI
const TAU = PI * 2
Эти значения будут константными и неизменными. Все работает как и положено. Хотите сохранить строковые величины? Пожалуйста. Нужно записать флаг, например завести константы DEBUG? Да пожалуйста. Все ваши константы будут работать как надо:
const VERSION = 'My Framework 1.0.5'
const DEBUG = true
В качестве константы нужно и правильно использовать примитивы. В некоторых языках программирования на уровне компилятора/интерпретатора не разрешено создавать константы из чего-то, кроме примитивов. Не должно быть констант объектов. Константы должны быть простыми типами — примитивами. Но есть исключения.
Исключения для const
Usecase #2. Для описания неизменяемых объектов Function
Вопрос на собеседовании: как отработает этот код?
function foo(){ return 42 }
function foo(){ return 43 }
console.log( foo() );
В JS можно переопределять уже созданные функции. Это значит что вашу ранее объявленную функцию можно не просто переопределить, ее вовсе можно “прибить”. И сделать это можно даже чисто случайно. Например правите вы такой вот файл и…
edit SomeApp.js
Line | Code
-----+----------------------------------
1 | // Some JS file
| ...
|
128 | function foo(){ return 42 }
|
| ...
512 |
513 | var foo = 456;
514 |
| ...
|
1024 | foo() // error!!! foo = 456
Наверняка кто-то из вас сталкивался с чем-то подобным. Этот пример утрирован, но он реален и имеет место быть в практике.
Кстати, почему так произошло — отдельная тема, связанная с всплытием переменных(hoisting). Про это поговорим в ближайшее время.
Что можно было сделать? Как вариант — использовать функциональные выражения:
const foo = function(){ return 42 }
// или
const foo = () => 42
Писать стрелочную функцию или нет — уже дело выбора и предназначения (нужно ли будет менять контекст или нужна привязка).
В данном случае foo — это объект. И у него есть даже поля, например вы можете получить имя функции:
console.log( foo.name ) // "foo"
Попробуйте изменить эту функцию или этот объект. Не получается?
Как создать константный объект?
Продолжаем наше импровизированное собеседование.
Если уж вам все-таки ну очень приспичило создать объект-константу, то тут мы все так же используем слово const но(!) добавляем такую штуку как Object.freeze().
Метод Object.freeze() замораживает объект. Это значит, что он предотвращает добавление новых свойств к объекту, удаление старых свойств из объекта и изменение существующих свойств или значения их атрибутов перечисляемости, настраиваемости и записываемости. В сущности, объект становится неизменным. Метод возвращает замороженный объект.
Видите, для заморозки объектов есть отдельный специальный механизм, который и делает наш любимый JS таким гибким. И если вы хотите создать констату-объект, то вы пишите следующий код:
const immutableObject = Object.freeze({
foo: 123,
bar: 'buz'
});
immutableObject = {} // error
immutableObject.foo = 456 // not alllowed
immutableObject.bebebe = 'abc' // not allowed
Все прекрасно работает. Но создавать неизменяемый объект это прерогатива других механизмов, а не ключевого слова const.
Нужно запомнить, что слово
CONST — только для неизменяемой ссылки на ячейку памяти со значением.
И это правильная нормальная работа. И я не понимаю почему у некоторых бомбит и они пишут статьи на тему неработающего const.
И линтеры не должны вам ничего говорить и указывать, если вы указали константную ссылку на объект. Ссылка на объект и сам объект — это две разные сущности. Изменяемость объекта, как и передача его по ссылке — это вообще отдельная тема. А const делает свою работу прекрасно и правильно! И здорово что у нас есть такая гибкость, в отличие от других языков, и мы можем отдельно управлять иммутабельностью объектов и ссылок на объекты.
Если же продолжить собеседование, то можно уточнить про то, весь ли объект будет иммутабельным. А точнее будут ли дочерние элементы неизменяемыми?
const foo = Object.freeze({
bar: 1,
deep: {
a: {
b: {
c: 1
}
}
}
});
foo.bar = 456; // freeze
foo.deep.a.b.c = 2; // mutate
console.dir(foo);
Да, Object.freeze() не делает глубокой заморозки. Но это все решаемо. Либо пишите все на ванильном JS либо используете специализированные библиотеки.
Так как это импровизированное собеседование, то давайте решим задачу на чистом JS:
const immutable = (obj) =>
(
Object.freeze(obj),
(
(void 0 === obj) ? obj :
(
Object.getOwnPropertyNames(obj)
.forEach(
(prop) =>
(
!Object.isFrozen(obj[prop]) &&
(
typeof obj[prop] == "object" ||
typeof obj[prop] == "function"
)
)
&& immutable(obj[prop])
),
obj
)
)
);
Вот так может быть реализована функция, порождающая неизменяемый объект. Да да да, много скобочек. Люблю, знаете ли, скобочки. Во мне умер lisp-программист на пару с perl программистом 🙂
Пример использования:
const foo = immutable({
bar: 1,
deep: {
a: {
b: {
c: 1
}
}
}
});
foo.bar = 456; // freeze
foo.deep.a.b.c = 2; // freeze
Кстати, в ООП мире, функции, создающие новые объекты называются фабриками. Но это уже другая история и другая часть собеседования 😉