CONST в JS делает свою работу правильно!

Разбираемся в сути и предназначении

На собеседовании фронтедеров частенько можно услышать вопрос, в чем разница между 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

Кстати, в ООП мире, функции, создающие новые объекты называются фабриками. Но это уже другая история и другая часть собеседования ;)


Лайк, хлопок, шер. Слушайте меня в iTunes, подписывайтесь на Телеграм канал или Вконтакте. :)