JavaScript "new Array (n)" und "Array.prototype.map" Verrücktheit

209

Ich habe dies in Firefox-3.5.7 / Firebug-1.5.3 und Firefox-3.6.16 / Firebug-1.6.2 beobachtet

Wenn ich Firebug starte:

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]

Was ist denn hier los? Ist das ein Fehler oder verstehe ich die Verwendung falsch new Array(3)?

Rampion
quelle
Ich erhalte nicht die gleichen Ergebnisse, die Sie aus der Array-Literal-Notation sehen. Ich werde immer noch undefiniert anstelle von 0. Ich erhalte das Ergebnis 0 nur, wenn ich so etwas wie einstelle var y = x.map(function(){return 0; });, und ich erhalte dies sowohl für die neue Array () -Methode als auch für das Array-Literal. Ich habe in Firefox 4 und Chrome getestet.
RussellUresti
Auch in Chrome kaputt, könnte dies in der Sprache definiert sein, obwohl es keinen Sinn macht, also hoffe ich wirklich, dass es nicht so ist
Hashbrown

Antworten:

125

Es scheint, dass das erste Beispiel

x = new Array(3);

Erstellt ein Array mit undefinierten Zeigern.

Und die zweite erstellt ein Array mit Zeigern auf 3 undefinierte Objekte. In diesem Fall sind die Zeiger selbst NICHT undefiniert, sondern nur die Objekte, auf die sie zeigen.

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

Da die Map im Kontext der Objekte im Array ausgeführt wird, kann die erste Map meiner Meinung nach die Funktion überhaupt nicht ausführen, während die zweite ausgeführt werden kann.

David Mårtensson
quelle
86
Von MDC (Schwerpunkt Mine): " mapRuft eine bereitgestellte Rückruffunktion einmal für jedes Element in einem Array in der angegebenen Reihenfolge auf und erstellt aus den Ergebnissen ein neues Array. callbackWird nur für Indizes des Arrays aufgerufen, denen Werte zugewiesen wurden . Es wird nicht aufgerufen." für Indizes, die gelöscht wurden oder denen nie Werte zugewiesen wurden. " In diesem Fall wurden xden Werten nicht explizit Werte zugewiesen, wohingegen die Werte yzugewiesen wurden, selbst wenn es sich um den Wert handelte undefined.
Martijn
2
Ist es also ein JavaScript-Fehler, bei dem nicht überprüft werden kann, ob es sich um einen undefinierten Zeiger oder einen Zeiger auf einen undefinierten Zeiger handelt? Ich meine (new Array(1))[0] === [undefined][0].
Trevor Norris
Nun, ein Array von undefinierten Objekten unterscheidet sich von einem Array von Zeigern auf undefinierte Objekte. Ein Array von Undefinierten wäre wie ein Array von Nullwerten [null, null, null], während ein Array von Zeigern auf undefiniert wie [343423, 343424, 343425] auf Null und null und null zeigt. Die zweiten Lösungen haben echte Zeiger, die auf Speicheradressen zeigen, während die ersten nirgendwo zeigen. Wenn das ein Versagen von JS ist, ist es wahrscheinlich eine Frage der Diskussion, aber nicht hier;)
David Mårtensson
4
@TrevNorris, Sie können das leicht testen, es hasOwnPropertysei denn , es hasOwnPropertyselbst hat einen Fehler: (new Array(1)).hasOwnProperty(0) === falseund [undefined].hasOwnProperty(0) === true. In der Tat können Sie genau das Gleiche tun mit in: 0 in [undefined] === trueund 0 in new Array(0) === false.
squid314
3
Das Sprechen über "undefinierte Zeiger" in JavaScript verwirrt das Problem. Der Begriff, den Sie suchen, ist "Elisionen" . x = new Array(3);ist gleichbedeutend mit x = [,,,];nicht x = [undefined, undefined, undefined].
Matt Kantor
118

Ich hatte eine Aufgabe, bei der ich nur die Länge des Arrays kannte und die Elemente transformieren musste. Ich wollte so etwas machen:

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

So erstellen Sie schnell ein Array wie folgt:

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

Aber es hat nicht funktioniert, weil: (siehe Jonathan Lonowskis Antwort einige Antworten oben)

Die Lösung könnte darin bestehen, die Array-Elemente mit Array.prototype.fill () mit einem beliebigen Wert (auch mit undefinierten) zu füllen.

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

