Rareza "Array (n)" y "Array.prototype.map" JavaScript

209

He observado esto en Firefox-3.5.7 / Firebug-1.5.3 y Firefox-3.6.16 / Firebug-1.6.2

Cuando enciendo 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]

¿Que está pasando aqui? ¿Es esto un error o no estoy entendiendo cómo usarlo new Array(3)?

rampion
fuente
No obtengo los mismos resultados que ves de la notación literal de matriz. Todavía obtengo indefinidos en lugar de 0. Solo obtengo el resultado 0 si configuro algo así var y = x.map(function(){return 0; });, y obtengo esto tanto para el nuevo método Array () como para la matriz literal. Probé en Firefox 4 y Chrome.
RussellUresti
también reventado en Chrome, esto podría definirse en el lenguaje, aunque no tiene sentido, así que realmente espero que no lo sea
Hashbrown

Respuestas:

125

Parece que el primer ejemplo

x = new Array(3);

Crea una matriz con punteros indefinidos.

Y el segundo crea una matriz con punteros a 3 objetos indefinidos, en este caso los punteros mismos NO están indefinidos, solo los objetos a los que apuntan.

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

Como el mapa se ejecuta en el contexto de los objetos en la matriz, creo que el primer mapa no puede ejecutar la función mientras que el segundo logra ejecutarse.

David Mårtensson
fuente
86
Desde MDC (énfasis mío): " mapllama a una función de devolución de llamada proporcionada una vez para cada elemento de una matriz, en orden, y construye una nueva matriz a partir de los resultados. callbackSe invoca solo para los índices de la matriz que tienen valores asignados ; no se invoca para índices que se han eliminado o a los que nunca se les han asignado valores ". En este caso, xlos valores no tienen valores explícitamente asignados, mientras que ylos valores se asignaron, incluso si era el valor undefined.
Martijn
2
Entonces, ¿es un error de JavaScript que es imposible verificar si es un puntero indefinido o un puntero indefinido? Me refiero (new Array(1))[0] === [undefined][0].
Trevor Norris
Bueno, una matriz de indefinidos es diferente de una matriz de punteros a objetos indefinidos. Una matriz de indefinidos sería como una matriz de valores nulos, [null, null, null], mientras que una matriz de punteros a indefinidos sería como [343423, 343424, 343425] apuntando a nulo y nulo y nulo. Las segundas soluciones tienen punteros reales que apuntan a direcciones de memoria, mientras que las primeras no apuntan a ninguna parte. Si eso es una falla de JS, probablemente sea un tema de discusión, pero no aquí;)
David Mårtensson
44
@TrevNorris, puedes probarlo fácilmente a hasOwnPropertymenos que hasOwnPropertytenga un error: (new Array(1)).hasOwnProperty(0) === falsey [undefined].hasOwnProperty(0) === true. De hecho, puede hacer exactamente lo mismo con in: 0 in [undefined] === truey 0 in new Array(0) === false.
squid314
3
Hablar sobre "punteros indefinidos" en JavaScript confunde el problema. El término que está buscando es "elisiones" . x = new Array(3);es equivalente a x = [,,,];, no x = [undefined, undefined, undefined].
Matt Kantor
118

Tenía una tarea que solo conocía la longitud de la matriz y necesitaba transformar los elementos. Quería hacer algo como esto:

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

Para crear rápidamente una matriz como esta:

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

Pero no funcionó porque: (vea la respuesta de Jonathan Lonowski algunas respuestas arriba)

La solución podría ser llenar los elementos de la matriz con cualquier valor (incluso con indefinido) usando Array.prototype.fill ()

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

Actualizar

Otra solución podría ser:

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

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

cstuncsik
fuente
28
Vale la pena señalar que no es necesario indicar undefinedel .fill()método, simplificando el código muy ligeramente paralet arr = new Array(10).fill().map((val,idx) => idx);
Yann Eves
De manera similar, puede usarArray.from(Array(10))
84

¡Con ES6, puedes hacerlo [...Array(10)].map((a, b) => a), rápido y fácil!

Manuel Beaudru
fuente
9
Pre-ES6 que puedes usar new Array(10).fill(). Mismo resultado que[...Array(10)]
Molomby
Con matrices grandes, la sintaxis de propagación crea problemas, por lo que es mejor evitarla
o[...Array(10).keys()]
Chungzuwalla
25

Solución ES6:

[...Array(10)]

Sin embargo, no funciona en mecanografiado (2.3)

