Stranezza JavaScript "new Array (n)" e "Array.prototype.map"


209

L'ho osservato in Firefox-3.5.7 / Firebug-1.5.3 e Firefox-3.6.16 / Firebug-1.6.2

Quando accendo 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]

Cosa sta succedendo qui? È un bug o sto fraintendendo come usare new Array(3)?


Non ottengo gli stessi risultati che vedi dalla notazione letterale di array. Ottengo ancora indefinito invece di 0. Ottengo il risultato 0 solo se imposto qualcosa di simile var y = x.map(function(){return 0; });e ottengo questo sia per il nuovo metodo Array () che per l'array letterale. Ho provato su Firefox 4 e Chrome.
RussellUresti,

anche rotto in Chrome, questo potrebbe essere definito nella lingua, anche se non ha senso, quindi spero davvero che non lo sia
Hashbrown

Risposte:


125

Sembra che il primo esempio

x = new Array(3);

Crea un array con puntatori indefiniti.

E il secondo crea un array con puntatori a 3 oggetti non definiti, in questo caso i puntatori stessi NON sono indefiniti, solo gli oggetti a cui puntano.

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

Poiché la mappa viene eseguita nel contesto degli oggetti nell'array, credo che la prima mappa non riesca a eseguire la funzione mentre la seconda riesce a funzionare.


86
Da MDC (Enfasi mia): " mapchiama una funzione di callback fornita una volta per ogni elemento in un array, in ordine, e costruisce un nuovo array dai risultati.callback Viene invocato solo per gli indici dell'array che hanno assegnato valori ; non viene invocato per gli indici che sono stati eliminati o ai quali non sono mai stati assegnati valori. " In questo caso, xi valori non hanno esplicitamente assegnato valori, mentre quelli ysono stati assegnati, anche se era il valore undefined.
Martijn,

2
Quindi è un errore JavaScript che è impossibile verificare se si tratta di un puntatore indefinito o di un puntatore indefinito? intendo(new Array(1))[0] === [undefined][0] .
Trevor Norris,

Bene, una matrice di non definiti è diversa da una matrice di puntatori a oggetti non definiti. Un array di undefine sarebbe come un array di valori null, [null, null, null] mentre un array di puntatori a indefinito sarebbe come [343423, 343424, 343425] che punta a null e null e null. Le seconde soluzioni hanno puntatori reali che puntano a indirizzi di memoria mentre le prime non puntano da nessuna parte. Se questo è un fallimento di JS è probabilmente una questione di discussione, ma non qui;)
David Mårtensson,

4
@TrevNorris, puoi facilmente testarlo con a hasOwnPropertymeno che non hasOwnPropertyabbia un bug: (new Array(1)).hasOwnProperty(0) === falsee [undefined].hasOwnProperty(0) === true. In effetti, puoi fare lo stesso conin : 0 in [undefined] === truee 0 in new Array(0) === false.
squid314

3
Parlare di "puntatori indefiniti" in JavaScript confonde il problema. Il termine che stai cercando è "elezioni" . x = new Array(3);equivale a x = [,,,];no x = [undefined, undefined, undefined].
Matt Kantor,

118

Avevo un compito che conoscevo solo la lunghezza dell'array e che avevo bisogno di trasformare gli oggetti. Volevo fare qualcosa del genere:

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

Per creare rapidamente un array come questo:

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

Ma non ha funzionato perché: (vedi la risposta di Jonathan Lonowski alcune risposte sopra)

La soluzione potrebbe essere quella di riempire gli elementi dell'array con qualsiasi valore (anche con undefined) usando Array.prototype.fill ()

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

Aggiornare

Un'altra soluzione potrebbe essere:

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

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


28
vale la pena notare che non è necessario indicare undefinedil .fill()metodo, semplificando leggermente il codice perlet arr = new Array(10).fill().map((val,idx) => idx);
Yann Eves,

Allo stesso modo è possibile utilizzareArray.from(Array(10))

84

Con ES6, puoi farlo in [...Array(10)].map((a, b) => a)modo semplice e veloce!


9
Pre-ES6 è possibile utilizzare new Array(10).fill(). Stesso risultato di[...Array(10)]
Molomby

Con array di grandi dimensioni la sintassi diffusa crea problemi, quindi è meglio evitare

oppure[...Array(10).keys()]
Chungzuwalla,

25

Soluzione ES6:

[...Array(10)]

Tuttavia, non funziona su dattiloscritto (2.3)


6
Array(10).fill("").map( ...è ciò che ha funzionato per me con Typescript 2.9
ibex,

19

Gli array sono diversi. La differenza è che new Array(3)crea un array con una lunghezza di tre ma nessuna proprietà, mentre [undefined, undefined, undefined]crea un array con una lunghezza di tre e tre proprietà chiamato "0", "1" e "2", ciascuno con un valore di undefined. Puoi vedere la differenza usando l' inoperatore:

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

Ciò deriva dal fatto un po 'confuso che se si tenta di ottenere il valore di una proprietà inesistente di qualsiasi oggetto nativo in JavaScript, viene restituito undefined(anziché generare un errore, come accade quando si tenta di fare riferimento a una variabile inesistente ), che è lo stesso di quello che si ottiene se la proprietà è stata precedentemente esplicitamente impostata su undefined.


17

Dalla pagina MDC per map:

[...] callbackviene invocato solo per gli indici dell'array che hanno assegnato valore; [...]

[undefined]applica effettivamente il setter sugli indici in modo che mapesegua l'iterazione, mentre new Array(1)inizializza semplicemente gli indici con un valore predefinito, undefinedquindi lo mapsalta.

Credo che questo sia lo stesso per tutti i metodi di iterazione .


8

Nella specifica ECMAScript 6a edizione.

new Array(3)definire solo la proprietà lengthe non definire proprietà dell'indice come {length: 3}. vedi https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Passaggio 9.

[undefined, undefined, undefined]definirà proprietà dell'indice e proprietà di lunghezza simili {0: undefined, 1: undefined, 2: undefined, length: 3}. vedi https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Step 5.

metodi map, every, some, forEach, slice, reduce, reduceRight, filterdi Array controllerà la proprietà indice HasPropertymetodo interno così,new Array(3).map(v => 1) non sarà invocare la richiamata.

per maggiori dettagli, vedi https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

Come risolvere?

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);

