저자 :
챔피언 :
고문
단계 : 2
Record
및 Tuple
표준 라이브러리 지원이 제안은 JavaScript에 두 가지 새로운 불변의 데이터 구조를 소개합니다.
Record
, 매우 불변의 물체와 같은 구조 #{ x: 1, y: 2 }
Tuple
, 매우 불변의 어레이와 같은 구조 #[1, 2, 3, 4]
레코드와 튜플에는 프리미티브 및 기타 레코드 및 튜플 만 포함 할 수 있습니다. 레코드와 튜플을 "화합물 프리미티브"로 생각할 수 있습니다. 사물, 레코드 및 튜플이 아닌 프리미티브를 기반으로 철저히 사용함으로써 깊이 불변 할 수 없습니다.
레코드와 튜플은 물체 및 어레이 작업과 유사하게 건축, 조작 및 사용을위한 편안한 관용구를 지원합니다. 그들은 정체성보다는 내용에 의해 깊이 비교됩니다.
JavaScript 엔진은 문자열이 종종 JS 엔진에서 구현되는 방식과 유사하게 건축, 조작 및 레코드 및 튜플 비교에 대한 특정 최적화를 수행 할 수 있습니다. (이러한 최적화는 보장되지 않는다는 것을 이해해야합니다.)
레코드와 튜플은 TypeScript 또는 Flow와 같은 외부 타입 시스템 슈퍼 세트로 사용할 수 있고 이해되는 것을 목표로합니다.
오늘날 Userland Libraries는 Empable.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 ) )
이것은이 제안을 통해 언어에 추가되는 새로운 구문을 정의합니다.
다른 정상 객체 또는 배열 표현식 앞에서 #
modifier를 사용하여 레코드 또는 튜플 표현식을 정의합니다.
# { }
# { 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
레코드와 튜플에는 프리미티브 및 기타 레코드 및 튜플 만 포함 할 수 있습니다. Object
포함 된 Record
또는 Tuple
만들려고 시도하면 ( 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
, ==
, ===
및 내부 SameValuezero 알고리즘 (맵과 세트의 키를 비교하는 데 사용됨). . -0
어떻게 처리되는지에 따라 다릅니다.
Object.is
-0
과 0
동일하지 않습니다==
, ===
및 SameValuezero -0
동일하게 처리합니다 0
==
및 ===
는 레코드와 튜플에 중첩 된 다른 종류의 값에 대해 더 직접적입니다. 내용이 동일 한 경우에만 ( 0
/ -0
을 제외하고) 만 true
됩니다. 이 직접성은 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
처럼 이러한 포장지를 만들 수 있다고 상상할 수 있지만, 레코드와 튜플은 Symbol과 Bigint가 설정 한 최신 컨벤션을 따르며,이 경우를 던지려고합니다. 프로그래머가 취하도록 장려하십시오.)
레코드 및 튜플 래퍼 객체는 writable: false, enumerable: true, configurable: false
. 래퍼 객체는 확장 가능하지 않습니다. 모두 합쳐서 얼어 붙은 물체로 행동합니다. 이는 JavaScript의 기존 래퍼 객체와 다르지만 레코드 및 튜플의 일반적인 조작에서 기대할 수있는 오류를 제공하는 데 필요합니다.
Record
인스턴스는 기본 record
값과 동일한 키와 값을 갖습니다. 이 레코드 래퍼 객체 각각의 __proto__
null
입니다 (토론 : #71).
Tuple
인스턴스에는 기본 tuple
값의 각 인덱스에 해당하는 정수가있는 키가 있습니다. 이 키 각각의 값은 원래 tuple
의 해당 값입니다. 또한, 발포 할 수없는 length
키가 있습니다. 전반적으로 이러한 속성은 String
래퍼 객체의 속성과 일치합니다. 즉, 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 Global Objects ( "Realms")에서 작업하는 경우 튜플. Tuple.prototype
다른 프리미티브의 .prototype
동작하는 방식과 유사하게 객체 변환이 수행 될 때 현재 영역을 기반으로 선택됩니다. 튜플 값 자체에 부착되지 않습니다. Tuple.prototype
에는 어레이와 유사한 다양한 방법이 있습니다.
무결성을 위해 TyedArray와 같이 프로토 타입 체인을 통해 전달하는 대신 Tuples의 수치 지수가 undefined
반환합니다. 비수막 부동산 키 조회는 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()
은 레코드, 튜플 또는 기타 기본 요소 (숫자, 문자열 등)로 구성된 컬렉션을 기대합니다. 중첩 된 객체 참조는 TypeError를 유발합니다. 응용 프로그램에 적합한 방식으로 내부 구조를 변환하는 것은 발신자에게 달려 있습니다.
참고 : 현재 초안 제안에는 재귀적인 전환 루틴이 포함되어 있지 않으며 얕은 전환 루틴 만 포함되어 있습니다. #122의 토론을 참조하십시오
배열과 마찬가지로 튜플은 반복 가능합니다.
const tuple = # [ 1 , 2 ] ;
// output is:
// 1
// 2
for ( const o of tuple ) { console . log ( o ) ; }
객체와 마찬가지로 레코드는 Object.entries
와 같은 API와 함께 반복 할 수 있습니다.
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
사용할 때는 값으로 비교됩니다.
Records
와 Tuple
S는 Objects
아니며 수명을 관찰 할 수 없기 때문에 Record
나 Tuple
WeakMap
또는 WeakSet
의 값으로 사용하는 것은 불가능합니다.
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에서 ===
매우 명확하고 일관된 의미를 가지고 있습니다. 레코드 및 튜플 프리미티브를 만들면 값을 기준으로 비교할 수 있습니다.
높은 수준에서, 물체/원시적 차이는 깊고 불변적이고 상황에 맞는 정체성이없는 세상과 그 위에 변한 물체의 세계 사이의 단단한 선을 형성하는 데 도움이됩니다. 이 카테고리 분할은 디자인과 정신 모델을 더 명확하게 만듭니다.
프리미티브로서 레코드와 튜플을 구현하는 대안은 오버로드 된 추상 평등 ( ==
) 연산자를 구현하여 객체를 깊이 비교하여 연산자 과부하를 사용하는 것입니다. 운영자 과부하가 ===
운영자에 대한 재정의를 제공하지 않기 때문에 이것이 가능하지만, 전체 사용 사례를 만족시키지 못합니다. 우리는 엄격한 평등 ( ===
) 연산자가 객체에 대한 "정체성"을 신뢰할 수있는 점검과 원시 유형에 대한 "관찰 가능한 값"(modulo -0/+0/nan)을 신뢰할 수 있기를 원합니다.
또 다른 옵션은 Interning 이라는 것을 수행하는 것입니다. 우리는 전 세계적으로 녹음 또는 튜플 오브젝트를 추적하며 기존 레코드 객체와 동일한 새 제품을 만들려고하면 새 레코드를 작성하는 대신이 기존 레코드를 참조합니다. 이것은 본질적으로 폴리 필이하는 일입니다. 우리는 이제 가치와 정체성을 동일시하고 있습니다. 이 접근법은 여러 JavaScript 컨텍스트에서 해당 동작을 확장하면 문제를 일으키고 본질적으로 깊은 불변성을주지 않으며 특히 레코드 및 튜플을 공연 음성 선택으로 만드는 느리게 발생합니다 .
레코드 및 튜플은 물체와 배열과 잘 상호 작용하도록 만들어졌습니다. 물체와 어레이와 같은 방식으로 정확히 동일한 방식으로 읽을 수 있습니다. 주요 변화는 깊은 불변성과 정체성 대신 가치에 따른 비교에 있습니다.
개발자는 불변의 방식으로 객체를 조작하는 데 사용했던 (예 : Redux 상태의 형태)는 이번에는 더 많은 보장을 통해 개체 및 어레이에서 수행했던 것과 동일한 조작을 계속할 수 있습니다.
우리는 인터뷰와 설문 조사를 통해 경험적 연구를 수행하여 이것이 우리가 생각하는 것처럼 효과가 있는지 알아냅니다.
.get()
/ .set()
메소드를 기반으로하지 않는 이유는 무엇입니까?이전 섹션에서 설명한대로 객체 및 배열과 유사한 레코드 및 튜플에 계속 액세스하려면 해당 액세스를 수행하는 방법에 의존 할 수 없습니다. 그렇게하려면 객체/어레이/레코드/튜플을 가져갈 수있는 "일반적인"기능을 만들려고 할 때 코드를 분기해야합니다.
다음은 Empable.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
이 기능은 단일 코드 경로로 객체와 레코드를 모두 지원할뿐만 아니라 소비자가 사용할 데이터 구조를 선택하도록 강요하지 않습니다.
어쨌든 우리는 왜 동시에 둘 다 지원해야합니까? 이것은 주로 생태계 분할을 피하기위한 것입니다. 우리가 Empable.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 Properties 제안을 별도의 후속 제안으로 개발하고 있습니다. 왜냐하면 우리는 그것을 독립적으로 잘 작동하는 레코드 사용의 핵심으로 보지 않기 때문입니다. 트랜스 필러에서 시간이 지남에 따라 프로토 타입에 잘 어울리는 구문 추가 및 레코드 및 튜플 (예 : 객체와의 작동 방식)과 관련이없는 많은 결정점이있는 곳입니다.
우리는 Readonly Collections Champions와 이야기를 나 and으며 두 그룹 모두 이것이 보완된다는 데 동의합니다.
어느 쪽도 기능 측면에서 다른 하나의 하위 집합이 아닙니다. 기껏해야 각 제안이 언어의 다른 컬렉션 유형과 평행 한 것처럼 평행합니다.
따라서 두 챔피언 그룹은 제안이 서로 관련이 있는지 확인하기 위해 해결했습니다. 예를 들어,이 제안은 새로운 Tuple.prototype.withReversed
을 추가합니다. 아이디어는 디자인 프로세스 중에이 서명이 읽기 전용 배열에도 적합한 경우 (존재하는 경우)를 확인하는 것입니다. 일관되고 공유 된 정신 모델을 구축합니다.
현재 제안 초안에는 동일한 종류의 데이터에 대한 겹치는 유형이 없지만 앞으로 이러한 방향으로 두 가지 제안이 커질 수 있으며, 우리는 미리 이러한 것들을 생각하려고 노력하고 있습니다. 언젠가 TC39는 기본 레코드 맵과 레코드 세트 유형을 깊이 불변의 세트 및지도 버전으로 추가하기로 결정할 수 있습니다! 그리고 이것들은 Readonly Collections 유형과 병행합니다.
TC39는 오랫동안 "값 유형"에 대해 논의 해 왔으며, 이는 몇 년 동안 원시 유형에 대한 일종의 클래스 선언이 될 것입니다. 이 제안의 이전 버전도 시도했습니다. 이 제안은 핵심 구조 만 제공하여 단순하고 최소한을 시작하려고합니다. 희망은 그것이 수업에 대한 향후 제안을위한 데이터 모델을 제공 할 수 있기를 희망한다.
이 제안은 운영자 과부하 및 확장 숫자 리터럴을 포함하여 광범위한 제안 세트와 느슨하게 관련되어 있습니다. 이들은 모두 사용자 정의 유형이 Bigint와 동일하게 수행 할 수있는 방법을 제공하기 위해 공모합니다. 그러나이 아이디어는 이러한 기능이 독립적으로 동기를 부여한 경우 이러한 기능을 추가하는 것입니다.
사용자 정의 원시/값 유형이 있다면 CSS 타이핑 OM 또는 시간 제안과 같은 내장 기능에서 사용하는 것이 합리적 일 수 있습니다. 그러나 이것은 미래에 멀다. 현재로서는 이러한 종류의 기능에 객체를 사용하는 것이 좋습니다.
두 종류의 레코드는 객체와 관련이 있고 두 종류의 튜플이 어레이와 관련이 있지만 유사성이 끝나는 위치에 관한 것입니다.
TypeScript의 레코드는 값 유형과 일치하는 키 유형을 취하는 객체를 나타내는 일반적인 유틸리티 유형입니다. 그들은 여전히 물체를 나타냅니다.
마찬가지로, TypeScript의 튜플은 제한된 크기의 배열로 유형을 표현하는 표기법입니다 (TypeScript 4.0부터 시작). 타입 스크립트의 튜플은 이종 유형으로 배열을 표현하는 방법입니다. ECMAScript 튜플은 동일한 유형의 무기한 값을 포함하거나 다른 유형의 제한된 수의 값을 포함 할 수 있으므로 TS 어레이 또는 TS 튜플에 쉽게 대응할 수 있습니다.
TS 레코드 또는 튜플은 ECMAScript 레코드 및 튜플에 대한 직교 기능이며 동시에 표현 될 수 있습니다.
const record : Readonly < Record < string , number > > = # {
foo : 1 ,
bar : 2 ,
} ;
const tuple : readonly [ number , string ] = # [ 1 , "foo" ] ;
이 제안은 성능을 보장하지 않으며 구현에서 특정 최적화가 필요하지 않습니다. 구현 자의 피드백을 기반으로 "선형 시간"알고리즘을 통해 공통 작업을 구현할 것으로 예상됩니다. 그러나이 제안은 다음을 포함하되 이에 국한되지 않는 순수한 기능적 데이터 구조에 대한 일부 전형적인 최적화를 방지하지는 않습니다.
이러한 최적화는 최신 자바 스크립트 엔진이 다양한 내부 유형의 문자열과 함께 문자열 연결을 처리하는 방식과 유사합니다. 이러한 최적화의 유효성은 레코드와 튜플의 신원의 관찰 불가능성에 달려 있습니다. 모든 엔진이 이러한 최적화와 관련하여 동일하게 행동 할 것으로 예상되는 것은 아니지만 오히려 각각의 특정 휴리스틱에 대한 결정을 내릴 것입니다. 이 제안의 4 단계 전에, 우리는 그 시점에서 우리가 가진 구현 경험을 바탕으로 엔진 크로스 엔진 최적화 가능한 레코드 및 튜플 사용을위한 모범 사례를위한 안내서를 게시 할 계획입니다.
이 문서에서 제안 된 새롭고 깊고 불변의 복합 원시 유형 데이터 구조는 객체와 유사합니다. #{ a: 1, b: 2 }
이 문서에서 제안 된 새롭고 깊고 불변의 복합 원시 유형 데이터 구조는 배열과 유사합니다. #[1, 2, 3, 4]
다른 JavaScript 프리미티브와 같이 작동하지만 다른 구성 값으로 구성된 값. 이 문서는 처음 두 가지 화합물 원시 유형 인 Record
와 Tuple
제안합니다.
String
, Number
, Boolean
, undefined
, null
, Symbol
및 BigInt
복합 또는 단순한 원시 유형 인 것들. JavaScript의 모든 프리미티브는 특정 속성을 공유합니다.
내부적으로 변경하는 작업을 수락하지 않고 해당 작업을 적용한 결과 인 새 값을 반환하는 작업이있는 데이터 구조.
이 제안서에서 Record
와 Tuple
매우 불변의 데이터 구조입니다.
연산자 ===
엄격한 평등 비교 알고리즘으로 정의됩니다. 엄격한 평등은 이러한 특별한 평등 개념을 말합니다.
구조 공유는 불변의 데이터 구조의 메모리 발자국을 제한하는 데 사용되는 기술입니다. 간단히 말해서, 불변의 구조의 새로운 버전을 도출하기 위해 작업을 적용 할 때, 구조 공유는 대부분의 내부 구조를 그 구조의 이전 버전과 파생 버전 모두에서 그대로 유지하려고 시도합니다. 이것은 새로운 구조를 도출하기 위해 복사 할 금액을 크게 제한합니다.