Одним из вводящих в заблуждение вариантов использования для новичков в JavaScript является создание константных массивов и объектов. С введением ES6 у нас появилось два новых декларатора: let
для изменяемых переменных и const
для констант. Многие новички считают, что это сделает их объекты и массивы неизменяемыми, чтобы позже обнаружить, что это не так. Неизменен сам объект или массив, но не их содержимое. До сих пор мы полагались на метод Object.freeze
для решения этого варианта использования.
// This is supposed to be immutable, isn't it? const obj = { a: 1 } obj.a = 2 console.assert(obj.a === 1, 'wtf') // Assertion failed: wtf
Знакомство с записью и кортежами.
Записи представляют собой неизменяемые массивы. Кортежи — это неизменяемые объекты.Они совместимы с методами Object и Array. По сути, вы можете удалить Tuple или Record в любом методе, который принимает объект или массив, и он будет вести себя так, как ожидалось, если только это не подразумевает изменение элемента. Это относится к методам стандартной библиотеки, итераторам и т.д.
// Record
const record = #{ x: 1, y: 2 }
// Tuple
const tuple = #[1, 2, 3, 4]
// We can use most of the methods that work with Arrays and Objects.
console.assert(tuple.includes(1) === true, 'OK, it will not print')
// Although they will return tuples and records.
console.assert(Object.keys(record) === #['x', 'y'], 'OK, it will not print')
// Iterators work too
for (const element of tuple) {
console.log(element)
}
// 1
// 2
// 3
// 4
// And you can nest them!
const nested = #{
a: 1,
b: 2,
c: #[1, 2, 3]
}
// Nope
tuple.map(x => doSomething(x));
// TypeError: Callback to Tuple.prototype.map may only return primitives, Records or Tuples
// This is ok
Array.from(tuple).map(x => doSomething(x))
Однако с большой силой приходит большая ответственность.
Сила
- Сравнение по значению. Как и другие простые примитивные типы, они сравниваются по значению, а не по идентификатору. Объекты и массивы равны, если они являются одной и той же сущностью. Кортежи и записи равны, если они содержат одни и те же элементы.
const objA = {a: 1} const objB = {a: 1} const objC = objA console.assert(objA === objB, 'Same content, but different entities, false') console.assert(objA === objC, 'They are the say, it will not print') const recordA = #{a: 1} const recordB = #{a: 1} const recordC = recordA console.assert(recordA === recordB, 'OK, will not print') console.assert(recordA === recordC, 'OK, will not print')
- Вы можете преобразовать в объекты и массив и наоборот: Используя функции
Record()
иTuple.from()
.
const obj = { ...#{a: 1, b: 2}} const record = Record({a:1, b:2}) const arr = [ ...#[1, 2, 3]] const tuple = Tuple.from([1, 2, 3])
- Они идентифицируются как отдельные типы: использование оператора
typeof
возвращает уникальные имена для каждого из них.
console.assert(typeof #{a: 1} === 'record', 'this will not print') console.assert(typeof #[1, 2] === 'tuple', 'this will not print')
Ответственность
- Они могут содержать только примитивные типы: они могут содержать только String, Number, Boolean, Symbol, BigInt, undefined, null, Record и Tuple. Это, никаких функций, объектов, массивов, классов и т.д.
- Вы можете использовать их в картах и наборах, но не с WeakMaps и WeakSets:Цитата из спецификации
Можно использовать
Record
илиTuple
как ключ вMap
и как значение вSet
. При использовании здесьRecord
илиTuple
они сравниваются по значению.
Невозможно использовать
Record
илиTuple
в качестве ключа вWeakMap
или в качестве значения вWeakSet
, потому чтоRecords
иTuple
не являютсяObjects
, и их время жизни невозможно наблюдать.
JSON.stringify
будет работать, как и ожидалось, ноJSON.parse
по-прежнему будет возвращать объекты и массивы: есть предложение добавитьJSON.parseImmutablee
, который будет вести себя какJSON.parse
, но будет возвращать записи и кортежи вместо массивов и объектов.
Вывод
Это дополнение приветствуется, так как определить неизменяемые значения в JavaScript было непросто, и это сбивает с толку многих новичков. Предыдущие решения подразумевали использование внешних библиотек, таких как Immutable.js, обходных путей в стандартной библиотеке, таких как Object.freeze
, или соглашений для достижения аналогичных результатов.
В настоящее время предложение находится на стадии 2, поэтому в него могут быть внесены изменения. Тем не менее, он уже выглядит солидно, и я лично надеюсь, что он пройдет и станет стандартом.
использованная литература
- Спецификация предложения [github, web]
- Детская площадка (осторожно, текущая спецификация реализована не полностью)
- "Руководство"
- "Поваренная книга"