Keanehan JavaScript “Array baru (n)” dan “Array.prototype.map”

209

Saya telah mengamati ini di Firefox-3.5.7 / Firebug-1.5.3 dan Firefox-3.6.16 / Firebug-1.6.2

Ketika saya menyalakan Firebug:

var x = new Array(3)
console.log(x) 
// [undefined, undefined, undefined]

var y = [undefined, undefined, undefined]
console.log(y) 
// [undefined, undefined, undefined]

console.log( x.constructor == y.constructor) // true

console.log( 
  x.map(function() { return 0; })
)
// [undefined, undefined, undefined]

console.log(
  y.map(function() { return 0; })
)
// [0, 0, 0]

Apa yang terjadi di sini? Apakah ini bug, atau saya salah paham cara menggunakan new Array(3)?

rampion
sumber
Saya tidak mendapatkan hasil yang sama dengan yang Anda lihat dari notasi literal array. Saya masih mendapatkan undefined bukan 0. Saya hanya mendapatkan hasil 0 jika saya mengatur sesuatu seperti var y = x.map(function(){return 0; });, dan saya mendapatkan ini untuk kedua metode Array () baru dan array literal. Saya menguji di Firefox 4 dan Chrome.
RussellUresti
juga rusak di Chrome, ini mungkin didefinisikan dalam bahasa, meskipun tidak masuk akal jadi saya sangat berharap itu tidak
Hashbrown

Jawaban:

125

Tampaknya contoh pertama

x = new Array(3);

Membuat array dengan pointer yang tidak ditentukan.

Dan yang kedua membuat array dengan pointer ke 3 objek yang tidak terdefinisi, dalam hal ini pointer mereka sendiri tidak terdefinisi, hanya objek yang mereka tuju.

y = [undefined, undefined, undefined]
// The following is not equivalent to the above, it's the same as new Array(3)
y = [,,,];

Karena peta dijalankan dalam konteks objek dalam array, saya yakin peta pertama gagal menjalankan fungsinya sama sekali sedangkan yang kedua berhasil dijalankan.

David Mårtensson
sumber
86
Dari MDC (penekanan tambang): " mapmemanggil fungsi callback yang disediakan satu kali untuk setiap elemen dalam array, secara berurutan, dan membangun array baru dari hasilnya. Hanya callbackdipanggil untuk indeks array yang telah menetapkan nilai ; tidak dipanggil untuk indeks yang telah dihapus atau yang tidak pernah diberi nilai. " Dalam hal ini, xnilai-nilai belum secara eksplisit menetapkan nilai, sedangkan nilai ydiberikan, meskipun itu adalah nilainya undefined.
Martijn
2
Jadi apakah itu JavaScript yang gagal sehingga tidak mungkin untuk memeriksa apakah itu sebuah pointer yang tidak terdefinisi, atau sebuah pointer yang tidak terdefinisi? Maksudku (new Array(1))[0] === [undefined][0].
Trevor Norris
Nah, array yang tidak terdefinisi berbeda dari array pointer ke objek yang tidak terdefinisi. Array undefine akan menjadi seperti array nilai null, [null, null, null] sedangkan array pointer ke undefined akan seperti [343423, 343424, 343425] menunjuk ke null dan null dan null. Solusi kedua memiliki pointer nyata yang menunjuk ke alamat memori sedangkan yang pertama tidak menunjuk ke mana pun. Jika itu adalah kegagalan JS mungkin merupakan masalah diskusi, tetapi tidak di sini;)
David Mårtensson
4
@ TrvNorris, Anda dapat dengan mudah mengujinya dengan hasOwnPropertykecuali jika hasOwnPropertydirinya sendiri memiliki bug: (new Array(1)).hasOwnProperty(0) === falsedan [undefined].hasOwnProperty(0) === true. Bahkan, Anda dapat melakukan hal yang persis sama dengan in: 0 in [undefined] === truedan 0 in new Array(0) === false.
squid314
3
Berbicara tentang "pointer tidak terdefinisi" dalam JavaScript membingungkan masalah ini. Istilah yang Anda cari adalah "elisions" . x = new Array(3);sama dengan x = [,,,];, tidak x = [undefined, undefined, undefined].
Matt Kantor
118

