Penulis:
Juara:
Penasihat
Tahap: 2
Record
dan Tuple
Proposal ini memperkenalkan dua struktur data baru yang sangat tidak dapat diubah untuk JavaScript:
Record
, struktur seperti objek yang sangat tidak dapat diubah #{ x: 1, y: 2 }
Tuple
, struktur seperti array yang sangat tidak dapat diubah #[1, 2, 3, 4]
Catatan dan tupel hanya dapat berisi primitif dan catatan dan tupel lainnya. Anda bisa menganggap catatan dan tupel sebagai "primitif majemuk". Dengan didasarkan pada primitif, bukan objek, catatan dan tupel sangat tidak berubah.
Catatan dan tupel mendukung idiom yang nyaman untuk konstruksi, manipulasi dan penggunaan, mirip dengan bekerja dengan objek dan array. Mereka dibandingkan secara mendalam dengan isinya, bukan oleh identitas mereka.
Mesin JavaScript dapat melakukan optimisasi tertentu pada konstruksi, manipulasi dan perbandingan catatan dan tupel, analog dengan cara string sering diimplementasikan dalam mesin JS. (Harus dipahami bahwa optimasi ini tidak dijamin.)
Catatan dan tupel bertujuan untuk dapat digunakan dan dipahami dengan superset sistem jenis eksternal seperti naskah atau aliran.
Saat ini, Perpustakaan Userland mengimplementasikan konsep serupa, seperti Immutable.js. Juga proposal sebelumnya telah dicoba tetapi ditinggalkan karena kompleksitas proposal dan kurangnya kasus penggunaan yang cukup.
Proposal baru ini masih terinspirasi oleh proposal sebelumnya ini tetapi memperkenalkan beberapa perubahan signifikan: catatan dan tupel sekarang sangat tidak berubah. Properti ini pada dasarnya didasarkan pada pengamatan bahwa, dalam proyek -proyek besar, risiko mencampur struktur data yang tidak dapat berubah dan berubah -ubah tumbuh ketika jumlah data yang disimpan dan dilewati juga tumbuh sehingga Anda akan lebih cenderung menangani struktur catatan & tuple yang besar . Ini dapat memperkenalkan bug yang sulit ditemukan.
Sebagai struktur data built-in yang sangat tidak berubah, proposal ini juga menawarkan beberapa keuntungan kegunaan dibandingkan dengan perpustakaan Userland:
IMMER adalah pendekatan penting untuk struktur data yang tidak dapat diubah, dan menetapkan pola untuk manipulasi melalui produsen dan pereduksi. Namun itu tidak memberikan tipe data yang tidak dapat diubah, karena menghasilkan objek beku. Pola yang sama ini dapat disesuaikan dengan struktur yang ditentukan dalam proposal ini selain objek beku.
Kesetaraan mendalam sebagaimana didefinisikan dalam pustaka pengguna dapat bervariasi secara signifikan, sebagian karena kemungkinan referensi ke objek yang dapat berubah. Dengan menggambar garis keras tentang hanya sangat mengandung primitif, catatan dan tupel, dan berulang melalui seluruh struktur, proposal ini mendefinisikan semantik sederhana dan terpadu untuk perbandingan.
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"]
Buka di taman bermain
Fungsi dapat menangani catatan dan objek secara umum dengan cara yang sama:
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
Buka di taman bermain
Lihat lebih banyak contoh di sini.
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]
Buka di taman bermain
Demikian pula dengan catatan, kita dapat memperlakukan tupel sebagai seperti array:
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
Buka di taman bermain
Lihat lebih banyak contoh di sini.
Seperti yang dinyatakan sebelum Record & Tuple sangat abadi: mencoba memasukkan objek di dalamnya akan menghasilkan tipeError:
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 ) )
Ini mendefinisikan potongan -potongan sintaks baru yang ditambahkan ke bahasa dengan proposal ini.
Kami mendefinisikan ekspresi rekaman atau tuple dengan menggunakan #
pengubah di depan objek atau ekspresi array yang normal.
# { }
# { a : 1 , b : 2 }
# { a : 1 , b : # [ 2 , 3 , # { c : 4 } ] }
# [ ]
# [ 1 , 2 ]
# [ 1 , 2 , # { a : 3 } ]
Lubang dicegah dalam sintaks, tidak seperti array, yang memungkinkan lubang. Lihat masalah #84 untuk diskusi lebih lanjut.
const x = # [ , ] ; // SyntaxError, holes are disallowed by syntax
Menggunakan __proto__
pengidentifikasi sebagai properti dicegah dalam sintaks. Lihat masalah #46 untuk diskusi lebih lanjut.
const x = # { __proto__ : foo } ; // SyntaxError, __proto__ identifier prevented by syntax
const y = # { [ "__proto__" ] : foo } ; // valid, creates a record with a "__proto__" property.
Metode singkat dilarang dalam sintaks catatan.
# { method ( ) { } } // SyntaxError
Catatan mungkin hanya memiliki kunci string, bukan kunci simbol, karena masalah yang dijelaskan dalam #15. Membuat catatan dengan kunci simbol adalah TypeError
.
const record = # { [ Symbol ( ) ] : # { } } ;
// TypeError: Record may only have string as keys
Catatan dan tupel hanya mengandung primitif dan catatan dan tupel lainnya. Mencoba membuat Record
atau Tuple
yang berisi Object
( null
bukan objek) atau Function
melempar TypeError
.
const obj = { } ;
const record = # { prop : obj } ; // TypeError: Record may only contain primitive values
Kesetaraan catatan dan tupel berfungsi seperti jenis JS primitif lainnya seperti nilai boolean dan string, dibandingkan dengan konten, bukan identitas:
assert ( # { a : 1 } === # { a : 1 } ) ;
assert ( # [ 1 , 2 ] === # [ 1 , 2 ] ) ;
Ini berbeda dari bagaimana kesetaraan bekerja untuk objek JS: Perbandingan objek akan mengamati bahwa setiap objek berbeda:
assert ( { a : 1 } !== { a : 1 } ) ;
assert ( Object ( # { a : 1 } ) !== Object ( # { a : 1 } ) ) ;
assert ( Object ( # [ 1 , 2 ] ) !== Object ( # [ 1 , 2 ] ) ) ;
Urutan penyisipan kunci catatan tidak mempengaruhi kesetaraan catatan, karena tidak ada cara untuk mengamati urutan asli dari tombol, karena mereka secara implisit diurutkan:
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"]
Jika struktur dan isinya sangat identik, maka nilai Record
dan Tuple
dianggap sama sesuai dengan semua operasi kesetaraan: Object.is
, ==
, ===
, dan algoritma Samevaluzero internal (digunakan untuk membandingkan kunci peta dan set)) . Mereka berbeda dalam hal bagaimana -0
diperlakukan:
Object.is
memperlakukan -0
dan 0
sebagai tidak setara==
, ===
dan samevaluzero memperlakukan -0
dengan 0
sama Perhatikan bahwa ==
dan ===
lebih langsung tentang jenis nilai lain yang bersarang dalam catatan dan tupel-kembali true
jika dan hanya jika isinya identik (dengan pengecualian 0
/ -0
). Ketajaman ini memiliki implikasi untuk NaN
serta perbandingan antar jenis. Lihat contoh di bawah ini.
Lihat diskusi lebih lanjut di #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
dan Tuple
Secara umum, Anda dapat memperlakukan catatan seperti objek. Misalnya, Object
namespace dan in
bekerja dengan 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
dan Pembungkus Tuple
Pengembang JS biasanya tidak perlu memikirkan objek Record
dan Tuple
wrapper, tetapi mereka adalah bagian penting dari bagaimana catatan dan tupel bekerja "di bawah kap" dalam spesifikasi JavaScript.
Mengakses catatan atau tuple via .
atau []
mengikuti GetValue
Semantik yang khas, yang secara implisit dikonversi ke instance dari tipe pembungkus yang sesuai. Anda juga dapat melakukan konversi secara eksplisit melalui Object()
:
Object(record)
Membuat Objek Pembungkus RekamObject(tuple)
membuat objek pembungkus tuple (Orang bisa membayangkan bahwa new Record
atau new Tuple
dapat membuat pembungkus ini, seperti new Number
dan new String
, tetapi catatan dan tupel mengikuti konvensi yang lebih baru yang ditetapkan oleh simbol dan bigint, membuat kasus -kasus ini melempar, karena itu bukan jalan yang kita inginkan Dorong programmer untuk diambil.)
Objek rekaman dan tuple wrapper memiliki semua properti mereka sendiri dengan atribut writable: false, enumerable: true, configurable: false
. Objek pembungkus tidak dapat diperluas. Semua disatukan, mereka berperilaku sebagai benda beku. Ini berbeda dari objek pembungkus yang ada di JavaScript, tetapi perlu untuk memberikan jenis kesalahan yang Anda harapkan dari manipulasi biasa pada catatan dan tupel.
Sebuah contoh Record
memiliki kunci dan nilai yang sama dengan nilai record
yang mendasarinya. __proto__
dari masing -masing objek pembungkus rekaman ini adalah null
(diskusi: #71).
Sebuah contoh Tuple
memiliki kunci yang merupakan bilangan bulat yang sesuai dengan masing -masing indeks dalam nilai tuple
yang mendasarinya. Nilai untuk masing -masing tombol ini adalah nilai yang sesuai dalam tuple
asli. Selain itu, ada kunci length
yang tidak dapat diasumsikan. Secara keseluruhan, properti ini cocok dengan objek pembungkus String
. Yaitu, Object.getOwnPropertyDescriptors(Object(#["a", "b"]))
dan Object.getOwnPropertyDescriptors(Object("ab"))
masing -masing mengembalikan objek yang terlihat seperti ini:
{
"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__
dari objek pembungkus tuple adalah Tuple.prototype
. Perhatikan bahwa, jika Anda bekerja di berbagai objek Global JavaScript ("Realms"), Tuple.prototype
dipilih berdasarkan ranah saat ini ketika konversi objek dilakukan, mirip dengan bagaimana .prototype
perilaku primitif lainnya - itu adalah perilaku - itu adalah. tidak melekat pada nilai tuple itu sendiri. Tuple.prototype
memiliki berbagai metode di atasnya, analog dengan array.
Untuk integritas, pengindeksan numerik di luar batas pada pengembalian tupel undefined
, daripada meneruskan melalui rantai prototipe, seperti dengan typedArrays. Pencarian tombol properti non-numerik ke depan hingga Tuple.prototype
, yang penting untuk menemukan metode seperti array mereka.
Record
dan Tuple
Nilai Tuple
memiliki fungsionalitas analog secara luas dengan Array
. Demikian pula, nilai Record
didukung oleh metode statis Object
yang berbeda.
assert . deepEqual ( Object . keys ( # { a : 1 , b : 2 } ) , [ "a" , "b" ] ) ;
assert ( # [ 1 , 2 , 3 ] . map ( x => x * 2 ) , # [ 2 , 4 , 6 ] ) ;
Lihat Lampiran untuk mempelajari lebih lanjut tentang Record
& Tuple
Namespaces.
Anda dapat mengonversi struktur menggunakan Record()
, Tuple()
(dengan operator spread), Record.fromEntries()
atau 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
Perhatikan bahwa Record()
, Tuple()
, Record.fromEntries()
dan Tuple.from()
mengharapkan koleksi yang terdiri dari catatan, tupel atau primitif lainnya (seperti angka, string, dll). Referensi objek bersarang akan menyebabkan typeerror. Terserah penelepon untuk mengonversi struktur dalam dengan cara apa pun yang sesuai untuk aplikasi.
Catatan : Proposal draf saat ini tidak mengandung rutinitas konversi rekursif, hanya yang dangkal. Lihat Diskusi di #122
Seperti array, tupel dapat berulang.
const tuple = # [ 1 , 2 ] ;
// output is:
// 1
// 2
for ( const o of tuple ) { console . log ( o ) ; }
Demikian pula dengan objek, catatan hanya dapat diulang dalam hubungannya dengan API seperti Object.entries
. Masuk.
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)
setara dengan memanggil JSON.stringify
pada objek yang dihasilkan dari secara rekursif mengonversi catatan ke objek yang tidak berisi catatan atau tupel.JSON.stringify(tuple)
setara dengan memanggil JSON.stringify
pada array yang dihasilkan dari secara rekursif mengonversi tuple menjadi array yang tidak berisi catatan atau tupel. JSON . stringify ( # { a : # [ 1 , 2 , 3 ] } ) ; // '{"a":[1,2,3]}'
JSON . stringify ( # [ true , # { a : # [ 1 , 2 , 3 ] } ] ) ; // '[true,{"a":[1,2,3]}]'
Silakan lihat https://github.com/tc39/proposal-json-parseimmutable
Tuple.prototype
Tuple
mendukung metode instan yang mirip dengan array dengan beberapa perubahan:
Tuple.prototype.push
.Tuple.prototype.withAt
. Lampiran berisi deskripsi lengkap prototipe Tuple
.
typeof
typeof
mengidentifikasi catatan dan tupel sebagai tipe yang berbeda:
assert ( typeof # { a : 1 } === "record" ) ;
assert ( typeof # [ 1 , 2 ] === "tuple" ) ;
Map
| Set
| WeakMap
| WeakSet
} Dimungkinkan untuk menggunakan Record
atau Tuple
sebagai kunci dalam Map
, dan sebagai nilai dalam satu Set
. Saat menggunakan Record
atau Tuple
di sini, mereka dibandingkan dengan nilai.
Tidak mungkin menggunakan Record
atau Tuple
sebagai kunci dalam WeakMap
atau sebagai nilai dalam WeakSet
, karena Records
dan Tuple
S bukan Objects
, dan masa hidupnya tidak dapat diamati.
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 ) ;
Salah satu manfaat inti dari catatan dan proposal tupel adalah bahwa mereka dibandingkan dengan isinya, bukan identitas mereka. Pada saat yang sama, ===
Dalam JavaScript pada objek memiliki semantik yang sangat jelas dan konsisten: untuk membandingkan objek dengan identitas. Membuat catatan dan tupel primitif memungkinkan perbandingan berdasarkan nilainya.
Pada tingkat tinggi, objek/perbedaan primitif membantu membentuk garis keras antara dunia yang sangat tidak berubah, bebas konteks, bebas identitas dan dunia objek yang dapat berubah di atasnya. Perpecahan kategori ini membuat desain dan model mental lebih jelas.
Alternatif untuk mengimplementasikan catatan dan tuple sebagai primitif adalah dengan menggunakan operator kelebihan untuk mencapai hasil yang sama, dengan menerapkan operator abstrak yang kelebihan beban ( ==
) yang sangat membandingkan objek. Meskipun ini mungkin, itu tidak memenuhi kasus penggunaan penuh, karena operator kelebihan beban tidak memberikan override untuk operator ===
. Kami ingin operator kesetaraan yang ketat ( ===
) menjadi pemeriksaan "identitas" yang andal untuk objek dan "nilai yang dapat diamati" (modulo -0/+0/nan) untuk tipe primitif.
Opsi lain adalah melakukan apa yang disebut Interning : Kami melacak objek rekaman atau tuple secara global dan jika kami mencoba membuat yang baru yang kebetulan identik dengan objek rekaman yang ada, kami sekarang merujuk catatan yang ada ini alih -alih membuat yang baru. Ini pada dasarnya apa yang dilakukan Polyfill. Kami sekarang menyamakan nilai dan identitas. Pendekatan ini menciptakan masalah begitu kami memperluas perilaku itu di berbagai konteks JavaScript dan tidak akan memberikan kekhasan yang mendalam pada dasarnya dan sangat lambat yang akan membuat penggunaan & tuple pilihan kinerja-negatif.
Record & Tuple dibangun untuk beroperasi dengan objek dan array dengan baik: Anda dapat membacanya dengan cara yang persis sama seperti yang Anda lakukan dengan objek dan array. Perubahan utama terletak pada kekekalan yang dalam dan perbandingan dengan nilai alih -alih identitas.
Pengembang yang digunakan untuk memanipulasi objek dengan cara yang tidak berubah (seperti mengubah potongan redux state) akan dapat terus melakukan manipulasi yang sama yang biasa mereka lakukan pada objek dan array, kali ini, dengan lebih banyak jaminan.
Kami akan melakukan penelitian empiris melalui wawancara dan survei untuk mencari tahu apakah ini berhasil seperti yang kami pikirkan.
.get()
/ .set()
Metode seperti Immutable.js?Jika kita ingin tetap mengakses Record & Tuple mirip dengan objek dan array seperti yang dijelaskan di bagian sebelumnya, kita tidak dapat mengandalkan metode untuk melakukan akses itu. Melakukan hal itu akan mengharuskan kita untuk kode cabang saat mencoba membuat fungsi "generik" dapat mengambil objek/array/catatan/tupel.
Berikut adalah contoh fungsi yang memiliki dukungan untuk catatan yang tidak dapat diubah.js dan objek biasa:
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
Ini rentan kesalahan karena kedua cabang dapat dengan mudah keluar dari sinkronisasi dari waktu ke waktu ...
Inilah bagaimana kami akan menulis fungsi yang mengambil catatan dari proposal ini dan objek biasa:
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
Fungsi ini mendukung kedua objek dan catatan dalam satu jalur kode serta tidak memaksa konsumen untuk memilih struktur data mana yang akan digunakan.
Mengapa kita perlu mendukung keduanya secara bersamaan? Ini terutama untuk menghindari perpecahan ekosistem. Katakanlah kami menggunakan Immutable.
state . jobResult = Immutable . fromJS (
ExternalLib . processJob (
state . jobDescription . toJS ( )
)
) ;
Baik toJS()
dan fromJS()
dapat menjadi operasi yang sangat mahal tergantung pada ukuran substruktur. Perpecahan ekosistem berarti konversi yang, pada gilirannya, berarti masalah kinerja yang mungkin.
Sintaks yang diusulkan secara signifikan meningkatkan ergonomi menggunakan Record
dan Tuple
dalam kode. Misalnya:
// 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 ,
) ,
} ) ,
} ) ,
} ) ;
Sintaks yang diusulkan dimaksudkan untuk lebih sederhana dan lebih mudah dipahami, karena sengaja mirip dengan sintaks untuk objek dan literal array. Ini memanfaatkan keakraban pengguna yang ada dengan objek dan array. Selain itu, contoh kedua memperkenalkan literal objek sementara tambahan, yang menambah kompleksitas ekspresi.
Menggunakan kata kunci sebagai awalan ke objek standar/array sintaks literal menyajikan masalah seputar kompatibilitas ke belakang. Selain itu, menggunakan kembali kata kunci yang ada dapat memperkenalkan ambiguitas.
Ecmascript mendefinisikan satu set kata kunci yang dipesan yang dapat digunakan untuk ekstensi di masa mendatang ke bahasa. Mendefinisikan kata kunci baru yang belum dicadangkan secara teori mungkin, tetapi membutuhkan upaya yang signifikan untuk memvalidasi bahwa kata kunci baru kemungkinan tidak akan memecah kompatibilitas ke belakang.
Menggunakan kata kunci yang dipesan membuat proses ini lebih mudah, tetapi ini bukan solusi yang sempurna karena tidak ada kata kunci yang dipesan yang sesuai dengan "niat" fitur, selain const
. Kata kunci const
juga rumit, karena menggambarkan konsep yang sama (kekekalan referensi variabel) sementara proposal ini bermaksud untuk menambahkan struktur data yang tidak dapat diubah. Sementara kekekalan adalah utas umum antara kedua fitur ini, ada umpan balik masyarakat yang signifikan yang menunjukkan bahwa menggunakan const
dalam kedua konteks tidak diinginkan.
Alih -alih menggunakan kata kunci, {| |}
dan [||]
telah disarankan sebagai alternatif yang memungkinkan. Saat ini, The Champion Group condong ke arah #[]
/ #{}
, tetapi diskusi sedang berlangsung di #10.
Definisi catatan & tuple sebagai primitif majemuk memaksa segala sesuatu dalam catatan & tuple untuk tidak menjadi objek. Ini datang dengan beberapa kelemahan (referensi objek menjadi lebih sulit tetapi masih mungkin) tetapi juga lebih banyak jaminan untuk menghindari kesalahan pemrograman umum.
const object = {
a : {
foo : "bar" ,
} ,
} ;
Object . freeze ( object ) ;
func ( object ) ;
// func is able to mutate object’s keys even if object is frozen
Dalam contoh di atas, kami mencoba membuat jaminan kekekalan dengan Object.freeze
. Freeze. Sayangnya, karena kami tidak membekukan objek secara mendalam, tidak ada yang memberi tahu kami bahwa object.a
itu. A tidak tersentuh. Dengan catatan & tuple yang merupakan kendala pada dasarnya dan tidak ada keraguan bahwa strukturnya tidak tersentuh:
const record = # {
a : # {
foo : "bar" ,
} ,
} ;
func ( record ) ;
// runtime guarantees that record is entirely unchanged
assert ( record . a . foo === "bar" ) ;
Akhirnya, kekekalan yang dalam menekan kebutuhan akan pola umum yang terdiri dari objek yang mengepuh dalam untuk menjaga jaminan:
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" ) ;
Secara umum, operator spread bekerja dengan baik untuk ini:
// 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]
Dan jika Anda mengubah sesuatu dalam tuple, metode Tuple.prototype.with
berfungsi:
// Change a Tuple index
let tup = # [ 1 , 2 , 3 ] ;
tup . with ( 1 , 500 ) // #[1, 500, 3]
Beberapa manipulasi "jalur dalam" bisa sedikit canggung. Untuk itu, Properti Path Deep untuk Proposal Catatan menambahkan sintaks steno tambahan untuk merekam literal.
Kami sedang mengembangkan proposal Properti Path Deep sebagai proposal tindak lanjut yang terpisah karena kami tidak melihatnya sebagai inti untuk menggunakan catatan, yang bekerja dengan baik secara mandiri. Ini adalah jenis penambahan sintaksis yang akan bekerja dengan baik untuk prototipe dari waktu ke waktu dalam transpiler, dan di mana kami memiliki banyak titik keputusan yang tidak ada hubungannya dengan catatan dan tupel (misalnya, cara kerjanya dengan objek).
Kami telah berbicara dengan juara koleksi readone, dan kedua kelompok sepakat bahwa ini adalah pelengkap:
Tidak ada satu pun yang merupakan bagian dari yang lain dalam hal fungsionalitas. Paling -paling, mereka paralel, sama seperti setiap proposal sejajar dengan jenis koleksi lainnya dalam bahasa tersebut.
Jadi, kedua kelompok juara telah memutuskan untuk memastikan bahwa proposal tersebut secara paralel sehubungan dengan satu sama lain . Misalnya, proposal ini menambahkan metode Tuple.prototype.withReversed
baru. Idenya adalah untuk memeriksa, selama proses desain, jika tanda tangan ini juga masuk akal untuk array hanya baca (jika ada): kami mengekstraksi metode baru ini ke array perubahan dengan proposal salin, sehingga kami dapat membahas API yang membangun model mental yang konsisten dan bersama.
Dalam draft proposal saat ini, tidak ada jenis yang tumpang tindih untuk jenis data yang sama, tetapi kedua proposal dapat tumbuh di arah ini di masa depan, dan kami mencoba memikirkan hal -hal ini sebelumnya. Siapa tahu, suatu hari TC39 dapat memutuskan untuk menambahkan Jenis RecordMap dan Recordset primitif, sebagai versi set dan peta yang sangat tidak dapat diubah! Dan ini akan paralel dengan jenis koleksi yang dibaca.
TC39 telah lama membahas "tipe nilai", yang akan menjadi semacam deklarasi kelas untuk tipe primitif, selama beberapa tahun, hidup dan mati. Versi sebelumnya dari proposal ini bahkan berusaha. Proposal ini mencoba untuk memulai sederhana dan minimal, hanya menyediakan struktur inti. Harapannya adalah dapat memberikan model data untuk proposal di masa depan untuk kelas.
Proposal ini secara longgar terkait dengan serangkaian proposal yang lebih luas, termasuk kelebihan muatan operator dan literal numerik yang diperluas: ini semua berkonspirasi untuk memberikan cara bagi tipe yang ditentukan pengguna untuk melakukan hal yang sama dengan BigInt. Namun, idenya adalah untuk menambahkan fitur -fitur ini jika kami menentukan mereka termotivasi secara mandiri.
Jika kita memiliki tipe primitif/nilai yang ditentukan pengguna, maka masuk akal untuk menggunakannya dalam fitur bawaan, seperti CSS yang diketik OM atau proposal temporal. Namun, ini jauh di masa depan, jika itu terjadi; Untuk saat ini, ini berfungsi dengan baik untuk menggunakan objek untuk fitur semacam ini.
Meskipun kedua jenis catatan berhubungan dengan objek, dan kedua jenis tupel berhubungan dengan array, di situlah kesamaan berakhir.
Catatan dalam TypeScript adalah tipe utilitas generik untuk mewakili objek yang mengambil jenis kunci yang cocok dengan tipe nilai. Mereka masih mewakili benda.
Demikian juga, tupel dalam naskah adalah notasi untuk mengekspresikan jenis dalam array dengan ukuran terbatas (dimulai dengan naskah 4.0 mereka memiliki bentuk variadik). Tupel dalam naskah adalah cara untuk mengekspresikan array dengan tipe heterogen. Tupel ecmascript dapat sesuai dengan array TS atau Ts Tsle dengan mudah karena mereka dapat mengandung jumlah nilai yang tidak terbatas dari tipe yang sama atau berisi jumlah nilai terbatas dengan jenis yang berbeda.
Catatan atau tupel adalah fitur ortogonal untuk catatan ecmascript dan tupel dan keduanya dapat diekspresikan pada saat yang sama:
const record : Readonly < Record < string , number > > = # {
foo : 1 ,
bar : 2 ,
} ;
const tuple : readonly [ number , string ] = # [ 1 , "foo" ] ;
Proposal ini tidak membuat jaminan kinerja apa pun dan tidak memerlukan optimasi spesifik dalam implementasi. Berdasarkan umpan balik dari pelaksana, diharapkan bahwa mereka akan menerapkan operasi umum melalui algoritma "waktu linier". Namun, proposal ini tidak mencegah beberapa optimasi klasik untuk struktur data murni fungsional, termasuk tetapi tidak terbatas pada:
Optimalisasi ini analog dengan cara mesin JavaScript modern menangani gabungan string, dengan berbagai jenis string internal yang berbeda. Validitas optimisasi ini bertumpu pada ketidakterservinan identitas catatan dan tupel. Tidak diharapkan bahwa semua mesin akan bertindak identik sehubungan dengan optimasi ini, tetapi masing -masing akan membuat keputusan tentang heuristik tertentu yang akan digunakan. Sebelum tahap 4 dari proposal ini, kami berencana untuk menerbitkan panduan untuk praktik terbaik untuk penggunaan catatan dan tupel yang dapat dioptimalkan, berdasarkan pengalaman implementasi yang akan kami miliki pada saat itu.
Struktur data tipe primitif primitif yang baru, sangat tidak dapat diubah, diusulkan dalam dokumen ini, yang analog dengan objek. #{ a: 1, b: 2 }
Struktur data tipe primitif yang baru, sangat tidak dapat diubah, diusulkan dalam dokumen ini, yang analog dengan array. #[1, 2, 3, 4]
Nilai -nilai yang bertindak seperti primitif JavaScript lainnya, tetapi terdiri dari nilai -nilai konstituen lainnya. Dokumen ini mengusulkan dua tipe primitif senyawa pertama: Record
dan Tuple
.
String
, Number
, Boolean
, undefined
, null
, Symbol
dan BigInt
Hal -hal yang merupakan tipe primitif majemuk atau sederhana. Semua primitif dalam JavaScript berbagi properti tertentu:
Struktur data yang tidak menerima operasi yang mengubahnya secara internal, tetapi sebaliknya memiliki operasi yang mengembalikan nilai baru yang merupakan hasil dari penerapan operasi itu.
Dalam Record
proposal dan Tuple
ini adalah struktur data yang sangat tidak dapat diubah.
Operator ===
didefinisikan dengan algoritma perbandingan kesetaraan yang ketat. Kesetaraan yang ketat mengacu pada gagasan kesetaraan khusus ini.
Berbagi struktural adalah teknik yang digunakan untuk membatasi jejak memori dari struktur data yang tidak dapat diubah. Singkatnya, ketika menerapkan operasi untuk mendapatkan versi baru dari struktur yang tidak dapat diubah, berbagi struktural akan berusaha menjaga sebagian besar struktur internal tetap utuh dan digunakan oleh versi lama dan turunan dari struktur itu. Ini sangat membatasi jumlah yang akan disalin untuk mendapatkan struktur baru.