Serge Intern
fuente
66
Array(10).fill("").map( ...es lo que funcionó para mí con Letra de imprenta 2.9
ibex
19

Las matrices son diferentes. La diferencia es que new Array(3)crea una matriz con una longitud de tres pero sin propiedades, mientras que [undefined, undefined, undefined]crea una matriz con una longitud de tres y tres propiedades llamadas "0", "1" y "2", cada una con un valor de undefined. Puedes ver la diferencia usando el inoperador:

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

Esto se debe al hecho un tanto confuso de que si intenta obtener el valor de una propiedad inexistente de cualquier objeto nativo en JavaScript, regresa undefined(en lugar de arrojar un error, como sucede cuando intenta hacer referencia a una variable inexistente ), que es lo mismo que obtienes si la propiedad se ha establecido explícitamente anteriormente undefined.

Tim Down
fuente
17

Desde la página de MDC para map:

[...] callbackse invoca solo para índices de la matriz que tienen un valor asignado; [...]

[undefined]en realidad aplica el setter en el (los) índice (es) para que mapitere, mientras que new Array(1)solo inicializa el (los) índice (es) con un valor predeterminado de undefinedmodo mapque lo omite.

Creo que esto es lo mismo para todos los métodos de iteración .

Jonathan Lonowski
fuente
8

En la especificación ECMAScript sexta edición.

new Array(3)solo define propiedades lengthy no define propiedades de índice como {length: 3}. ver https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Paso 9.

[undefined, undefined, undefined]definirá propiedades de índice y propiedades de longitud como {0: undefined, 1: undefined, 2: undefined, length: 3}. ver https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Paso 5.

métodos map, every, some, forEach, slice, reduce, reduceRight, filterde array se compruebe la propiedad de índice por HasPropertymétodo interno, por lo que new Array(3).map(v => 1)no invocar la devolución de llamada.

Para obtener más detalles, consulte https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

¿Como arreglar?

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
fuente
Muy buena explicación.
Dheeraj Rao
7

Creo que la mejor manera de explicar esto es mirar la forma en que Chrome lo maneja.

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

Entonces, lo que realmente está sucediendo es que el nuevo Array () está devolviendo una matriz vacía que tiene una longitud de 3, pero sin valores. Por lo tanto, cuando se ejecuta x.mapen una matriz técnicamente vacía, no hay nada que configurar.

Firefox simplemente 'llena' esas ranuras vacías undefinedaunque no tenga valores.

No creo que esto sea explícitamente un error, solo una mala forma de representar lo que está sucediendo. Supongo que Chrome es "más correcto" porque muestra que en realidad no hay nada en la matriz.

holaandre
fuente
4

Acabo de encontrarme con esto. Seguro que sería conveniente poder usarlo Array(n).map.

Array(3) rinde aproximadamente {length: 3}

[undefined, undefined, undefined]crea las propiedades numeradas:
{0: undefined, 1: undefined, 2: undefined, length: 3}.

La implementación de map () solo actúa sobre propiedades definidas.

Vezquex
fuente
3

No es un error Así es como se define el constructor Array para trabajar.

De MDC:

Cuando especifica un único parámetro numérico con el constructor de matriz, especifica la longitud inicial de la matriz. El siguiente código crea una matriz de cinco elementos:

var billingMethod = new Array(5);

El comportamiento del constructor de matriz depende de si el parámetro único es un número.

El .map()método solo incluye en la iteración elementos de la matriz que tienen valores asignados explícitamente. Incluso una asignación explícita de undefinedhará que un valor se considere elegible para su inclusión en la iteración. Eso parece extraño, pero es esencialmente la diferencia entre una undefinedpropiedad explícita en un objeto y una propiedad que falta:

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

El objeto xno tiene una propiedad llamada "z", y el objeto ysí. Sin embargo, en ambos casos parece que el "valor" de la propiedad es undefined. En una matriz, la situación es similar: el valor de lengthrealiza implícitamente una asignación de valor a todos los elementos desde cero hasta length - 1. La .map()función por lo tanto no va a hacer nada (no llamará a la devolución de llamada) cuando se le llama en una serie de nueva construcción con el constructor Array y un argumento numérico.

Puntiagudo
fuente
¿Está definido como roto? ¿Está diseñado para producir una serie de tres elementos que siempre serán undefinedpara siempre?
Carreras de ligereza en órbita
Sí, eso es correcto, excepto por la parte "para siempre". Posteriormente puede asignar valores a los elementos.
Puntiagudo
3
Es por eso que debe usar en x = []lugar dex = new Array()
Rocket Hazmat
3

Si está haciendo esto para llenar fácilmente una matriz con valores, no puede usar el relleno por razones de soporte del navegador y realmente no quiere hacer un ciclo for, también puede hacer lo x = new Array(3).join(".").split(".").map(...que le dará una matriz de vacío instrumentos de cuerda.

Debo decir que es bastante feo, pero al menos el problema y la intención se comunican con bastante claridad.

Alex
fuente
1

Dado que la pregunta es por qué, esto tiene que ver con cómo se diseñó JS.

Hay dos razones principales que se me ocurren para explicar este comportamiento:

  • Rendimiento: dado x = 10000y new Array(x)es aconsejable que el constructor evite bucles de 0 a 10000 para llenar la matriz con undefinedvalores.

  • Implícitamente "indefinido": Dar a = [undefined, undefined]y b = new Array(2), a[1]y b[1]lo hará tanto la rentabilidad undefined, pero a[8]y b[8]también regresará undefinedincluso si están fuera de rango.

En última instancia, la notación empty x 3es un atajo para evitar establecer y mostrar una larga lista de undefinedvalores que de undefinedtodos modos se deben a que no se declaran explícitamente.

Nota: Dada la matriz a = [0]y a[9] = 9, console.log(a)regresará (10) [0, empty x 8, 9], llenando el vacío automáticamente al devolver la diferencia entre los dos valores declarados explícitamente.

BPS Julien
fuente
1

Por razones explicadas a fondo en otras respuestas, Array(n).mapno funciona. Sin embargo, en ES2015 Array.fromacepta una función de mapa:

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

Aquí hay un método de utilidad simple como solución alternativa:

Mapa simple para

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]

Ejemplo completo

Aquí hay un ejemplo más completo (con controles de cordura) que también permite especificar un índice inicial opcional:

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 hacia atrás

Manipular el índice pasado a la devolución de llamada permite contar hacia atrás:

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