作者:
冠軍:
顧問
階段: 2
Record
和Tuple
標準庫支持該提案將兩個新的深入的數據結構介紹給JavaScript:
Record
,一種深層不可變的類似對象的結構#{ x: 1, y: 2 }
Tuple
,一種深層的陣列狀結構#[1, 2, 3, 4]
記錄和元組只能包含原始記錄以及其他記錄和元組。您可以將記錄和元素視為“化合物原始人”。通過徹底基於原語,而不是物體,記錄和元素是深層的。
記錄和元組支持舒適的習慣,以進行構建,操縱和使用,類似於使用對象和陣列。它們的內容而不是通過其身份進行了深入的比較。
JavaScript引擎可能會對記錄和元素進行構建,操縱和比較進行某些優化,這類似於JS發動機在JS發動機中經常實現字符串的方式。 (應該理解,這些優化不能保證。)
記錄和元組旨在使用外部類型系統超集(例如打字稿或流程)來使用並理解。
如今,Userland庫實施了類似的概念,例如Inmuctable.js。此外,由於提案的複雜性和缺乏足夠的用例,因此已經嘗試了以前的提議,但由於提案的複雜性而被放棄。
這項新提案仍然受到以前的建議的啟發,但引入了一些重大變化:記錄和元組現在是深層不變的。從根本上講,該屬性是基於這樣的觀察結果,即在大型項目中,混合不可變和可變的數據結構的風險隨著存儲和傳遞的數據的增長而增長,因此您更有可能處理大型記錄和元組結構。這可能會引入難以找到的錯誤。
作為內置的,深層不變的數據結構,該提案還提供了與Userland庫相比的一些可用性優勢:
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
在操場上開放
在此處查看更多示例。
如記錄和元組之前所述:嘗試在其中插入對象將導致typeserror:
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
記錄和元組可能只包含原始記錄以及其他記錄和元素。試圖創建包含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 treat -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
,但是記錄和元組遵循符號和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全局對象(“ ronems”)工作,則在執行對象轉換時,根據當前領域選擇了Tuple.prototype
.prototype
不附帶元組價值本身。 Tuple.prototype
具有各種方法,類似於數組。
為了完整性,元素上的數值索引返回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 ) ; }
與對像類似,只有與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-parseimmmutable
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
組不是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中===
具有非常清晰,一致的語義:通過身份比較對象。製作記錄和元組原語可以根據其值進行比較。
在高水平上,對象/原始區別有助於在深度不變,無上下文,無身份的世界與上面的可變對象世界之間形成一條硬線。此類別拆分使設計和心理模型更加清晰。
通過實現運算符超載來實現類似結果,通過實現超載的抽象平等( ==
)操作員,可以使用操作員超載來實現相似的結果,從而實現記錄和元組。儘管這是可能的,但它無法滿足完整用例,因為運算===
超載並不能為操作員提供覆蓋。我們希望嚴格的平等( ===
)操作員對原始類型的對象和“可觀察值-0/+0/nan)的“身份”的可靠檢查。
另一個選項是執行所謂的實習:我們跟踪全球記錄或元組對象,如果我們嘗試創建一個與現有記錄對象相同的新記錄,我們現在引用此現有記錄而不是創建新記錄。從本質上講,這就是多填充的作用。我們現在等同於價值和身份。一旦我們將這種行為擴展到多個JavaScript上下文中,並且不會給出深刻的不變性,並且它尤其慢,這將使使用Record&Tuple成為性能為陰性的選擇,就會產生問題。
記錄和元組的構建是可以與對象和數組的互操作互操作的:您可以與對象和數組完全相同的方式讀取它們的方式。主要變化在於深度不變性和按價值而不是身份進行比較。
用於以不可變的方式操縱對象的開發人員(例如轉換Redux狀態的部分)將能夠繼續進行與他們過去在對象和陣列上進行相同的操作,這一次,並提供了更多的保證。
我們將通過訪談和調查進行實證研究,以弄清楚這是否如我們認為的那樣解決。
.get()
/ .set()
方法等錄音和元組?如果我們想繼續訪問與上一節中所述的對象和數組相似的記錄和元組,我們不能依靠方法來執行該訪問權限。這樣做將需要我們在嘗試創建一個“通用”功能時能夠採用對象/數組/記錄/元組時進行分支代碼。
這是一個示例函數,它支持對不可變的記錄和普通對象:
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
此功能支持單個代碼路徑中的對象和記錄,並且不強迫消費者選擇要使用的數據結構。
為什麼我們需要同時支持這兩個?這主要是為了避免生態系統分裂。假設我們正在使用不變的。
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]
對“深路”的某些操縱可能有點尷尬。為此,記錄提案的深度路徑屬性增加了額外的速記語法來記錄文字。
我們將深度路徑屬性提案作為一個單獨的後續提案開發,因為我們不認為它是使用記錄的核心,這些記錄是獨立工作的。這是一種句法添加,可以隨著時間的推移在轉載器中隨著時間的推移而效果很好,並且我們有許多決策點與記錄和元素無關(例如,它如何與對像一起工作)。
我們已經與Readonly Collections Champions進行了交談,兩個小組都同意這些都是補充:
就功能而言,這兩個都不是另一個子集。充其量,它們是平行的,就像每個建議與語言中的其他收藏類型平行一樣。
因此,兩個冠軍團體已決定確保這些建議相對於彼此平行。例如,該建議添加了一個新的Tuple.prototype.withReversed
組。這個想法是在設計過程中檢查此簽名是否對僅閱讀數組有意義(如果存在):我們通過複製提案將這些新方法提取到更改數組,以便我們可以討論API這建立了一個一致的共享心理模型。
在當前的提案草案中,沒有任何相同數據的重疊類型,但是兩個建議將來都可以在這些方向上增長,我們正在嘗試提前思考這些事情。誰知道,有一天,TC39可以決定添加原始的記錄圖和記錄集類型,作為Set和Map的深層版本!這些將與可讀的收藏類型平行。
TC39長期以來一直在討論“價值類型”,這將是原始類型的某種類型的班級聲明,多年來。該提案的較早版本甚至嘗試了。該提案試圖從簡單而最小的時候開始,僅提供核心結構。希望它可以為未來的課程提供數據模型。
該提案與更廣泛的建議相關,包括運營商的超載和擴展的數字文字:這些都共同提供了一種使用戶定義類型與BigInt相同的方式。但是,如果我們確定它們是出於獨立動機,則是添加這些功能。
如果我們具有用戶定義的原始/價值類型,那麼在內置功能(例如CSS Typed OM或時間提案)中使用它們是有意義的。但是,如果發生的話,這在將來還很遠。目前,將對像用於這些功能很好。
儘管兩種記錄都與對像有關,而兩種元素都與數組有關,但這就是相似性結束的位置。
打字稿中的記錄是一種通用實用程序類型,代表將鍵類型與值類型匹配的對象。它們仍然代表對象。
同樣,打字稿中的元組是以有限尺寸的數組表示類型的表示法(從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" ] ;
該建議不會提供任何績效保證,也不需要在實施中進行特定的優化。根據實施者的反饋,預計他們將通過“線性時間”算法實施共同的操作。但是,該建議並不能阻止某些純粹功能數據結構的經典優化,包括但不限於:
這些優化類似於現代JavaScript引擎處理字符串串聯的方式,具有不同的內部類型的字符串。這些優化的有效性取決於記錄和元素身份的不可觀念性。不期望所有發動機都在這些優化方面採取相同的作用,而是他們每個人都會決定要使用哪種特定的啟發式方法。在本提案的第4階段之前,我們計劃根據我們當時將擁有的實施經驗,發布可優化記錄和元組的最佳實踐指南。
本文檔中提出的一種新的,深層的,複合的原始類型數據結構,類似於對象。 #{ a: 1, b: 2 }
本文檔中提出的一種新的,深層的,複合的原始類型數據結構,類似於數組。 #[1, 2, 3, 4]
具有與其他JavaScript原始原則的值,但由其他組成值組成。本文檔提出了前兩種化合物原始類型: Record
和Tuple
。
String
, Number
, Boolean
, undefined
, null
, Symbol
和BigInt
是複合或簡單原始類型的東西。 JavaScript中的所有原語都共享某些屬性:
一個不接受內部更改操作的數據結構,而是具有返回新值的操作,這是將該操作應用於該操作的結果。
在此提案Record
和Tuple
中,是深層不變的數據結構。
操作員===
使用嚴格的平等比較算法定義。嚴格的平等是指這種特殊的平等觀念。
結構共享是一種用於限制不變數據結構的內存足蹟的技術。簡而言之,當應用操作得出新版本的不變結構時,結構共享將嘗試保持大多數內部結構完整,並由該結構的舊版本和派生版本使用。這極大地限制了複製以得出新結構的金額。