Saya punya tugas yang saya hanya tahu panjang array dan perlu mengubah item. Saya ingin melakukan sesuatu seperti ini:

let arr = new Array(10).map((val,idx) => idx);

Untuk dengan cepat membuat array seperti ini:

[0,1,2,3,4,5,6,7,8,9]

Tapi itu tidak berhasil karena: (lihat jawaban Jonathan Lonowski beberapa jawaban di atas)

Solusinya bisa dengan mengisi item array dengan nilai apa pun (bahkan dengan tidak terdefinisi) menggunakan Array.prototype.fill ()

let arr = new Array(10).fill(undefined).map((val,idx) => idx);

Memperbarui

Solusi lain dapat:

let arr = Array.apply(null, Array(10)).map((val, idx) => idx);

console.log(Array.apply(null, Array(10)).map((val, idx) => idx));

cstuncsik
sumber
28
Perlu dicatat Anda tidak perlu menyatakan undefineddalam .fill()metode ini, menyederhanakan kode sangat sedikit untuklet arr = new Array(10).fill().map((val,idx) => idx);
Yann Eves
Demikian pula Anda dapat menggunakanArray.from(Array(10))
84

Dengan ES6, Anda dapat melakukannya [...Array(10)].map((a, b) => a), cepat dan mudah!

Manuel Beaudru
sumber
9
Pre-ES6 dapat Anda gunakan new Array(10).fill(). Hasil yang sama seperti[...Array(10)]
Molomby
Dengan array besar, sintaksis spread menciptakan masalah sehingga lebih baik untuk dihindari
atau[...Array(10).keys()]
Chungzuwalla
25

Solusi ES6:

[...Array(10)]

Tidak bekerja pada naskah (2.3), meskipun

Serge Intern
sumber
6
Array(10).fill("").map( ...adalah apa yang bekerja untuk saya dengan Script 2.9
ibex
19

Array berbeda. Perbedaannya adalah bahwa new Array(3)menciptakan sebuah array dengan panjang tiga tetapi tanpa properti, sementara [undefined, undefined, undefined]menciptakan array dengan panjang tiga dan tiga properti yang disebut "0", "1" dan "2", masing-masing dengan nilai undefined. Anda dapat melihat perbedaan menggunakan inoperator:

"0" in new Array(3); // false
"0" in [undefined, undefined, undefined]; // true

Ini berasal dari fakta yang sedikit membingungkan bahwa jika Anda mencoba untuk mendapatkan nilai properti yang tidak ada dari objek asli apa pun di JavaScript, ia mengembalikan undefined(daripada melempar kesalahan, seperti yang terjadi ketika Anda mencoba merujuk ke variabel yang tidak ada) ), yang sama dengan apa yang Anda dapatkan jika properti sebelumnya secara eksplisit diatur ke undefined.

Tim Down
sumber
17

Dari halaman MDC untuk map:

[...] callbackdipanggil hanya untuk indeks array yang memiliki nilai yang ditetapkan; [...]

[undefined]sebenarnya menerapkan setter pada indeks (es) sehingga mapakan mengulangi, sedangkan new Array(1)hanya menginisialisasi indeks (es) dengan nilai default undefinedsehingga mapmelewatkannya.

Saya percaya ini sama untuk semua metode iterasi .

Jonathan Lonowski
sumber
8

Dalam spesifikasi edisi 6 ECMAScript.

new Array(3)hanya mendefinisikan properti lengthdan tidak mendefinisikan seperti properti indeks {length: 3}. lihat https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Langkah 9.