Aktualisieren

Eine andere Lösung könnte sein:

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

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

cstuncsik
quelle
28
Erwähnenswert ist, dass Sie undefinedin der .fill()Methode nicht angeben müssen , wodurch der Code geringfügig vereinfacht wirdlet arr = new Array(10).fill().map((val,idx) => idx);
Yann Eves
Ebenso können Sie verwendenArray.from(Array(10))
84

Mit ES6 können Sie dies [...Array(10)].map((a, b) => a)schnell und einfach tun !

Manuel Beaudru
quelle
9
Pre-ES6 können Sie verwenden new Array(10).fill(). Gleiches Ergebnis wie[...Array(10)]
Molomby
Bei großen Arrays verursacht die Spread-Syntax Probleme, daher ist es besser, sie zu vermeiden
oder[...Array(10).keys()]
Chungzuwalla
25

ES6-Lösung:

[...Array(10)]

Funktioniert jedoch nicht mit Typoskript (2.3)

Serge Intern
quelle
6
Array(10).fill("").map( ...hat bei Typescript 2.9
ibex
18

Die Arrays sind unterschiedlich. Der Unterschied besteht darin, dass new Array(3)ein Array mit einer Länge von drei, aber keinen Eigenschaften erstellt wird, während [undefined, undefined, undefined]ein Array mit einer Länge von drei und drei Eigenschaften mit den Namen "0", "1" und "2" mit dem Wert "0" erstellt wird undefined. Sie können den Unterschied mit dem inOperator sehen:

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

Dies ist auf die etwas verwirrende Tatsache zurückzuführen, dass beim Versuch, den Wert einer nicht vorhandenen Eigenschaft eines nativen Objekts in JavaScript abzurufen, dieser zurückgegeben wird undefined(anstatt einen Fehler auszulösen, wie dies der Fall ist, wenn Sie versuchen, auf eine nicht vorhandene Variable zu verweisen ), was dem entspricht, was Sie erhalten, wenn die Eigenschaft zuvor explizit festgelegt wurde undefined.

Tim Down
quelle
16

Von der MDC-Seite für map:

[...] callbackwird nur für Indizes des Arrays aufgerufen, denen ein Wert zugewiesen wurde. [...]

[undefined]Wendet den Setter tatsächlich auf die Indizes an, sodass diese mapiteriert werden, während new Array(1)die Indizes nur mit dem Standardwert initialisiert werden, undefinedsodass sie mapübersprungen werden.

Ich glaube, dass dies für alle Iterationsmethoden gleich ist .

Jonathan Lonowski
quelle
8

In der Spezifikation der 6. Ausgabe von ECMAScript.

new Array(3)Definieren Sie nur Eigenschaften lengthund definieren Sie keine Indexeigenschaften wie {length: 3}. Siehe https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Schritt 9.

[undefined, undefined, undefined]definiert Indexeigenschaften und Längeneigenschaften wie {0: undefined, 1: undefined, 2: undefined, length: 3}. Siehe https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Schritt 5.

Methoden map,every , some, forEach, slice, reduce, reduceRight, filterder den Index Array Durch prüfen HasPropertyinterne Methode, so new Array(3).map(v => 1)wird nicht invoke den Rückruf.

Weitere Informationen finden Sie unter https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

Wie repariert man?

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
quelle
Sehr schöne Erklärung.
Dheeraj Rao
7

Ich denke, der beste Weg, dies zu erklären, besteht darin, sich anzusehen, wie Chrome damit umgeht.

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

Was also tatsächlich passiert, ist, dass new Array () ein leeres Array zurückgibt, das eine Länge von 3, aber keine Werte hat. Wenn Sie also x.mapauf einem technisch leeren Array ausgeführt werden, muss nichts festgelegt werden.

Firefox "füllt" nur diese leeren Slots mit undefined, obwohl es keine Werte hat.

Ich denke nicht, dass dies explizit ein Fehler ist, sondern nur eine schlechte Art, das darzustellen, was vor sich geht. Ich nehme an, Chrome ist "korrekter", weil es zeigt, dass das Array eigentlich nichts enthält.

halloandre
quelle
4

Bin gerade darauf gestoßen. Es wäre sicher bequem, verwenden zu können Array(n).map.

Array(3) ergibt ungefähr {length: 3}

[undefined, undefined, undefined]erstellt die nummerierten Eigenschaften :
{0: undefined, 1: undefined, 2: undefined, length: 3}.

Die map () - Implementierung wirkt sich nur auf definierte Eigenschaften aus.

Vezquex
quelle
3

Kein Fehler. So wird der Array-Konstruktor so definiert, dass er funktioniert.

Von MDC:

Wenn Sie mit dem Array-Konstruktor einen einzelnen numerischen Parameter angeben, geben Sie die Anfangslänge des Arrays an. Der folgende Code erstellt ein Array mit fünf Elementen:

var billingMethod = new Array(5);

Das Verhalten des Array-Konstruktors hängt davon ab, ob der einzelne Parameter eine Zahl ist.

Die .map()Methode enthält nur Elemente in dem Iterationsbereich des Arrays, denen explizit Werte zugewiesen wurden. Selbst eine explizite Zuweisung von undefinedführt dazu, dass ein Wert als für die Aufnahme in die Iteration geeignet angesehen wird. Das scheint seltsam, aber es ist im Wesentlichen der Unterschied zwischen einer expliziten undefinedEigenschaft für ein Objekt und einer fehlenden Eigenschaft:

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

Das Objekt xhat keine Eigenschaft namens "z", und das Objekt yhat eine. In beiden Fällen scheint jedoch der "Wert" der Eigenschaft zu sein undefined. In einem Array ist die Situation ähnlich: Der Wert von lengthführt implizit eine Wertzuweisung für alle Elemente von Null bis durch length - 1. Die .map()Funktion führt daher nichts aus (ruft den Rückruf nicht auf), wenn sie für ein Array aufgerufen wird, das mit dem Array-Konstruktor und einem numerischen Argument neu erstellt wurde.

Spitze
quelle
Es ist definiert, gebrochen zu sein? Es ist entworfen, um eine Reihe von drei Elementen zu produzieren, die immer undefinedfür immer sein werden?
Leichtigkeitsrennen im Orbit
Ja, das ist richtig, bis auf den Teil "für immer". Anschließend können Sie den Elementen Werte zuweisen.
Pointy
3
Deshalb sollten Sie x = []anstelle vonx = new Array()
Rocket Hazmat
3

Wenn Sie dies tun, um ein Array einfach mit Werten zu füllen, aus Gründen der Browserunterstützung keine Füllung verwenden können und wirklich keine for-Schleife ausführen möchten, können Sie dies auch tun, x = new Array(3).join(".").split(".").map(...wodurch Sie ein leeres Array erhalten Saiten.

Ziemlich hässlich muss ich sagen, aber zumindest das Problem und die Absicht werden ganz klar kommuniziert.

Alex
quelle
1

Da die Frage ist, warum, hat dies damit zu tun, wie JS entworfen wurde.

Es gibt zwei Hauptgründe, warum ich mir dieses Verhalten erklären kann:

  • Leistung: Gegeben x = 10000und new Array(x)es ist ratsam, dass der Konstruktor eine Schleife von 0 bis 10000 vermeidet, um das Array mit undefinedWerten zu füllen .

  • Implizit „undefined“: give a = [undefined, undefined]und b = new Array(2), a[1]und b[1]werden beide Rückkehr undefined, sondern a[8]und b[8]auch zurückkehren , undefinedselbst wenn sie von Bereich unterwegs sind.

Letztendlich ist die Notation empty x 3eine Verknüpfung, um zu vermeiden, dass eine lange Liste von undefinedWerten festgelegt und angezeigt wird , die undefinedohnehin nicht explizit deklariert sind.

Hinweis: Bei Array a = [0]und a[9] = 9, console.log(a)kehrt (10) [0, empty x 8, 9], füllt die Lücke automatisch , indem die Differenz zwischen den beiden Werten Rückkehr ausdrücklich erklärt.

BPS Julien
quelle
1

Aus Gründen, die in anderen Antworten ausführlich erläutert wurden, Array(n).mapfunktioniert dies nicht. In ES2015 wird jedoch Array.fromeine Kartenfunktion akzeptiert:

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
quelle
0

Hier ist eine einfache Dienstprogrammmethode als Problemumgehung:

Einfache mapFor

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]

Vollständiges Beispiel

Hier ist ein vollständigeres Beispiel (mit Sanity Checks), mit dem auch ein optionaler Startindex angegeben werden kann:

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]

Runterzählen

Durch Manipulieren des an den Rückruf übergebenen Index kann rückwärts gezählt werden:

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