Авторы:
Чемпионы:
Советники
Стадия: 2
Record
и Tuple
Это предложение вводит две новые глубоко неизменные структуры данных в JavaScript:
Record
, глубоко неизменная объектно-подобная структура #{ x: 1, y: 2 }
Tuple
, глубоко неизменная массива, подобная структуре #[1, 2, 3, 4]
Записи и кортежи могут содержать только примитивы и другие записи и кортежи. Вы можете думать о записях и кортежах как о «составных примитивах». Будучи тщательно основанным на примитивах, а не объектах, записи и кортежи глубоко неизменны.
Записи и кортежи поддерживают удобные идиомы для строительства, манипуляции и использования, аналогично работе с объектами и массивами. Они глубоко сравниваются по их содержанию, а не их идентичностью.
Двигатели JavaScript могут выполнять определенные оптимизации по строительству, манипулированию и сравнению записей и кортежников, аналогично тому, как строки часто реализуются в двигателях JS. (Следует понимать, что эти оптимизации не гарантированы.)
Записи и кортежины направлены на использование и понятные с помощью внешних типовых засох, таких как TypeScript или поток.
Сегодня пользовательские библиотеки внедряют аналогичные концепции, такие как Immutable.js. Кроме того, предыдущее предложение было предпринято, но заброшено из -за сложности предложения и отсутствия достаточных случаев использования.
Это новое предложение по -прежнему вдохновляется этим предыдущим предложением, но вносит некоторые существенные изменения: записи и кортежи в настоящее время глубоко неизменны. Это свойство в основном основано на наблюдении, что в крупных проектах риск смешивания неизменных и изменяемых структур данных растет по мере роста объема хранимых данных и передачи, так что вы будете более вероятно, что обрабатывает большие записи и структуры чипов. Полем Это может ввести труднодоступные ошибки.
В качестве встроенной, глубоко неизменной структуры данных это предложение также предлагает несколько преимуществ использования по сравнению с библиотеками пользователя:
Immer является заметным подходом к неизменным структурам данных и предписывает шаблон для манипуляций через производителей и редукторов. Однако он не предоставляет неизменные типы данных, поскольку генерирует замороженные объекты. Этот же шаблон может быть адаптирован к структурам, определенным в этом предложении в дополнение к замороженным объектам.
Глубокое равенство, как определено в пользовательских библиотеках, может значительно различаться, частично из -за возможных ссылок на изменяемые объекты. Нарисовав жесткую линию только глубоко содержащих примитивы, записей и кортежей, и повторяя всю структуру, это предложение определяет простую, объединенную семантику для сравнений.
Record
const proposal = # {
id : 1234 ,
title : "Record & Tuple proposal" ,
contents : `...` ,
// tuples are primitive types so you can put them in records:
keywords : # [ "ecma" , "tc39" , "proposal" , "record" , "tuple" ] ,
} ;
// Accessing keys like you would with objects!
console . log ( proposal . title ) ; // Record & Tuple proposal
console . log ( proposal . keywords [ 1 ] ) ; // tc39
// Spread like objects!
const proposal2 = # {
... proposal ,
title : "Stage 2: Record & Tuple" ,
} ;
console . log ( proposal2 . title ) ; // Stage 2: Record & Tuple
console . log ( proposal2 . keywords [ 1 ] ) ; // tc39
// Object functions work on Records:
console . log ( Object . keys ( proposal ) ) ; // ["contents", "id", "keywords", "title"]
Открыт на детской площадке
Функции могут обрабатывать записи и объекты в целом одинаково:
const ship1 = # { x : 1 , y : 2 } ;
// ship2 is an ordinary object:
const ship2 = { x : - 1 , y : 3 } ;
function move ( start , deltaX , deltaY ) {
// we always return a record after moving
return # {
x : start . x + deltaX ,
y : start . y + deltaY ,
} ;
}
const ship1Moved = move ( ship1 , 1 , 0 ) ;
// passing an ordinary object to move() still works:
const ship2Moved = move ( ship2 , 3 , - 1 ) ;
console . log ( ship1Moved === ship2Moved ) ; // true
// ship1 and ship2 have the same coordinates after moving
Открыт на детской площадке
Смотрите больше примеров здесь.
Tuple
const measures = # [ 42 , 12 , 67 , "measure error: foo happened" ] ;
// Accessing indices like you would with arrays!
console . log ( measures [ 0 ] ) ; // 42
console . log ( measures [ 3 ] ) ; // measure error: foo happened
// Slice and spread like arrays!
const correctedMeasures = # [
... measures . slice ( 0 , measures . length - 1 ) ,
- 1
] ;
console . log ( correctedMeasures [ 0 ] ) ; // 42
console . log ( correctedMeasures [ 3 ] ) ; // -1
// or use the .with() shorthand for the same result:
const correctedMeasures2 = measures . with ( 3 , - 1 ) ;
console . log ( correctedMeasures2 [ 0 ] ) ; // 42
console . log ( correctedMeasures2 [ 3 ] ) ; // -1
// Tuples support methods similar to Arrays
console . log ( correctedMeasures2 . map ( x => x + 1 ) ) ; // #[43, 13, 68, 0]
Открыт на детской площадке
Аналогично, чем с записями, мы можем рассматривать кортежи как массивные:
const ship1 = # [ 1 , 2 ] ;
// ship2 is an array:
const ship2 = [ - 1 , 3 ] ;
function move ( start , deltaX , deltaY ) {
// we always return a tuple after moving
return # [
start [ 0 ] + deltaX ,
start [ 1 ] + deltaY ,
] ;
}
const ship1Moved = move ( ship1 , 1 , 0 ) ;
// passing an array to move() still works:
const ship2Moved = move ( ship2 , 3 , - 1 ) ;
console . log ( ship1Moved === ship2Moved ) ; // true
// ship1 and ship2 have the same coordinates after moving
Открыт на детской площадке
Смотрите больше примеров здесь.
Как указывалось до записи и корпуса, глубоко неизменны: попытка вставить в них объект приведет к типам:
const instance = new MyClass ( ) ;
const constContainer = # {
instance : instance
} ;
// TypeError: Record literals may only contain primitives, Records and Tuples
const tuple = # [ 1 , 2 , 3 ] ;
tuple . map ( x => new MyClass ( x ) ) ;
// TypeError: Callback to Tuple.prototype.map may only return primitives, Records or Tuples
// The following should work:
Array . from ( tuple ) . map ( x => new MyClass ( x ) )
Это определяет новые части синтаксиса, добавляемые в язык с этим предложением.
Мы определяем выражение записи или кортежей, используя модификатор #
перед иным образом нормальным объектом или выражением массива.
# { }
# { a : 1 , b : 2 }
# { a : 1 , b : # [ 2 , 3 , # { c : 4 } ] }
# [ ]
# [ 1 , 2 ]
# [ 1 , 2 , # { a : 3 } ]
Отверстия предотвращаются в синтаксисе, в отличие от массивов, которые позволяют отверстия. Смотрите вопрос № 84 для получения дополнительной дискуссии.
const x = # [ , ] ; // SyntaxError, holes are disallowed by syntax
Использование идентификатора __proto__
в качестве свойства предотвращается в синтаксисе. Смотрите выпуск № 46 для получения дополнительной информации.
const x = # { __proto__ : foo } ; // SyntaxError, __proto__ identifier prevented by syntax
const y = # { [ "__proto__" ] : foo } ; // valid, creates a record with a "__proto__" property.
Краткие методы запрещены в синтаксисе записи.
# { method ( ) { } } // SyntaxError
Записи могут иметь только струнные клавиши, а не символы, из -за проблем, описанных в #15. Создание записи с ключом символа является TypeError
.
const record = # { [ Symbol ( ) ] : # { } } ;
// TypeError: Record may only have string as keys
Записи и кортежи могут содержать только примитивы и другие записи и кортежи. Попытка создать Record
или Tuple
, который содержит Object
( null
не является объектом), или Function
бросает TypeError
.
const obj = { } ;
const record = # { prop : obj } ; // TypeError: Record may only contain primitive values
Равенство записей и кортежей работает, как у других примитивных типов JS, таких как логические и строковые значения, сравнение по содержанию, а не идентичности:
assert ( # { a : 1 } === # { a : 1 } ) ;
assert ( # [ 1 , 2 ] === # [ 1 , 2 ] ) ;
Это отличается от того, как работает равенство для объектов JS: сравнение объектов будет отмечать, что каждый объект отличается:
assert ( { a : 1 } !== { a : 1 } ) ;
assert ( Object ( # { a : 1 } ) !== Object ( # { a : 1 } ) ) ;
assert ( Object ( # [ 1 , 2 ] ) !== Object ( # [ 1 , 2 ] ) ) ;
Порядок внедрения ключей записи не влияет на равенство записей, потому что нет способа наблюдать за исходным упорядочением ключей, поскольку они неявно отсортированы:
assert ( # { a : 1 , b : 2 } === # { b : 2 , a : 1 } ) ;
Object . keys ( # { a : 1 , b : 2 } ) // ["a", "b"]
Object . keys ( # { b : 2 , a : 1 } ) // ["a", "b"]
Если их структура и содержимое глубоко идентичны, то значения Record
и Tuple
считаются равными в соответствии со всеми операциями равенства: Object.is
, ==
, ===
и внутренний алгоритм Somevaluezero (используется для сравнения ключей карт и наборов) Полем Они отличаются с точки зрения того, как лечится -0
:
Object.is
рассматривает -0
и 0
как неравные==
, ===
и SomeValuezero Trake -0
с 0
как равные Обратите внимание, что ==
и ===
более прямые в отношении других видов значений, вложенных в записи и кортежей-возврат true
, если и только тогда, когда содержимое идентична (за исключением 0
/ -0
). Эта прямая имеет значение для NaN
, а также сравнения между типами. Смотрите примеры ниже.
Смотрите дальнейшее обсуждение в #65.
assert ( # { a : 1 } === # { a : 1 } ) ;
assert ( # [ 1 ] === # [ 1 ] ) ;
assert ( # { a : - 0 } === # { a : + 0 } ) ;
assert ( # [ - 0 ] === # [ + 0 ] ) ;
assert ( # { a : NaN } === # { a : NaN } ) ;
assert ( # [ NaN ] === # [ NaN ] ) ;
assert ( # { a : - 0 } == # { a : + 0 } ) ;
assert ( # [ - 0 ] == # [ + 0 ] ) ;
assert ( # { a : NaN } == # { a : NaN } ) ;
assert ( # [ NaN ] == # [ NaN ] ) ;
assert ( # [ 1 ] != # [ "1" ] ) ;
assert ( ! Object . is ( # { a : - 0 } , # { a : + 0 } ) ) ;
assert ( ! Object . is ( # [ - 0 ] , # [ + 0 ] ) ) ;
assert ( Object . is ( # { a : NaN } , # { a : NaN } ) ) ;
assert ( Object . is ( # [ NaN ] , # [ NaN ] ) ) ;
// Map keys are compared with the SameValueZero algorithm
assert ( new Map ( ) . set ( # { a : 1 } , true ) . get ( # { a : 1 } ) ) ;
assert ( new Map ( ) . set ( # [ 1 ] , true ) . get ( # [ 1 ] ) ) ;
assert ( new Map ( ) . set ( # [ - 0 ] , true ) . get ( # [ 0 ] ) ) ;
Record
и Tuple
В общем, вы можете относиться к записям, как объекты. Например, пространство имен Object
и in
работают с Records
.
const keysArr = Object . keys ( # { a : 1 , b : 2 } ) ; // returns the array ["a", "b"]
assert ( keysArr [ 0 ] === "a" ) ;
assert ( keysArr [ 1 ] === "b" ) ;
assert ( keysArr !== # [ "a" , "b" ] ) ;
assert ( "a" in # { a : 1 , b : 2 } ) ;
Record
и Tuple
Разработчики JS, как правило, не должны думать о объектах Record
и Tuple
, но они являются ключевой частью того, как работают записи и кортежи «под капотом» в спецификации JavaScript.
Доступ к записи или кортеже через .
или []
следует типичной семантике GetValue
, которая косвенно преобразуется в экземпляр соответствующего типа обертки. Вы также можете сделать преобразование явно через Object()
:
Object(record)
создает объект оболочки записиObject(tuple)
создает объект обертки кортежей (Можно представить, что new Record
или new Tuple
могут создать эти обертки, такие как new Number
и new String
, но записи и кортеж поощрять программистов взять.)
Объекты записи и кортежеры имеют все свои собственные свойства с атрибутами writable: false, enumerable: true, configurable: false
. Объект объекта не расширяется. Все собрались вместе, они ведут себя как замороженные объекты. Это отличается от существующих объектов обертки в JavaScript, но необходимо дать виды ошибок, которые вы ожидаете от обычных манипуляций на записях и кортежах.
Экземпляр Record
имеет те же ключи и значения, что и базовое значение record
. __proto__
из каждого из этих объектов записи обертки является null
(обсуждение: #71).
В экземпляре Tuple
есть ключи, которые являются целыми числами, соответствующими каждому индексу в базовом значении tuple
. Значение для каждого из этих ключей является соответствующим значением в исходном tuple
. Кроме того, существует не выключаемая ключ length
. В целом, эти свойства соответствуют свойствам объекта String
Orbper. То есть Object.getOwnPropertyDescriptors(Object(#["a", "b"]))
и Object.getOwnPropertyDescriptors(Object("ab"))
каждый возвращает объект, который выглядит так:
{
"0" : {
"value" : " a " ,
"writable" : false ,
"enumerable" : true ,
"configurable" : false
},
"1" : {
"value" : " b " ,
"writable" : false ,
"enumerable" : true ,
"configurable" : false
},
"length" : {
"value" : 2 ,
"writable" : false ,
"enumerable" : false ,
"configurable" : false
}
}
__proto__
из объектов обертки кортежей является Tuple.prototype
. Обратите внимание, что если вы работаете в разных глобальных объектах JavaScript («Realms»), Tuple.prototype
выбирается на основе текущей области, когда выполняется преобразование объекта, аналогично тому, как ведет себя .prototype
не прилагается к самому значению кортежа. Tuple.prototype
имеет различные методы на нем, аналогично массивам.
Для целостности, не связанная с фиксацией численного индексации на кортежах возвращает undefined
, а не пересылавшись через цепь прототипа, как при typedarrays. Поиск немерных ключей свойства вперед продвигается до Tuple.prototype
.
Record
и Tuple
Значения Tuple
имеют функциональность, в целом аналогичную Array
. Точно так же значения Record
поддерживаются различными статическими методами Object
.
assert . deepEqual ( Object . keys ( # { a : 1 , b : 2 } ) , [ "a" , "b" ] ) ;
assert ( # [ 1 , 2 , 3 ] . map ( x => x * 2 ) , # [ 2 , 4 , 6 ] ) ;
Смотрите приложение, чтобы узнать больше о пространствах имен Record
и Tuple
.
Вы можете преобразовать структуры с использованием Record()
, Tuple()
(с оператором спреда), Record.fromEntries()
или Tuple.from()
:
const record = Record ( { a : 1 , b : 2 , c : 3 } ) ;
const record2 = Record . fromEntries ( [ [ "a" , 1 ] , # [ "b" , 2 ] , { 0 : 'c' , 1 : 3 } ] ) ; // note that any iterable of entries will work
const tuple = Tuple ( ... [ 1 , 2 , 3 ] ) ;
const tuple2 = Tuple . from ( [ 1 , 2 , 3 ] ) ; // note that an iterable will also work
assert ( record === # { a : 1 , b : 2 , c : 3 } ) ;
assert ( tuple === # [ 1 , 2 , 3 ] ) ;
Record ( { a : { } } ) ; // TypeError: Can't convert Object with a non-const value to Record
Tuple . from ( [ { } , { } , { } ] ) ; // TypeError: Can't convert Iterable with a non-const value to Tuple
Обратите внимание, что Record()
, Tuple()
, Record.fromEntries()
и Tuple.from()
Ожидайте коллекции, состоящие из записей, кортежей или других примитивов (таких как числа, строки и т. Д.). Вложенные ссылки на объект могут вызвать тип ENRROR. Вызывник должен преобразовать внутренние структуры любым способом, подходящим для приложения.
ПРИМЕЧАНИЕ . Текущее проект предложения не содержит процедуры рекурсивного преобразования, только мелкие. См. Обсуждение в #122
Как массивы, кортежи итеральны.
const tuple = # [ 1 , 2 ] ;
// output is:
// 1
// 2
for ( const o of tuple ) { console . log ( o ) ; }
Подобно объектам, записи являются только итерационными в сочетании с API, такими как Object.entries
.
const record = # { a : 1 , b : 2 } ;
// TypeError: record is not iterable
for ( const o of record ) { console . log ( o ) ; }
// Object.entries can be used to iterate over Records, just like for Objects
// output is:
// a
// b
for ( const [ key , value ] of Object . entries ( record ) ) { console . log ( key ) }
JSON.stringify(record)
эквивалентно вызову JSON.stringify
на объекте, возникающем в результате рекурсивного преобразования записи в объект, который не содержит записей или кортежей.JSON.stringify(tuple)
эквивалентно вызове JSON.stringify
в массиве, возникающем в результате рекурсивного преобразования кортежа в массив, который не содержит записей или кортежей. JSON . stringify ( # { a : # [ 1 , 2 , 3 ] } ) ; // '{"a":[1,2,3]}'
JSON . stringify ( # [ true , # { a : # [ 1 , 2 , 3 ] } ] ) ; // '[true,{"a":[1,2,3]}]'
Пожалуйста, смотрите https://github.com/tc39/proposal-json-parseimmutable
Tuple.prototype
Tuple
поддерживает методы экземпляра, аналогичные массиву с несколькими изменениями:
Tuple.prototype.push
.Tuple.prototype.withAt
. Приложение содержит полное описание прототипа Tuple
.
typeof
typeof
идентифицирует записи и кортежи как отдельные типы:
assert ( typeof # { a : 1 } === "record" ) ;
assert ( typeof # [ 1 , 2 ] === "tuple" ) ;
Map
| Set
| WeakMap
| WeakSet
} Можно использовать Record
или Tuple
в качестве ключа в Map
и в качестве значения в Set
. При использовании Record
или Tuple
здесь они сравниваются по значению.
Невозможно использовать Record
или Tuple
в качестве ключа в WeakMap
или в качестве значения в WeakSet
, потому что Records
и Tuple
S не являются Objects
, а их жизнь не наблюдается.
const record1 = # { a : 1 , b : 2 } ;
const record2 = # { a : 1 , b : 2 } ;
const map = new Map ( ) ;
map . set ( record1 , true ) ;
assert ( map . get ( record2 ) ) ;
const record1 = # { a : 1 , b : 2 } ;
const record2 = # { a : 1 , b : 2 } ;
const set = new Set ( ) ;
set . add ( record1 ) ;
set . add ( record2 ) ;
assert ( set . size === 1 ) ;
const record = # { a : 1 , b : 2 } ;
const weakMap = new WeakMap ( ) ;
// TypeError: Can't use a Record as the key in a WeakMap
weakMap . set ( record , true ) ;
const record = # { a : 1 , b : 2 } ;
const weakSet = new WeakSet ( ) ;
// TypeError: Can't add a Record to a WeakSet
weakSet . add ( record ) ;
Одним из основных преимуществ предложения записей и кортежей является то, что они сравниваются по их содержанию, а не их идентичностью. В то же время, ===
в JavaScript на объектах имеет очень четкую, последовательную семантику: сравнить объекты по идентичности. Создание записей и кортежников примитивами позволяет сравнивать на основе их значений.
На высоком уровне объектное/примитивное различие помогает сформировать твердую линию между глубоко неизменным, без контекста, без идентификационным миром и миром измененных объектов над ним. Этот раскол категории делает дизайн и ментальную модель более четкой.
Альтернативой внедрению записи и корпуса в качестве примитивах будет использование перегрузки оператора для достижения аналогичного результата, внедряя оператор перегруженного абстрактного равенства ( ==
), который глубоко сравнивает объекты. Хотя это возможно, это не удовлетворяет варианту полного использования, поскольку перегрузка оператора не обеспечивает переопределение оператора ===
. Мы хотим, чтобы оператор строгого равенства ( ===
) была надежной проверкой «идентичности» для объектов и «наблюдаемого значения» (модуля -0/+0/NAN) для примитивных типов.
Другой вариант заключается в том, чтобы выполнить так называемое rogning : мы отслеживаем глобальные записи или объекты корпуса, и если мы попытаемся создать новый, который оказывается идентичным существующему объекту записи, мы теперь ссылаемся на эту существующую запись вместо создания новой. По сути, это то, что делает полифилл. Сейчас мы приравниваем ценность и личность. Этот подход создает проблемы, как только мы расширяем это поведение по нескольким контекстам JavaScript и не даст глубокой неизменности по своей природе, и оно особенно медленно , что сделает использование записи и деликатора отрицательным выбором.
Record & Tuple создана для того, чтобы хорошо взаимодействовать с объектами и массивами: вы можете прочитать их точно так же, как и с объектами и массивами. Основное изменение заключается в глубокой неизменности и сравнении по значению вместо идентичности.
Разработчики, используемые для манипулирования объектами, неизменным образом (например, преобразование кусочков состояния Redux) смогут продолжать выполнять те же манипуляции, которые они использовали, на объектах и массивах, на этот раз, с большими гарантиями.
Мы собираемся провести эмпирические исследования с помощью интервью и опросов, чтобы выяснить, работает ли это, как мы думаем.
.get()
/ .set()
как Immutable.js?Если мы хотим сохранить доступ к записи и рубеже, похожим на объекты и массивы, как описано в предыдущем разделе, мы не можем полагаться на методы для выполнения этого доступа. Это потребовало бы, чтобы мы могли верить код при попытке создать «универсальную» функцию, способную принимать объекты/массивы/записи/кортежи.
Вот пример функция, которая поддерживает Immutable.js записи и обычные объекты:
const ProfileRecord = Immutable . Record ( {
name : "Anonymous" ,
githubHandle : null ,
} ) ;
const profileObject = {
name : "Rick Button" ,
githubHandle : "rickbutton" ,
} ;
const profileRecord = ProfileRecord ( {
name : "Robin Ricard" ,
githubHandle : "rricard" ,
} ) ;
function getGithubUrl ( profile ) {
if ( Immutable . Record . isRecord ( profile ) ) {
return `https://github.com/ ${
profile . get ( "githubHandle" )
} ` ;
}
return `https://github.com/ ${
profile . githubHandle
} ` ;
}
console . log ( getGithubUrl ( profileObject ) ) // https://github.com/rickbutton
console . log ( getGithubUrl ( profileRecord ) ) // https://github.com/rricard
Это подвержено ошибкам, так как обе ветви могут легко выйти из синхронизации с течением времени ...
Вот как мы бы писали эту функцию, взяв записи из этого предложения и обычные объекты:
const profileObject = {
name : "Rick Button" ,
githubHandle : "rickbutton" ,
} ;
const profileRecord = # {
name : "Robin Ricard" ,
githubHandle : "rricard" ,
} ;
function getGithubUrl ( profile ) {
return `https://github.com/ ${
profile . githubHandle
} ` ;
}
console . log ( getGithubUrl ( profileObject ) ) // https://github.com/rickbutton
console . log ( getGithubUrl ( profileRecord ) ) // https://github.com/rricard
Эта функция поддерживает как объекты, так и записи в одном кодовом пути, а также не заставляя потребителя выбирать, какие структуры данных использовать.
Зачем нам все равно поддерживать оба одновременно? Это в первую очередь, чтобы избежать разделения экосистемы. Допустим, мы используем Immutable.js для выполнения нашего государственного управления, но мы должны кормить наше состояние несколькими внешними библиотеками, которые не поддерживают его:
state . jobResult = Immutable . fromJS (
ExternalLib . processJob (
state . jobDescription . toJS ( )
)
) ;
Как toJS()
, так и fromJS()
могут быть очень дорогими операциями в зависимости от размера субструктур. Экосистемное разделение означает преобразования, которые, в свою очередь, означает возможные проблемы с производительностью.
Предложенный синтаксис значительно улучшает эргономику использования Record
и Tuple
в коде. Например:
// with the proposed syntax
const record = # {
a : # {
foo : "string" ,
} ,
b : # {
bar : 123 ,
} ,
c : # {
baz : # {
hello : # [
1 ,
2 ,
3 ,
] ,
} ,
} ,
} ;
// with only the Record/Tuple globals
const record = Record ( {
a : Record ( {
foo : "string" ,
} ) ,
b : Record ( {
bar : 123 ,
} ) ,
c : Record ( {
baz : Record ( {
hello : Tuple (
1 ,
2 ,
3 ,
) ,
} ) ,
} ) ,
} ) ;
Предложенный синтаксис предназначен для того, чтобы быть проще и проще для понимания, поскольку он намеренно похож на синтаксис для литералов объекта и массива. Это использует преимущества существующего знакомства пользователя с объектами и массивами. Кроме того, второй пример вводит дополнительные временные литералы объектов, что добавляет сложности выражения.
Использование ключевого слова в качестве префикса для стандартного объекта/литерального синтаксиса объекта/массива представляет проблемы вокруг обратной совместимости. Кроме того, повторное использование существующих ключевых слов может ввести двусмысленность.
Ecmascript определяет набор зарезервированных ключевых слов , которые можно использовать для будущих расширений на язык. Определение нового ключевого слова, которое еще не зарезервировано, теоретически возможно, но требует значительных усилий, чтобы подтвердить, что новое ключевое слово вряд ли сломается назад.
Использование зарезервированного ключевого слова облегчает этот процесс, но это не идеальное решение, потому что нет зарезервированных ключевых слов, которые соответствуют «намерению» этой функции, кроме const
. Ключевое слово const
также является сложным, потому что оно описывает аналогичную концепцию (ориентировочная неизменность), в то время как это предложение намеревается добавить новые неизменные структуры данных. Несмотря на то, что неизбежность является общей нитью между этими двумя функциями, существует значительная обратная связь с сообществом, которая указывает на то, что использование const
в обоих контекстах нежелательно.
Вместо использования ключевого слова, {| |}
и [||]
были предложены в качестве возможных альтернатив. В настоящее время группа чемпионов склоняется к #[]
/ #{}
, но обсуждение продолжается в #10.
Определение записи и кортежей как составных примитивов заставляет все в записи и кортеже, чтобы не быть объектами. Это связано с некоторыми недостатками (ссылка на объекты становится сложнее, но все еще возможно), но также и больше гарантий, чтобы избежать общих ошибок программирования.
const object = {
a : {
foo : "bar" ,
} ,
} ;
Object . freeze ( object ) ;
func ( object ) ;
// func is able to mutate object’s keys even if object is frozen
В приведенном выше примере мы стараемся создать гарантию неизменности с Object.freeze
. К сожалению, поскольку мы не замораживали объект глубоко, ничто не говорит нам об этом object.a
. А не было затронут. С записи и корпусом, что ограничение является природой, и нет никаких сомнений в том, что структура нетронута:
const record = # {
a : # {
foo : "bar" ,
} ,
} ;
func ( record ) ;
// runtime guarantees that record is entirely unchanged
assert ( record . a . foo === "bar" ) ;
Наконец, глубокая неизменность подавляет необходимость в общей закономерности, которая состоит из объектов глубокого клонирования, чтобы сохранить гарантии:
const clonedObject = JSON . parse ( JSON . stringify ( object ) ) ;
func ( clonedObject ) ;
// now func can have side effects on clonedObject, object is untouched
// but at what cost?
assert ( object . a . foo === "bar" ) ;
В общем, оператор спреда хорошо работает для этого:
// Add a Record field
let rec = # { a : 1 , x : 5 }
# { ... rec , b : 2 } // #{ a: 1, b: 2, x: 5 }
// Change a Record field
# { ... rec , x : 6 } // #{ a: 1, x: 6 }
// Append to a Tuple
let tup = # [ 1 , 2 , 3 ] ;
# [ ... tup , 4 ] // #[1, 2, 3, 4]
// Prepend to a Tuple
# [ 0 , ... tup ] // #[0, 1, 2, 3]
// Prepend and append to a Tuple
# [ 0 , ... tup , 4 ] // #[0, 1, 2, 3, 4]
И если вы что -то меняете в кортеже, Tuple.prototype.with
с помощью метода работает:
// Change a Tuple index
let tup = # [ 1 , 2 , 3 ] ;
tup . with ( 1 , 500 ) // #[1, 500, 3]
Некоторые манипуляции с «глубокими путями» могут быть немного неловкими. Для этого свойства Deep Path для записей добавляет дополнительный синтаксис сокращения для записи литералов.
Мы разрабатываем предложение «Свойства Deep Path» в качестве отдельного последующего предложения, потому что мы не считаем его основным использованием записей, которые хорошо работают независимо. Это тип синтаксического дополнения, который будет хорошо работать для прототипа с течением времени у транспористов, и где у нас есть много точек принятия решений, которые не имеют отношения к записям и кортежам (например, как это работает с объектами).
Мы поговорили с чемпионами Creatonly Collections, и обе группы согласны с тем, что это дополнения:
Ни один из них не является подмножеством другого с точки зрения функциональности. В лучшем случае они параллельны, точно так же, как каждое предложение параллельно другим типам сбора на языке.
Таким образом, две группы чемпионов решили обеспечить, чтобы предложения были параллельными по отношению друг к другу . Например, это предложение добавляет новый Tuple.prototype.withReversed
. Неверный метод. Идея состоит в том, чтобы проверить в процессе проектирования, если эта подпись также имеет смысл для массивов только для чтения (если они существуют): мы извлекли эти новые методы в массив изменения по предложению копирования, чтобы мы могли обсудить API который создает последовательную, общую ментальную модель.
В текущих проектах предложения нет каких -либо перекрывающихся типов для одинаковых данных, но оба предложения могут расти в этих направлениях в будущем, и мы пытаемся думать об этом заранее. Кто знает, когда -нибудь TC39 может принять решение добавить примитивную карту записи и типы записей, как глубоко неизменные версии набора и карты! И они будут параллельно с типами коллекций Readonly.
TC39 долго обсуждал «типы ценностей», что было бы каким -то классовым объявлением для примитивного типа в течение нескольких лет, включенных и выключенных. Более ранняя версия этого предложения даже предприняла попытку. Это предложение пытается начать простой и минимальный, обеспечивая только основные структуры. Надежда состоит в том, что она может предоставить модель данных для будущего предложения для занятий.
Это предложение слабо связано с более широким набором предложений, включая перегрузку оператора и расширенные цифровые литералы: все они сговорились, чтобы обеспечить способ, определенные пользователями, делать то же самое, что и Bigint. Тем не менее, идея состоит в том, чтобы добавить эти функции, если мы определим, что они независимо мотивированы.
Если бы у нас были пользовательские типы примитива/значения, то это может иметь смысл использовать их в встроенных функциях, таких как CSS Typed OM или временное предложение. Однако это далеко в будущем, если это когда -либо произойдет; На данный момент он хорошо работает для использования объектов для таких функций.
Хотя оба вида записей связаны с объектами, и оба вида кортежей относятся к массивам, это то, где заканчивается сходство.
Записи в TypeScript - это общий тип утилиты для представления объекта, принимающего тип ключа с типом значения. Они все еще представляют объекты.
Аналогичным образом, кортежи в TypeScript являются обозначением для выражения типов в массиве ограниченного размера (начиная с TypeScript 4.0, они имеют переменную форму). Клетки в TypeScript - это способ выразить массивы с гетерогенными типами. Клетки Ecmascript могут легко соответствовать массивам TS или платеж TS легко, так как они могут либо содержать неопределенное количество значений одного и того же типа, либо содержать ограниченное количество значений с различными типами.
Записи TS или кортежи являются ортогональными функциями для записей и кортежей Ecmascript, и оба могут быть выражены одновременно:
const record : Readonly < Record < string , number > > = # {
foo : 1 ,
bar : 2 ,
} ;
const tuple : readonly [ number , string ] = # [ 1 , "foo" ] ;
Это предложение не дает никаких гарантий производительности и не требует конкретной оптимизации в реализациях. Основываясь на обратной связи от исполнителей, ожидается, что они будут реализовать общие операции с помощью алгоритмов «линейного времени». Тем не менее, это предложение не предотвращает некоторые классические оптимизации для чисто функциональных структур данных, включая, помимо прочего:
Эти оптимизации аналогичны тому, как современные двигатели JavaScript обрабатывают конкатенацию строки, с различными внутренними типами строк. Достоверность этих оптимизаций основывается на ненаблюдаемости идентичности записей и кортежей. Не ожидается, что все двигатели будут действовать одинаково в отношении этих оптимизаций, а скорее, каждый из них будет принимать решения о том, какую конкретную эвристику использовать. До 4-го этапа этого предложения мы планируем опубликовать руководство для лучших практик для оптимизируемого использования записей и кортежей для перекрестного двигателя, основанного на опыте реализации, который мы будем иметь на этом этапе.
Новая, глубоко неизменная, составная структура данных примитивного типа, предложенная в этом документе, которая аналогична объекту. #{ a: 1, b: 2 }
Новая, глубоко неизменная, составная структура данных примитивного типа, предложенная в этом документе, аналогично массиву. #[1, 2, 3, 4]
Значения, которые действуют как другие примитивы JavaScript, но состоят из других составляющих значений. Этот документ предлагает первые два примитивных типах соединений: Record
и Tuple
.
String
, Number
, Boolean
, undefined
, null
, Symbol
и BigInt
Вещи, которые являются либо составными, либо простыми примитивными типами. Все примитивы в JavaScript разделяют определенные свойства:
Структура данных, которая не принимает операции, которые изменяют их внутренне, но вместо этого имеют операции, которые возвращают новое значение, которое является результатом применения этой операции к ней.
В этом предложении Record
и Tuple
являются глубоко неизменными структурами данных.
Оператор ===
определяется с помощью строгого алгоритма сравнения равенства. Строгое равенство относится к этому конкретному понятию равенства.
Структурное совместное использование - это метод, используемый для ограничения следов памяти неподвижных структур данных. Короче говоря, при применении операции для вывода новой версии неизменной конструкции, разделиться структурным образом попытается сохранить большую часть внутренней структуры нетронутой и используемой как старой, так и полученной версией этой структуры. Это значительно ограничивает сумму для копирования, чтобы получить новую структуру.