Spiegazione molto bella.
Dheeraj Rao, il

7

Penso che il modo migliore per spiegare questo sia guardare al modo in cui Chrome lo gestisce.

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

Quindi quello che sta realmente accadendo è che il nuovo Array () sta restituendo un array vuoto che ha una lunghezza di 3, ma nessun valore. Pertanto, quando si esegue x.mapun tecnicamente array vuoto, non è necessario impostare nulla.

Firefox semplicemente 'riempie' quelle slot vuote con undefined anche se non ha valori.

Non penso che questo sia esplicitamente un bug, solo un cattivo modo di rappresentare ciò che sta accadendo. Suppongo che Chrome sia "più corretto" perché dimostra che in realtà non c'è nulla nell'array.


4

Mi sono appena imbattuto in questo. Sarebbe sicuramente comodo poterlo usare Array(n).map.

Array(3) cede all'incirca {length: 3}

[undefined, undefined, undefined]crea le proprietà numerati:
{0: undefined, 1: undefined, 2: undefined, length: 3}.

L'implementazione map () agisce solo su proprietà definite.


3

Non è un bug. Ecco come viene definito il costruttore di array per funzionare.

Da MDC:

Quando si specifica un singolo parametro numerico con il costruttore della matrice, si specifica la lunghezza iniziale della matrice. Il codice seguente crea una matrice di cinque elementi:

var billingMethod = new Array(5);

Il comportamento del costruttore di array dipende dal fatto che il singolo parametro sia un numero.

Il .map()metodo include solo negli elementi di iterazione dell'array a cui sono stati esplicitamente assegnati valori. Anche un'assegnazione esplicita di undefinedun valore sarà considerata ammissibile per l'inclusione nell'iterazione. Sembra strano, ma è essenzialmente la differenza tra una undefinedproprietà esplicita su un oggetto e una proprietà mancante:

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

L'oggetto xnon ha una proprietà denominata "z" e l'oggetto yha. Tuttavia, in entrambi i casi sembra che il "valore" della proprietà sia undefined. In un array, la situazione è simile: il valore di lengthesegue implicitamente un'assegnazione di valore a tutti gli elementi da zero a length - 1. La .map()funzione pertanto non farà nulla (non chiamerà il callback) quando chiamato un array di nuova costruzione con la costruzione Array e un argomento numerico.


È definito per essere rotto? È progettato per produrre una matrice di tre elementi che sarà sempre undefinedper sempre?
Corse di leggerezza in orbita

Sì, è corretto, tranne per la parte "per sempre". Successivamente è possibile assegnare valori agli elementi.
Punta a punta il

3
Ecco perché dovresti usare x = []invece dix = new Array()
Rocket Hazmat

3

Se lo stai facendo per riempire facilmente un array di valori, non puoi usare fill per motivi di supporto del browser e davvero non vuoi fare un for-loop, puoi anche farlo x = new Array(3).join(".").split(".").map(...che ti darà un array di vuoto stringhe.

Devo dire che è piuttosto brutto, ma almeno il problema e l'intenzione sono comunicati in modo abbastanza chiaro.


1

Poiché la domanda è: perché, questo ha a che fare con il modo in cui JS è stato progettato.

Ci sono 2 motivi principali che mi vengono in mente per spiegare questo comportamento:

  • Prestazioni: dato x = 10000ed new Array(x)è saggio per il costruttore evitare di eseguire il loop da 0 a 10000 per riempire l'array di undefinedvalori.

  • Implicitamente "indefinito": Give a = [undefined, undefined]e b = new Array(2), a[1]e b[1]ritorneranno entrambi undefined, ma a[8]e b[8]ritorneranno undefinedanche se sono fuori portata.

In definitiva, la notazione empty x 3è una scorciatoia per evitare di impostare e visualizzare un lungo elenco di undefinedvalori che sono undefinedcomunque perché non dichiarati esplicitamente.

Nota: dato array a = [0]e a[9] = 9, console.log(a)verrà restituito (10) [0, empty x 8, 9], riempiendo automaticamente il gap restituendo la differenza tra i due valori dichiarati esplicitamente.


1

Per ragioni ben spiegate in altre risposte, Array(n).mapnon funziona. Tuttavia, in ES2015 Array.fromaccetta una funzione mappa:

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


0

Ecco un semplice metodo di utilità come soluzione alternativa:

Mappa semplice Per

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]

Esempio completo

Ecco un esempio più completo (con controlli di integrità) che consente anche di specificare un indice iniziale facoltativo:

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]

Contando alla rovescia

La manipolazione dell'indice passato al callback consente il conteggio all'indietro:

var count = 3;
var arr = arrayUtil.mapFor(count, function (i) {
    return count - 1 - i;
});
// arr = [2, 1, 0]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.