[undefined, undefined, undefined]akan mendefinisikan properti indeks dan panjang properti seperti {0: undefined, 1: undefined, 2: undefined, length: 3}. lihat https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Langkah 5.

metode map, every, some, forEach, slice, reduce, reduceRight, filterArray akan memeriksa properti index oleh HasPropertymetode internal, sehingga new Array(3).map(v => 1)tidak akan meminta panggilan balik itu.

untuk detail lebih lanjut, lihat https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

Bagaimana cara memperbaiki?

let a = new Array(3);
a.join('.').split('.').map(v => 1);

let a = new Array(3);
a.fill(1);

let a = new Array(3);
a.fill(undefined).map(v => 1);

let a = new Array(3);
[...a].map(v => 1);
Wenshin
sumber
Penjelasan yang sangat bagus.
Dheeraj Rao
7

Saya pikir cara terbaik untuk menjelaskan ini adalah dengan melihat cara Chrome menanganinya.

>>> x = new Array(3)
[]
>>> x.length
3

Jadi apa yang sebenarnya terjadi adalah bahwa Array baru () mengembalikan array kosong yang memiliki panjang 3, tetapi tidak ada nilai. Oleh karena itu, ketika Anda menjalankan x.mappada teknis array kosong, tidak ada yang harus ditetapkan.

Firefox hanya 'mengisi' slot-slot kosong undefineditu walaupun tidak memiliki nilai.

Saya tidak berpikir ini adalah bug, hanya cara yang buruk untuk mewakili apa yang sedang terjadi. Saya kira Chrome "lebih benar" karena ini menunjukkan bahwa sebenarnya tidak ada apa pun dalam array.

helloandre
sumber
4

Baru saja berlari ke ini. Tentu akan nyaman untuk dapat menggunakan Array(n).map.

Array(3) hasil kira-kira {length: 3}

[undefined, undefined, undefined]menciptakan sifat bernomor:
{0: undefined, 1: undefined, 2: undefined, length: 3}.

Implementasi map () hanya bekerja pada properti yang didefinisikan.

Vezquex
sumber
3

Bukan bug. Begitulah konstruktor Array didefinisikan untuk bekerja.

Dari MDC:

Ketika Anda menentukan parameter numerik tunggal dengan konstruktor Array, Anda menentukan panjang awal array. Kode berikut membuat array dari lima elemen:

var billingMethod = new Array(5);

Perilaku konstruktor Array tergantung pada apakah parameter tunggal adalah angka.

The .map()Metode hanya mencakup pada iterasi elemen dari array yang telah secara eksplisit memiliki nilai-nilai yang ditetapkan. Bahkan penugasan eksplisit undefinedakan menyebabkan nilai dianggap memenuhi syarat untuk dimasukkan dalam iterasi. Tampaknya aneh, tetapi pada dasarnya perbedaan antara undefinedproperti eksplisit pada objek dan properti yang hilang:

var x = { }, y = { z: undefined };
if (x.z === y.z) // true

Objek xtidak memiliki properti yang disebut "z", dan objek ytidak. Namun, dalam kedua kasus itu tampak bahwa "nilai" properti itu undefined. Dalam sebuah array, situasinya mirip: nilai lengthapakah secara implisit melakukan penugasan nilai ke semua elemen dari nol hingga length - 1. The .map()Fungsi karena itu tidak akan melakukan apa-apa (tidak akan memanggil callback) saat dipanggil pada sebuah array baru dibangun dengan konstruktor Array dan argumen numerik.

Runcing
sumber
Itu didefinisikan rusak? Ini dirancang untuk menghasilkan berbagai elemen yang akan selalu ada undefineduntuk selamanya?
Lightness Races dalam Orbit
Ya, itu benar, kecuali untuk bagian "selamanya". Anda selanjutnya dapat menetapkan nilai ke elemen.
Runcing
3
Itu sebabnya Anda harus menggunakan x = []sebagai gantinyax = new Array()
Rocket Hazmat
3

