web-dev-qa-db-fra.com

JavaScript "new Array (n)" et "Array.prototype.map" bizarre

J'ai observé cela dans Firefox-3.5.7/Firebug-1.5.3 et Firefox-3.6.16/Firebug-1.6.2

Quand j'allume Firebug:

    >>> x = new Array(3)
    [undefined, undefined, undefined]
    >>> y = [undefined, undefined, undefined]
    [undefined, undefined, undefined]

    >>> x.constructor == y.constructor
    true

    >>> x.map(function(){ return 0; })
    [undefined, undefined, undefined]
    >>> y.map(function(){ return 0; })
    [0, 0, 0]

Que se passe t-il ici? Est-ce un bug ou est-ce que je ne comprends pas comment utiliser new Array(3)?

183
rampion

Il semble que le premier exemple

x = new Array(3);

Crée un tableau avec des pointeurs indéfinis.

Et le second crée un tableau avec des pointeurs sur 3 objets non définis. Dans ce cas, les pointeurs eux-mêmes ne sont PAS indéfinis, mais uniquement les objets sur lesquels ils pointent.

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

Comme la carte est exécutée dans le contexte des objets du tableau, je pense que la première carte ne parvient pas à exécuter la fonction, tandis que la seconde parvient à s'exécuter.

112
David Mårtensson

J'avais une tâche dont je ne connaissais que la longueur et que je devais transformer les éléments. Je voulais faire quelque chose comme ça:

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

Pour créer rapidement un tableau comme celui-ci:

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

Mais cela n'a pas fonctionné parce que: voir la réponse de Jonathan Lonowski plus haut.

La solution pourrait être de remplir les éléments du tableau avec n'importe quelle valeur (même avec undefined) en utilisant Array.prototype.fill ()

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

Mettre à jour

Une autre solution pourrait être:

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

Avec ES6, vous pouvez faire [...Array(10)].map((a, b) => a), rapidement et facilement!

74
Manuel Beaudru

Solution ES6:

[...Array(10)]

Ne fonctionne pas sur TypeScript (2.3), cependant

21
Serge Intern

Les tableaux sont différents. La différence est que new Array(3) crée un tableau d'une longueur de trois mais pas de propriétés, tandis que [undefined, undefined, undefined] crée un tableau d'une longueur de trois et trois propriétés appelées "0", "1" et "2", chacune avec une valeur de undefined. Vous pouvez voir la différence en utilisant l'opérateur in:

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

Cela provient du fait légèrement déroutant que si vous essayez d'obtenir la valeur d'une propriété inexistante d'un objet natif en JavaScript, il retourne undefined (plutôt que de générer une erreur, comme cela se produit lorsque vous essayez de vous référer variable inexistante), ce qui est identique à ce que vous obtenez si la propriété a déjà été explicitement définie sur undefined.

17
Tim Down

Depuis la page MDC pour map :

[...] callback n'est appelé que pour les index du tableau auxquels une valeur a été attribuée; [...]

[undefined] Applique le paramètre sur l'index (s) de sorte que map itérera, alors que new Array(1) initialise simplement l'index (s) avec une valeur par défaut de undefined so map le saute.

Je crois que c'est la même pour tous les méthodes d'itération .

14
Jonathan Lonowski

Je pense que la meilleure façon d’expliquer cela est d’examiner la façon dont Chrome le gère.

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

Donc, ce qui se passe réellement, c'est que new Array () renvoie un tableau vide qui a une longueur de 3, mais aucune valeur. Par conséquent, lorsque vous exécutez x.map sur un tableau techniquement vide, il n'y a rien à définir.

Firefox "remplit" ces emplacements vides avec undefined même s'il n'a aucune valeur.

Je ne pense pas que ce soit explicitement un bug, mais simplement une mauvaise façon de représenter ce qui se passe. Je suppose que Chrome est "plus correct" car il montre qu'il n'y a rien en réalité dans le tableau.

7
helloandre

Dans la spécification ECMAScript 6ème édition.

new Array(3) définit uniquement la propriété length et ne définit pas de propriétés d'index telles que {length: 3}. voir https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Étape 9.

[undefined, undefined, undefined] Définira les propriétés de l'index et de la longueur, comme {0: undefined, 1: undefined, 2: undefined, length: 3}. voir https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulationElementList étape 5.

méthodes map, every, some, forEach, slice, reduce, reduceRight, filter of Array vérifiera la propriété index par HasProperty méthode interne, de sorte que new Array(3).map(v => 1) n'appellera pas le rappel.

pour plus de détails, voir https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

Comment réparer?

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

Juste couru dans cela. Il serait pratique d’utiliser Array(n).map.

Array(3) donne approximativement {length: 3}

[undefined, undefined, undefined] crée les propriétés numérotées:
{0: undefined, 1: undefined, 2: undefined, length: 3}.

L'implémentation map () n'agit que sur les propriétés définies.

4
Vezquex

Pas un bug. C'est ainsi que le constructeur Array est défini pour fonctionner.

De MDC:

Lorsque vous spécifiez un paramètre numérique unique avec le constructeur Array, vous indiquez la longueur initiale du tableau. Le code suivant crée un tableau de cinq éléments:

var billingMethod = new Array(5);

Le comportement du constructeur Array dépend de si le paramètre unique est un nombre.

La méthode .map() inclut uniquement dans l'itération des éléments du tableau auxquels des valeurs ont été explicitement attribuées. Même une affectation explicite de undefined fera en sorte qu'une valeur soit considérée comme éligible pour l'inclusion dans l'itération. Cela semble étrange, mais c'est essentiellement la différence entre une propriété explicite undefined sur un objet et une propriété manquante:

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

L'objet x ne possède pas de propriété appelée "z", et l'objet y. Cependant, dans les deux cas, il apparaît que la "valeur" de la propriété est undefined. Dans un tableau, la situation est similaire: la valeur de length effectue implicitement une attribution de valeur à tous les éléments de zéro à length - 1. La fonction .map() ne fera donc rien (n'appelle pas le rappel) lorsqu'elle est appelée sur un tableau nouvellement construit avec le constructeur Array et un argument numérique.

3
Pointy

Si vous faites cela pour remplir facilement un tableau avec des valeurs, vous ne pouvez pas utiliser remplissage pour des raisons de support du navigateur et vous ne voulez vraiment pas faire de boucle for, vous pouvez aussi faire x = new Array(3).join(".").split(".").map(... qui vous donnera un tableau de chaînes vides.

Assez moche, je dois le dire, mais au moins le problème et l'intention sont clairement communiqués.

3
Alex

Voici une méthode d’utilité simple comme solution de contournement:

Simple 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]

Exemple complet

Voici un exemple plus complet (avec vérifications de sécurité) qui permet également de spécifier un index de départ facultatif:

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]

décompter

La manipulation de l'index transmis au rappel permet de compter à rebours:

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