Beberapa bulan yang lalu saya menemukan sebuah pertanyaan menarik tentang
stackoverflow , di mana, singkatnya, seseorang ingin membuat matriks
5x5 kosong, dan menggunakan satu metode yang berhasil, tetapi menggunakan yang lain tidak. Dalam diskusi berikutnya, pemikiran menarik dikutip tentang hal ini.
Benar, orang yang mengajukan pertanyaan, serta orang-orang yang menjawabnya, tidak memperhatikan fakta bahwa sebenarnya matriks tidak dapat dibuat, dan hasil perhitungannya tidak benar. Semua ini membuat saya tertarik, dan saya memutuskan untuk menggali sedikit lebih dalam, lalu mengambil kesimpulan menarik, yang akan saya bagikan kepada Anda sekarang.
Catatan: Saya juga menjawab di bawah diskusi itu, dengan julukan AndreyGS - di sana saya menjawab agak singkat, di sini saya akan mencoba untuk sepenuhnya menutupi masalahnya.Secara umum, tantangannya adalah membuat array. Bagaimana kita akan melakukan ini? Anehnya, ada opsi yang berbeda, tergantung pada apa yang ingin kita dapatkan.
Kita tahu bahwa fungsi dalam JavaScript memiliki dua metode internal,
Panggil dan
Bangun . Jika kita menggunakan kata kunci
baru , metode Bangun digunakan, yang membuat instance objek baru, memberikan referensi
ini untuk
itu , dan kemudian mengeksekusi tubuh fungsi. Tidak semua fungsi memiliki metode ini, tetapi bagi kami ini tidak begitu penting saat ini.
Saat membuat array, ada satu kekhasan: tidak masalah jika kita menggunakan
Array (...) atau
Array baru (...) - spesifikasi
ECMAScript tidak membuat perbedaan untuk mereka dan, apalagi, menganggapnya setara.
22.1.1 The Array Constructor The Array constructor is the %Array% intrinsic object and the initial value of the Array property of the global object. When called as a constructor it creates and initializes a new exotic Array object. When Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(β¦) is equivalent to the object creation expression new Array(β¦) with the same arguments.
Oleh karena itu, saya tidak akan berfilosofi secara nakal, dan dalam contoh-contoh ini saya hanya akan menggunakan konstruksi
Array (...) baru , agar tidak membingungkan siapa pun.
Mari kita mulai.
Buat sebuah array:
let arr = new Array(5);
Apa yang kita dapatkan?
console.log(arr);
Hmm ... yah, pada prinsipnya, seharusnya begitu - kita mengatur panjang dan mendapat lima sel kosong, dengan nilai
tidak terdefinisi , yang dapat dikerjakan lebih lanjut, bukan? Benar, ada beberapa poin yang membingungkan saya. Mari kita periksa.
let arr = new Array(5).map(function() { return new Array(5); }); console.log(arr);
Bagaimana mungkin, setelah semua, kami harus mendapatkan matriks, dan di setiap sel, karenanya, harus ada array dari 5 elemen ...
Mari kita kembali ke dokumentasi
ECMAScript dan melihat apa yang tertulis di dalamnya mengenai metode membuat array dengan satu argumen:
22.1.1.2 Array (len) This description applies if and only if the Array constructor is called with exactly one argument. 1. Let numberOfArgs be the number of arguments passed to this function call. 2. Assert: numberOfArgs = 1. 3. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. 4. Let proto be GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%"). 5. ReturnIfAbrupt(proto). 6. Let array be ArrayCreate(0, proto). 7. If Type(len) is not Number, then 1. Let defineStatus be CreateDataProperty(array, "0", len). 2. Assert: defineStatus is true. 3. Let intLen be 1. 8. Else, 1. Let intLen be ToUint32(len). 2. If intLen β len, throw a RangeError exception. 9. Let setStatus be Set(array, "length", intLen, true). 10. Assert: setStatus is not an abrupt completion. 11. Return array.
Dan, apa yang kita lihat, ternyata objek tersebut dibuat, properti
panjang dibuat dalam prosedur ArrayCreate (titik 6), nilai dalam properti
panjang diatur (titik 9), dan bagaimana dengan sel? Terlepas dari kasus khusus ketika argumen yang dilewati bukan angka, dan array dibuat dengan sel tunggal "0" dengan nilai yang sesuai (poin 7), tidak ada sepatah kata pun tentang mereka ... Artinya, panjang == 5 adalah, tetapi tidak ada lima sel. Ya, kompiler membingungkan kita ketika kita mencoba mengakses sel tunggal, ia memberi tahu bahwa nilainya tidak
terdefinisi , padahal sebenarnya tidak.
Di sini, untuk perbandingan, metode membuat array dengan beberapa argumen yang dikirim ke konstruktor:
22.1.1.3 Array (...items ) This description applies if and only if the Array constructor is called with at least two arguments. When the Array function is called the following steps are taken: 1. Let numberOfArgs be the number of arguments passed to this function call. 2. Assert: numberOfArgs β₯ 2. 3. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. 4. Let proto be GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%"). 5. ReturnIfAbrupt(proto). 6. Let array be ArrayCreate(numberOfArgs, proto). 7. ReturnIfAbrupt(array). 8. Let k be 0. 9. Let items be a zero-origined List containing the argument items in order. 10. Repeat, while k < numberOfArgs 1. Let Pk be ToString(k). 2. Let itemK be items[k]. 3. Let defineStatus be CreateDataProperty(array, Pk, itemK). 4. Assert: defineStatus is true. 5. Increase k by 1. 11. Assert: the value of array's length property is numberOfArgs. 12. Return array.
Di sini, tolong - 10 poin, pembuatan sel-sel yang sama.
Dan sekarang, apa yang dilakukan
Array.prototype.map () selanjutnya?
22.1.3.15 Array.prototype.map ( callbackfn [ , thisArg ] ) 1. Let O be ToObject(this value). 2. ReturnIfAbrupt(O). 3. Let len be ToLength(Get(O, "length")). 4. ReturnIfAbrupt(len). 5. If IsCallable(callbackfn) is false, throw a TypeError exception. 6. If thisArg was supplied, let T be thisArg; else let T be undefined. 7. Let A be ArraySpeciesCreate(O, len). 8. ReturnIfAbrupt(A). 9. Let k be 0. 10. Repeat, while k < len 1. Let Pk be ToString(k). 2. Let kPresent be HasProperty(O, Pk). 3. ReturnIfAbrupt(kPresent). 4. If kPresent is true, then 1. Let kValue be Get(O, Pk). 2. ReturnIfAbrupt(kValue). 3. Let mappedValue be Call(callbackfn, T, Β«kValue, k, OΒ»). 4. ReturnIfAbrupt(mappedValue). 5. Let status be CreateDataPropertyOrThrow (A, Pk, mappedValue). 6. ReturnIfAbrupt(status). 5. Increase k by 1. 11. Return A.
Klausa 7 - salinan larik asli dibuat, dalam klausa 10 jika iterasi dilakukan pada elemen-elemennya, dan, khususnya, klausa 10.2 memeriksa untuk melihat apakah ada sel tertentu dalam larik sumber, sehingga, jika berhasil, peta (10.4) dan buat sel yang sesuai dalam salinan - 10.4.5. Karena 10.2 memberikan
false untuk masing-masing dari
5 lintasan, tidak satu sel pun dalam salinan array yang dikembalikan akan dibuat juga.
Jadi, bagaimana cara kerja konstruktor array dan metode
Array.prototype.map (), kami mengetahuinya, tetapi tugas tetap seperti sebelumnya tidak terpecahkan, karena matriks tidak dibangun.
Function.prototype.apply () akan datang untuk menyelamatkan!
Mari segera kita periksa beraksi:
let arr = Array.apply(null, new Array(5)); console.log(arr);
Hore, kelima sel tersebut diamati dengan jelas di sini, dan juga sel pertama, tes, dengan angka
"0" memiliki deskriptor.
Dalam hal ini, program ini bekerja sebagai berikut:
- Kami memanggil metode Function.prototype.apply () dan meneruskan konteks nol ke sana, dan sebagai array Array baru (5) .
- Array baru (5) membuat array tanpa sel, tetapi dengan panjang 5 .
- Function.prototype.apply () menggunakan metode internal untuk memecah array menjadi argumen yang terpisah, sebagai akibatnya, meneruskan lima argumen dengan nilai yang tidak ditentukan ke konstruktor Array .
- Array menerima 5 argumen dengan nilai yang tidak ditentukan , menambahkannya ke sel yang sesuai.
Semuanya tampak jelas, kecuali apa metode internal
Function.prototype.apply () ini , yang membuat
5 argumen keluar dari ketiadaan - saya sarankan lagi untuk melihat dokumentasi
ECMAScript :
19.2.3.1 Function.prototype.apply 1. If IsCallable(func) is false, throw a TypeError exception. 2. If argArray is null or undefined, then Return Call(func, thisArg). 3. Let argList be CreateListFromArrayLike(argArray). 7.3.17 CreateListFromArrayLike (obj [, elementTypes] ) 1. ReturnIfAbrupt(obj). 2. If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object). 3. If Type(obj) is not Object, throw a TypeError exception. 4. Let len be ToLength(Get(obj, "length")). 5. ReturnIfAbrupt(len). 6. Let list be an empty List. 7. Let index be 0. 8. Repeat while index < len a. Let indexName be ToString(index). b. Let next be Get(obj, indexName). c. ReturnIfAbrupt(next). d. If Type(next) is not an element of elementTypes, throw a TypeError exception. e. Append next as the last element of list. f. Set index to index + 1. 9. Return list.
Kami melihat poin yang paling menarik:
19.2.3.1 - paragraf 3: membuat daftar argumen dari objek yang mirip dengan array (seperti yang kita ingat, objek tersebut harus memiliki properti panjang).
7.3.17 - metode pembuatan daftar itu sendiri. Ia memeriksa apakah objeknya adalah atau tidak, dan, jika demikian, permintaan untuk bidang
panjang (paragraf 4). Kemudian indeks sama dengan
"0" dibuat (paragraf 7). Sebuah loop dibuat dengan kenaikan indeks ke nilai yang diambil dari bidang
panjang (paragraf 8). Dalam siklus ini, kami merujuk pada nilai sel array yang ditransmisikan dengan indeks yang sesuai (klausa 8a dan 8b). Dan seperti yang kita ingat, ketika mengakses nilai sel tunggal dalam array di mana sebenarnya tidak ada sel, itu masih memberikan nilai -
tidak terdefinisi . Nilai yang dihasilkan ditambahkan ke akhir daftar argumen (paragraf 8e).
Nah, sekarang semuanya telah jatuh ke tempatnya, Anda dapat dengan aman membangun matriks yang sangat kosong.
let arr = Array.apply(null, new Array(5)).map(function(){ return Array.apply(null,new Array(5)); }); console.log(arr);
Sekarang, seperti yang Anda lihat, semuanya menyatu dan terlihat cukup sederhana: kami, dengan cara yang sudah kami ketahui, membuat
array kosong yang sederhana.
Terapkan array (null, new Array (5)) dan kemudian meneruskannya ke metode peta, yang menciptakan array yang sama di masing-masing sel.
Selain itu, Anda dapat membuatnya lebih mudah. Operator
spread -
... muncul di
ECMAScript6 , dan, yang tipikal, ia juga secara khusus bekerja dengan array. Karena itu, kita cukup mengemudi di:
let arr = new Array(...new Array(5)).map(() => new Array(...new Array(5)));
atau kami akan menyederhanakannya sepenuhnya, meskipun sebelumnya saya berjanji baru untuk tidak menyentuh ...
let arr = Array(...Array(5)).map(() => Array(...Array(5)));
Catatan: di sini kami juga menggunakan fungsi panah, karena kami masih berurusan dengan operator spread yang muncul dalam spesifikasi yang sama dengan mereka.Kami tidak akan masuk ke prinsip operator
penyebaran , namun, untuk pengembangan umum, saya percaya contoh ini juga berguna.
Selain itu, kami, tentu saja, dapat membangun fungsi kami, yang, menggunakan sorting
Function.protot.apply () , akan membuat array normal bagi kami dengan sel kosong, namun, memahami prinsip-prinsip internal JavaScript dan, karenanya, penggunaan yang benar dan memadai fungsi bawaan, adalah dasar untuk menguasai yang merupakan prioritas. Ya, dan tentu saja, ini sangat sederhana, lebih cepat, dan lebih nyaman.
Dan akhirnya, kembali ke pertanyaan yang sama tentang
stackoverflow - di sana, saya ingat, orang itu secara keliru menganggap bahwa metode yang diterimanya mengarah ke jawaban yang benar, dan bahwa ia menerima matriks
5x5 , namun - kesalahan kecil merayap di sana.
Dia melaju masuk:
Array.apply(null, new Array(5)).map(function(){
return new Array(5);
});
Menurut Anda apa hasil sebenarnya di sini?
Jawabannyaconsole.log (arr); // Array (5) [(5) [...], (5) [...], (5) [...], (5) [...], (5) [...]]
console.log (arr [0]); // Array (5) [<5 slot kosong>]
console.log (Object.getOwnPropertyDescriptor (arr, "0")); // Objek {nilai: (5) [...], dapat ditulis: true, enumerable: true, dapat dikonfigurasi: true}
console.log (arr [0] [0]); // tidak terdefinisi
console.log (Object.getOwnPropertyDescriptor (arr [0], "0")); // tidak terdefinisi
bukan, bukan itu yang dia inginkan ...
Referensi:
β
Spesifikasi Bahasa ECMAScript 2015β
Apa yang sebenarnya dilakukan Array