Записи и кортежи — это новые неизменяемые типы данных JavaScript, которые сейчас находятся на втором этапе процесса утверждения стандартов TC39. Они могут быть изменены и в настоящее время недоступны ни в одном браузере или среде выполнения, но рабочие реализации должны появиться в течение следующего года. Они помогают решить пару запутанных головоломок, с которыми сталкиваются кодеры...
Постоянные изменения
Профессиональные программисты JavaScript скажут вам, что присваивание переменных с помощью const— это лучшая практика, где это возможно. Это делает переменные неизменяемыми. Значения нельзя изменить, поэтому у вас будет меньше проблем.
К сожалению, constделает неизменяемыми только примитивные значения (String, Number, BigInt, Boolean, Symbol и т. д undefined.). Вы не можете переназначить массив или объект, но можно изменить содержащиеся в них значения и свойства. Например:
// array constant
const myArray = [1, 2, 3];
// change array values
myArray[0] = 99;
myArray.push (42) ;
console.log (myArray) ; // [ 99, 2, 3, 42 ]
myArray = 'change’; // ERROR!
Аналогично для объектов:
// object constant
const myObj = { a: 1, b: 2, c: 3 }
// change object properties
myObj.a = 99;
myObj.d = 42;
console.log (myObj) ; // { a:99, b:2, c:3, d:42 }
myObj = 'change’; // ERROR!
Метод Object.freeze () может помочь, но к непосредственным дочерним свойствам объекта применяется только неглубокая заморозка:
const myObj = { a: 1, b: 2, c: { v: 3 } }
Object.freeze (myObj) ;
myObj.a = 99; // silently ignored
myObj.c.v = 99; // works fine
console.log (myObj) ; // { a: 1, b: 2, c: { v: 99 } }
Поэтому трудно гарантировать, что функция не изменит преднамеренно или случайно значения, хранящиеся в массиве или объекте. Разработчики должны либо надеяться на лучшее, либо передавать клонированную версию переменной — (что имеет свои проблемы).
Эквивалентное неравенство
Дальнейший хаос может возникнуть, когда разработчики попытаются, казалось бы, разумными сравнениями объектов или массивов:
const str = 'my string’;
console.log (str === 'mystring’) ; // true
const num = 123;
console.log (num === 123) ; // true
const arr = [1, 2, 3];
console.log (arr === [1, 2, 3]) ; // false
const obj = { a: 1 };
console.log (obj === { a: 1 }) ; // false
Сравнивать по значению можно только примитивные типы. Объекты и массивы передаются и сравниваются по ссылке. Две переменные будут эквивалентны, только если они указывают на один и тот же элемент в памяти:
const a = [1, 2];
const b = a;
b.push (3) ;
console.log (a === b) ; // true
// original array has changed
console.log (a) ; // [1, 2, 3]
Для глубокого сравнения двух объектов или массивов требуется функция рекурсивного сравнения для оценки каждого значения по очереди. Даже в этом случае вы можете столкнуться с проблемами с такими типами, как даты или функции, которые могут храниться
Кортежи: неизменяемые структуры данных, подобные массивам
Кортежи — это глубоко неизменяемые структуры данных, подобные массивам. По сути, это составные примитивные типы, идентифицируемые #модификатором перед синтаксисом обычного массива:
// new tuples
const t1 = #[1, 2, 3];
const t2 = #[1, 2, #[3, 4]];
В качестве альтернативы новый Tuple.from () метод может создать кортеж из массива:
// new tuple from an array
const t3 = Tuple.from ([1, 2, 3]) ;
В отличие от стандартных массивов кортежи должны удовлетворять следующим требованиям:
В них не должно быть отверстий с неустановленными значениями. Например, #[1, 4]является недействительным.
Они должны устанавливать только примитивы, другие кортежи или записи. Такие типы, как массивы, объекты или функции, не разрешены:
const t4 = #[ new Date () ]; // ERROR (sets an object)
const t5 = #[1, 2, [3, 4]]; // ERROR (sets an array)
Поскольку кортежи являются примитивами, их можно глубоко сравнивать по значению с другими кортежами:
const t6 = #[1, 2];
console.log (t6 === #[1, 2]) ; // true
Обратите внимание, что сравнения с использованием менее строгого ==оператора возможны, если кортеж содержит одно значение. Например:
const t7 = #[99];
console.log (t7 == #[99]) ; // true
console.log (t7 == 99) ; // true
console.log (t7 == '99') ; // true
// tuple cannot be compared to an array
console.log (t7 == [99]) ; // false
Записи: неизменяемые
Записи — это глубоко неизменяемые
// new records
const r1 = #{ a: 1, b: 2 };
const r2 = #{
a: 1,
b: #{ c: 2 }, // child record
d: #[ 3, 4 ] // child tuple
};
Кроме того, новый Record () конструктор может создать запись из объекта:
// new record from an object
// #{ a: 1, b: 2 }
const r3 = Record ({ a: 1, b: 2 }) ;
Или Record.fromEntries () метод может создать запись из серии пар значений массива или кортежа:
// new record from array of
// #{ a: 1, b: 2 }
const r4 = Record.fromEntries ([
['a’, 1],
['b’, 2]
]) ;
В отличие от стандартных объектов, записи должны соответствовать следующим требованиям:
Они должны использовать строковые имена свойств. Например, #{ Symbol (): 1 }является недействительным.
Они должны устанавливать значения только с использованием примитивов, других кортежей или записей. Такие типы, как массивы, объекты или функции, не разрешены:
const r5 = #{ 'd’: new Date () }; // ERROR (sets an object)
const r6 = #{ a: 1, b: { c: 2 } }; // ERROR (sets an object)
Записи можно глубоко сравнивать с другими записями, и порядок свойств не имеет значения:
const r7 = #{ a: 1, b: 2 };
console.log (r7 === #{ b: 2, a: 1 }) ; // true
Записи можно сравнивать только с другими записями, поэтому использование оператора ==or ===не имеет значения. Однако можно извлечь объект keys () и values () для конкретных сравнений. Например:
const r8 = #{ a: 99 };
console.log (Object.values (r8) == 99) ; // true
Неизменяемые обновления
Кортежи и записи могут звучать как сложные термины информатики, но они, наконец, позволят надежное неизменяемое хранилище данных и сравнения в JavaScript. Вы можете опробовать их сегодня на этой игровой площадке или с этим полифиллом, но имейте в виду, что предлагаемая реализация может измениться в ближайшие месяцы.