Didefinisikan atau Tidak Ditentukan? Nuansa membuat array dalam JavaScript

gambar

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); // Array(5) [ <5 empty slots> ] console.log(arr[0]); // undefined console.log(Object.getOwnPropertyDescriptor(arr,"0")); // undefined 

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); // Array(5) [ <5 empty slots> ] console.log(arr[0]); // undefined console.log(Object.getOwnPropertyDescriptor(arr,"0")); // undefined console.log(arr[0][0]); // TypeError: arr[0] is undefined 

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); // Array(5) [ undefined, undefined, undefined, undefined, undefined ] console.log(arr[0]); // undefined console.log(Object.getOwnPropertyDescriptor(arr,"0")); // Object { value: undefined, writable: true, enumerable: true, configurable: true } 

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:

  1. Kami memanggil metode Function.prototype.apply () dan meneruskan konteks nol ke sana, dan sebagai array Array baru (5) .
  2. Array baru (5) membuat array tanpa sel, tetapi dengan panjang 5 .
  3. 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 .
  4. 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); // Array(5) [ (5) […], (5) […], (5) […], (5) […], (5) […] ] console.log(arr[0]); // Array(5) [ undefined, undefined, undefined, undefined, undefined ] console.log(Object.getOwnPropertyDescriptor(arr,"0")); // Object { value: (5) […], writable: true, enumerable: true, configurable: true } console.log(arr[0][0]); // undefined console.log(Object.getOwnPropertyDescriptor(arr[0],"0")); // Object { value: undefined, writable: true, enumerable: true, configurable: true } 

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?

Jawabannya
console.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

Source: https://habr.com/ru/post/id463041/


All Articles