Jika Anda melakukan ini untuk mengisi array dengan nilai dengan mudah, tidak dapat menggunakan fill untuk alasan dukungan browser dan benar-benar tidak ingin melakukan for-loop, Anda juga dapat melakukan x = new Array(3).join(".").split(".").map(...yang akan memberi Anda array kosong string.

Cukup jelek yang harus saya katakan, tetapi setidaknya masalah dan niat dikomunikasikan dengan jelas.

Alex
sumber
1

Karena pertanyaannya adalah mengapa, ini berkaitan dengan bagaimana JS dirancang.

Ada 2 alasan utama yang bisa saya pikirkan untuk menjelaskan perilaku ini:

  • Kinerja: Diberikan x = 10000dan new Array(x)bijaksana bagi konstruktor untuk menghindari perulangan dari 0 hingga 10.000 untuk mengisi array dengan undefinednilai.

  • Secara implisit "tidak terdefinisi": Memberi a = [undefined, undefined]dan b = new Array(2), a[1]dan b[1]keduanya akan kembali undefined, tetapi a[8]dan b[8]juga akan kembali undefinedmeskipun mereka berada di luar jangkauan.

Pada akhirnya, notasi empty x 3adalah jalan pintas untuk menghindari pengaturan dan menampilkan daftar panjang undefinednilai yang undefinedtetap karena mereka tidak dinyatakan secara eksplisit.

Catatan: Array yang diberikan a = [0]dan a[9] = 9, console.log(a)akan kembali (10) [0, empty x 8, 9], mengisi celah secara otomatis dengan mengembalikan perbedaan antara dua nilai yang dinyatakan secara eksplisit.

BPS Julien
sumber
1

Untuk alasan yang dijelaskan dalam jawaban lain, Array(n).map tidak berfungsi. Namun, dalam ES2015 Array.frommenerima fungsi peta:

let array1 = Array.from(Array(5), (_, i) => i + 1)
console.log('array1', JSON.stringify(array1)) // 1,2,3,4,5

let array2 = Array.from({length: 5}, (_, i) => (i + 1) * 2)
console.log('array2', JSON.stringify(array2)) // 2,4,6,8,10

Eden Landau
sumber
0

Inilah metode utilitas sederhana sebagai solusinya:

MapFor sederhana

function mapFor(toExclusive, callback) {
    callback = callback || function(){};
    var arr = [];
    for (var i = 0; i < toExclusive; i++) {
        arr.push(callback(i));
    }
    return arr;
};

var arr = mapFor(3, function(i){ return i; });
console.log(arr); // [0, 1, 2]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

Contoh Lengkap

Berikut adalah contoh yang lebih lengkap (dengan pemeriksaan kewarasan) yang juga memungkinkan menentukan indeks awal opsional:

function mapFor() {
var from, toExclusive, callback;
if (arguments.length == 3) {
    from = arguments[0];
    toExclusive = arguments[1];
    callback = arguments[2];
} else if (arguments.length == 2) {
    if (typeof arguments[1] === 'function') {
        from = 0;
        toExclusive = arguments[0];
        callback = arguments[1];
    } else {
        from = arguments[0];
        toExclusive = arguments[1];
    }
} else if (arguments.length == 1) {
    from = 0;
    toExclusive = arguments[0];
}

callback = callback || function () {};

var arr = [];
for (; from < toExclusive; from++) {
    arr.push(callback(from));
}
return arr;
}

var arr = mapFor(1, 3, function (i) { return i; });
console.log(arr); // [1, 2]
arr = mapFor(1, 3);
console.log(arr); // [undefined, undefined]
arr = mapFor(3);
console.log(arr); // [undefined, undefined, undefined]

Menghitung mundur

Memanipulasi indeks yang diteruskan ke panggilan balik memungkinkan penghitungan mundur:

var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
    return count - 1 - i;
});
// arr = [2, 1, 0]
DJDaveMark
sumber