Одним из вводящих в заблуждение вариантов использования для новичков в 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]
  • Детская площадка (осторожно, текущая спецификация реализована не полностью)
  • "Руководство"
  • "Поваренная книга"