Mengapa objek tidak dapat diulang secara default?
Saya melihat pertanyaan sepanjang waktu terkait dengan objek iterasi, solusi yang umum adalah mengulang properti objek dan mengakses nilai dalam objek dengan cara itu. Ini tampak sangat umum sehingga membuat saya bertanya-tanya mengapa objek itu sendiri tidak dapat diulang.
Pernyataan seperti ES6 for...of
akan bagus digunakan untuk objek secara default. Karena fitur ini hanya tersedia untuk "objek iterable" khusus yang tidak menyertakan {}
objek, kita harus melalui lingkaran untuk membuat ini berfungsi untuk objek yang ingin kita gunakan.
Pernyataan for ... of membuat loop Iterasi di atas objek yang dapat diulang (termasuk Array, Map, Set, objek argumen, dan sebagainya) ...
Misalnya menggunakan fungsi generator ES6 :
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(example)) {
console.log(key);
console.log(value);
for (let [key, value] of entries(value)) {
console.log(key);
console.log(value);
}
}
Data log di atas dengan benar dalam urutan yang saya harapkan ketika saya menjalankan kode di Firefox (yang mendukung ES6 ):
Secara default, {}
objek tidak dapat diulang, tapi mengapa? Apakah kerugiannya lebih besar daripada manfaat potensial dari objek yang dapat diulang? Apa masalah yang terkait dengan ini?
Selain itu, karena {}
objek yang berbeda dari "Array-seperti" koleksi dan "iterable objek" seperti NodeList
, HtmlCollection
, dan arguments
, mereka tidak dapat diubah menjadi Array.
Sebagai contoh:
var argumentsArray = Array.prototype.slice.call(arguments);
atau digunakan dengan metode Array:
Array.prototype.forEach.call(nodeList, function (element) {})
.
Selain pertanyaan yang saya miliki di atas, saya ingin melihat contoh kerja tentang cara membuat {}
objek menjadi iterable, terutama dari mereka yang telah menyebutkan [Symbol.iterator]
. Ini harus memungkinkan {}
"objek iterable" baru ini untuk menggunakan pernyataan seperti for...of
. Juga, saya bertanya-tanya apakah membuat objek iterable memungkinkannya diubah menjadi Array.
Saya mencoba kode di bawah ini, tetapi saya mendapatkan TypeError: can't convert undefined to object
.
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
// I want to be able to use "for...of" for the "example" object.
// I also want to be able to convert the "example" object into an Array.
example[Symbol.iterator] = function* (obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
};
for (let [key, value] of example) { console.log(value); } // error
console.log([...example]); // error
Symbol.iterator
properti bersifat iterable. Jadi, Anda hanya perlu menerapkan properti itu. Satu penjelasan yang mungkin mengapa objek tidak dapat diulang bisa jadi bahwa ini menyiratkan semuanya dapat diulang, karena semuanya adalah objek (kecuali primitif tentu saja). Namun, apa artinya mengulang fungsi atau objek ekspresi reguler?Jawaban:
Saya akan mencoba ini. Perhatikan bahwa saya tidak berafiliasi dengan ECMA dan tidak dapat melihat proses pengambilan keputusan mereka, jadi saya tidak dapat secara pasti mengatakan mengapa mereka telah atau belum melakukan apa pun. Namun, saya akan menyatakan asumsi saya dan melakukan yang terbaik.
1. Mengapa menambahkan
for...of
konstruksi di tempat pertama?JavaScript sudah menyertakan
for...in
konstruksi yang dapat digunakan untuk mengulang properti suatu objek. Namun, ini sebenarnya bukan perulangan forEach , karena ia menyebutkan semua properti pada sebuah objek dan cenderung hanya bekerja dengan diprediksi dalam kasus sederhana.Ini rusak dalam kasus yang lebih kompleks (termasuk dengan larik, di mana penggunaannya cenderung tidak disarankan atau sepenuhnya dikaburkan oleh pengamanan yang diperlukan untuk digunakan
for...in
dengan larik dengan benar ). Anda dapat mengatasinya dengan menggunakanhasOwnProperty
(antara lain), tetapi itu agak kikuk dan tidak elegan.Jadi oleh karena itu asumsi saya adalah bahwa
for...of
konstruksi sedang ditambahkan untuk mengatasi kekurangan yang terkait denganfor...in
konstruksi tersebut, dan memberikan utilitas dan fleksibilitas yang lebih besar saat melakukan iterasi. Orang cenderung memperlakukanfor...in
sebagaiforEach
loop yang secara umum dapat diterapkan pada koleksi apa pun dan menghasilkan hasil yang waras dalam konteks yang memungkinkan, tetapi bukan itu yang terjadi. Thefor...of
Loop perbaikan itu.Saya juga berasumsi bahwa penting untuk kode ES5 yang ada untuk berjalan di bawah ES6 dan menghasilkan hasil yang sama seperti di bawah ES5, jadi perubahan yang melanggar tidak dapat dilakukan, misalnya, pada perilaku
for...in
konstruksi.2. Bagaimana cara
for...of
kerjanya?The dokumentasi referensi berguna untuk bagian ini. Secara spesifik, sebuah objek dipertimbangkan
iterable
jika ia mendefinisikanSymbol.iterator
properti.Definisi properti harus berupa fungsi yang mengembalikan item dalam koleksi, satu, oleh, satu, dan menyetel bendera yang menunjukkan apakah ada lebih banyak item untuk diambil atau tidak. Implementasi yang telah ditentukan disediakan untuk beberapa tipe objek , dan cukup jelas bahwa
for...of
hanya menggunakan delegasi ke fungsi iterator.Pendekatan ini berguna, karena membuatnya sangat mudah untuk menyediakan iterator Anda sendiri. Saya dapat mengatakan bahwa pendekatan tersebut dapat menyajikan masalah praktis karena ketergantungannya pada pendefinisian properti yang sebelumnya tidak ada, kecuali dari apa yang dapat saya katakan bukan itu masalahnya karena properti baru pada dasarnya diabaikan kecuali Anda sengaja mencarinya (mis. itu tidak akan muncul dalam
for...in
loop sebagai kunci, dll.). Jadi bukan itu masalahnya.Terlepas dari non-masalah praktis, mungkin telah dianggap kontroversial secara konseptual untuk memulai semua objek dengan properti baru yang telah ditentukan sebelumnya, atau secara implisit mengatakan bahwa "setiap objek adalah koleksi".
3. Mengapa objek tidak
iterable
digunakanfor...of
secara default?Dugaan saya adalah bahwa ini adalah kombinasi dari:
iterable
secara default mungkin dianggap tidak dapat diterima karena menambahkan properti yang sebelumnya tidak ada, atau karena objek tidak (harus) merupakan koleksi. Seperti yang Felix catat, "apa artinya mengulang fungsi atau objek ekspresi reguler"?for...in
, dan tidak jelas apa implementasi iterator built-in bisa dilakukan secara berbeda / lebih baik darifor...in
perilaku yang ada . Jadi, meskipun # 1 salah dan menambahkan properti dapat diterima, mungkin tidak terlihat berguna .iterable
dapat dengan mudah melakukannya, dengan mendefinisikanSymbol.iterator
properti.iterable
default dan memiliki beberapa keuntungan kecil lainnya dibandingkan menggunakan objek biasa sebagaiMap
.Bahkan ada contoh yang diberikan untuk # 3 dalam dokumentasi referensi:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; for (var value of myIterable) { console.log(value); }
Mengingat bahwa objek dapat dengan mudah dibuat
iterable
, bahwa mereka sudah dapat diiterasi menggunakanfor...in
, dan bahwa ada kemungkinan tidak ada kesepakatan yang jelas tentang apa yang harus dilakukan oleh iterator objek default (jika apa yang dilakukannya dimaksudkan untuk entah bagaimana berbeda dari apa yangfor...in
dilakukannya), tampaknya masuk akal cukup sehingga objek tidak dibuatiterable
secara default.Perhatikan bahwa kode contoh Anda dapat ditulis ulang menggunakan
for...in
:for (let levelOneKey in object) { console.log(levelOneKey); // "example" console.log(object[levelOneKey]); // {"random":"nest","another":"thing"} var levelTwoObj = object[levelOneKey]; for (let levelTwoKey in levelTwoObj ) { console.log(levelTwoKey); // "random" console.log(levelTwoObj[levelTwoKey]); // "nest" } }
... atau Anda juga dapat membuat objek
iterable
sesuai keinginan dengan melakukan sesuatu seperti berikut (atau Anda dapat membuat semua objekiterable
dengan menetapkannyaObject.prototype[Symbol.iterator]
):obj = { a: '1', b: { something: 'else' }, c: 4, d: { nested: { nestedAgain: true }} }; obj[Symbol.iterator] = function() { var keys = []; var ref = this; for (var key in this) { //note: can do hasOwnProperty() here, etc. keys.push(key); } return { next: function() { if (this._keys && this._obj && this._index < this._keys.length) { var key = this._keys[this._index]; this._index++; return { key: key, value: this._obj[key], done: false }; } else { return { done: true }; } }, _index: 0, _keys: keys, _obj: ref }; };
Anda dapat memainkannya di sini (di Chrome, sewa): http://jsfiddle.net/rncr3ppz/5/
Sunting
Dan sebagai tanggapan atas pertanyaan Anda yang telah diperbarui, ya, dimungkinkan untuk mengonversi sebuah
iterable
menjadi larik, menggunakan operator sebaran di ES6.Namun, ini sepertinya belum berfungsi di Chrome, atau setidaknya saya tidak bisa membuatnya berfungsi di jsFiddle saya. Secara teori, ini harus sesederhana:
var array = [...myIterable];
sumber
obj[Symbol.iterator] = obj[Symbol.enumerate]
di contoh terakhir Anda?[[enumerate]]
bukan simbol yang terkenal (@@ enumerate) tetapi metode internal. Saya harus menjadiobj[Symbol.iterator] = function(){ return Reflect.enumerate(this) }
for...in
, yang memiliki tujuan berbeda - untuk beralih di seluruh properti suatu objek.Map
untuk saat ini.Object
s tidak mengimplementasikan protokol iterasi dalam Javascript untuk alasan yang sangat bagus. Ada dua tingkat di mana properti objek dapat diiterasi dalam JavaScript:Iterasi Tingkat Program
Ketika Anda melakukan iterasi pada sebuah objek pada level program, Anda memeriksa sebagian dari struktur program Anda. Ini adalah operasi reflektif. Mari kita ilustrasikan pernyataan ini dengan tipe array, yang biasanya diulangi pada tingkat data:
const xs = [1,2,3]; xs.f = function f() {}; for (let i in xs) console.log(xs[i]); // logs `f` as well
Kami baru saja memeriksa tingkat program
xs
. Karena array menyimpan urutan data, kami secara teratur hanya tertarik pada level data.for..in
jelas tidak masuk akal dalam kaitannya dengan array dan struktur "berorientasi data" lainnya dalam banyak kasus. Itulah alasan mengapa ES2015 diperkenalkanfor..of
dan protokol yang dapat diulang.Iterasi Tingkat Data
Apakah itu berarti bahwa kita dapat dengan mudah membedakan data dari level program dengan membedakan fungsi dari tipe primitif? Tidak, karena fungsi juga dapat berupa data dalam Javascript:
Array.prototype.sort
misalnya mengharapkan suatu fungsi untuk melakukan algoritma pengurutan tertentu() => 1 + 2
hanyalah pembungkus fungsional untuk nilai yang dievaluasi dengan malasSelain itu nilai primitif dapat mewakili level program juga:
[].length
misalnya aNumber
tetapi mewakili panjang sebuah array dan dengan demikian menjadi milik domain programArtinya kita tidak bisa membedakan program dan level data hanya dengan memeriksa jenis.
Penting untuk dipahami bahwa penerapan protokol iterasi untuk objek Javascript lama yang biasa akan bergantung pada tingkat data. Namun seperti yang baru saja kita lihat, perbedaan yang dapat diandalkan antara data dan iterasi tingkat program tidak dimungkinkan.
Dengan
Array
s perbedaan ini sepele: Setiap elemen dengan kunci seperti integer adalah elemen data.Object
s memiliki fitur yang setara:enumerable
Deskriptor. Tetapi apakah benar-benar disarankan untuk mengandalkan ini? Saya yakin tidak! Artienumerable
deskriptornya terlalu kabur.Kesimpulan
Tidak ada cara yang berarti untuk mengimplementasikan protokol iterasi untuk objek, karena tidak setiap objek adalah kumpulan.
Jika properti objek dapat diulang secara default, program dan tingkat data akan tercampur. Karena setiap tipe komposit dalam Javascript didasarkan pada objek biasa, ini akan berlaku untuk
Array
danMap
juga.for..in
,Object.keys
,Reflect.ownKeys
Dll dapat digunakan untuk kedua refleksi dan data iterasi, perbedaan yang jelas secara teratur tidak mungkin. Jika Anda tidak berhati-hati, Anda akan berakhir dengan cepat dengan pemrograman meta dan ketergantungan yang aneh. TheMap
tipe data abstrak secara efektif mengakhiri conflating program dan tingkat data. Saya yakinMap
ini adalah pencapaian paling signifikan di ES2015, meskipunPromise
jauh lebih menarik.sumber
Saya kira pertanyaan harus "mengapa tidak ada built-in objek iterasi?
Menambahkan iterabilitas ke objek itu sendiri dapat menimbulkan konsekuensi yang tidak diinginkan, dan tidak, tidak ada cara untuk menjamin keteraturan, tetapi menulis iterator sesederhana
function* iterate_object(o) { var keys = Object.keys(o); for (var i=0; i<keys.length; i++) { yield [keys[i], o[keys[i]]]; } }
Kemudian
for (var [key, val] of iterate_object({a: 1, b: 2})) { console.log(key, val); } a 1 b 2
sumber
[Symbol.iterator]
serta jika Anda dapat memperluas konsekuensi yang tidak diinginkan tersebut.Anda dapat dengan mudah membuat semua objek dapat diulang secara global:
Object.defineProperty(Object.prototype, Symbol.iterator, { enumerable: false, value: function * (){ for(let key in this){ if(this.hasOwnProperty(key)){ yield [key, this[key]]; } } } });
sumber
Ini adalah pendekatan terbaru (yang berfungsi di chrome canary)
var files = { '/root': {type: 'directory'}, '/root/example.txt': {type: 'file'} }; for (let [key, {type}] of Object.entries(files)) { console.log(type); }
Ya
entries
sekarang adalah metode yang merupakan bagian dari Objek :)edit
Setelah melihat lebih dalam, sepertinya Anda bisa melakukan hal berikut
Object.prototype[Symbol.iterator] = function * () { for (const [key, value] of Object.entries(this)) { yield {key, value}; // or [key, value] } };
jadi Anda sekarang bisa melakukan ini
for (const {key, value:{type}} of files) { console.log(key, type); }
edit2
Kembali ke contoh awal Anda, jika Anda ingin menggunakan metode prototipe di atas, akan seperti ini
for (const {key, value:item1} of example) { console.log(key); console.log(item1); for (const {key, value:item2} of item1) { console.log(key); console.log(item2); } }
sumber
Saya juga merasa terganggu dengan pertanyaan ini.
Kemudian saya mendapatkan ide untuk menggunakan
Object.entries({...})
, ini mengembalikan yangArray
mana adalahIterable
.Juga, Dr. Axel Rauschmayer memposting jawaban yang sangat bagus tentang ini. Lihat Mengapa benda biasa TIDAK dapat diulang
sumber
Secara teknis, ini bukanlah jawaban atas pertanyaan mengapa? tetapi saya telah mengadaptasi jawaban Jack Slocum di atas berdasarkan komentar BT menjadi sesuatu yang dapat digunakan untuk membuat Object dapat berulang.
var iterableProperties={ enumerable: false, value: function * () { for(let key in this) if(this.hasOwnProperty(key)) yield this[key]; } }; var fruit={ 'a': 'apple', 'b': 'banana', 'c': 'cherry' }; Object.defineProperty(fruit,Symbol.iterator,iterableProperties); for(let v of fruit) console.log(v);
Tidak senyaman yang seharusnya, tetapi dapat diterapkan, terutama jika Anda memiliki banyak objek:
var instruments={ 'a': 'accordion', 'b': 'banjo', 'c': 'cor anglais' }; Object.defineProperty(instruments,Symbol.iterator,iterableProperties); for(let v of instruments) console.log(v);
Dan, karena setiap orang berhak mendapatkan opini, saya tidak bisa melihat mengapa Object juga belum dapat diulang. Jika Anda dapat mem-polyfill seperti di atas, atau gunakan
for … in
maka saya tidak dapat melihat argumen sederhana.Salah satu saran yang mungkin adalah bahwa apa yang dapat di-iterable adalah jenis objek, jadi ada kemungkinan bahwa iterable telah dibatasi pada subset objek untuk berjaga-jaga jika beberapa objek lain meledak dalam upaya tersebut.
sumber