Estranho JavaScript "nova matriz (n)" e "matriz.protótipo.map"

209

Eu observei isso no Firefox-3.5.7 / Firebug-1.5.3 e Firefox-3.6.16 / Firebug-1.6.2

Quando eu inicio o 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]

O que está acontecendo aqui? Isso é um bug ou estou entendendo mal como usar new Array(3)?

rampion
fonte
Não recebo os mesmos resultados que você vê na notação literal da matriz. Ainda fico indefinido em vez de 0. Só recebo o resultado 0 se definir algo parecido var y = x.map(function(){return 0; });e o recebo tanto no novo método Array () quanto no literal da matriz. Eu testei no Firefox 4 e Chrome.
RussellUresti
também foi eliminado no Chrome, isso pode ser definido no idioma, embora não faça sentido, então eu realmente espero que não seja
Hashbrown

Respostas:

125

Parece que o primeiro exemplo

x = new Array(3);

Cria uma matriz com ponteiros indefinidos.

E o segundo cria uma matriz com ponteiros para 3 objetos indefinidos; nesse caso, os ponteiros não são indefinidos, apenas os objetos para os quais apontam.

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

Como o mapa é executado no contexto dos objetos na matriz, acredito que o primeiro mapa não consiga executar a função, enquanto o segundo consegue executar.

David Mårtensson
fonte
86
Do MDC (ênfase minha): " mapchama uma função de retorno de chamada fornecida uma vez para cada elemento de uma matriz, em ordem e constrói uma nova matriz a partir dos resultados. callbackÉ invocada apenas para índices da matriz que possuem valores atribuídos ; não é invocada para índices que foram excluídos ou aos quais nunca foram atribuídos valores ". Nesse caso, xos valores de não atribuíram valores explicitamente, enquanto yos foram atribuídos, mesmo que fosse o valor undefined.
Martijn
2
Então, se um JavaScript falha, é impossível verificar se é um ponteiro indefinido ou um ponteiro para indefinido? Quero dizer (new Array(1))[0] === [undefined][0].
Trevor Norris
Bem, uma matriz de indefinida é diferente de uma matriz de ponteiros para objetos indefinidos. Uma matriz de undefines seria como uma matriz de valores nulos, [null, null, null] enquanto uma matriz de ponteiros para undefined seria como [343423, 343424, 343425] apontando para null e null e null. As segundas soluções têm indicadores reais apontando para endereços de memória, enquanto a primeira não aponta para lugar nenhum. Se isso é uma falha de JS é provavelmente uma questão o debate, mas não aqui;)
David Mårtensson
4
@TrevNorris, você pode facilmente testá-lo com hasOwnPropertymenos que hasOwnPropertyele tenha um bug: (new Array(1)).hasOwnProperty(0) === falsee [undefined].hasOwnProperty(0) === true. De fato, você pode fazer exatamente o mesmo com in: 0 in [undefined] === truee 0 in new Array(0) === false.
squid314
3
Falar sobre "ponteiros indefinidos" em JavaScript confunde o problema. O termo que você procura é "elisões" . x = new Array(3);é equivalente a x = [,,,];, não x = [undefined, undefined, undefined].
Matt Kantor
118

Eu tinha uma tarefa que sabia apenas o comprimento da matriz e precisava transformar os itens. Eu queria fazer algo assim:

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

Para criar rapidamente uma matriz como esta:

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

Mas não funcionou porque: (veja a resposta de Jonathan Lonowski, algumas respostas acima)

A solução poderia ser preencher os itens da matriz com qualquer valor (mesmo com indefinido) usando Array.prototype.fill ()

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

Atualizar

Outra solução poderia ser:

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

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

cstuncsik
fonte
28
importante notar que você não precisa para estado undefinedno .fill()método, simplificar o código muito ligeiramente paralet arr = new Array(10).fill().map((val,idx) => idx);
Yann Eves
Da mesma forma, você pode usarArray.from(Array(10))
84

Com o ES6, você pode fazer [...Array(10)].map((a, b) => a), rápido e fácil!

Manuel Beaudru
fonte
9
Pré-ES6 você pode usar new Array(10).fill(). Mesmo resultado que[...Array(10)]
Molomby em 01/08/19
Com grandes matrizes a sintaxe propagação cria problemas por isso é melhor evitar
ou[...Array(10).keys()]
Chungzuwalla
25

Solução ES6:

[...Array(10)]

Não funciona no texto datilografado (2.3), embora

Serge Intern
fonte
6
Array(10).fill("").map( ...é o que funcionou para mim com Typescript 2.9
ibex
19

As matrizes são diferentes. A diferença é que new Array(3)cria uma matriz com um comprimento de três, mas sem propriedades, enquanto [undefined, undefined, undefined]cria uma matriz com um comprimento de três e três propriedades chamadas "0", "1" e "2", cada uma com um valor de undefined. Você pode ver a diferença usando o inoperador:

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

Isso decorre do fato levemente confuso de que, se você tentar obter o valor de uma propriedade inexistente de qualquer objeto nativo em JavaScript, ele retornará undefined(em vez de gerar um erro, como acontece quando você tenta se referir a uma variável inexistente ), que é o mesmo que você obtém se a propriedade tiver sido explicitamente definida como undefined.

Tim Down
fonte
17

Na página MDC para map:

[...] callbacké invocado apenas para índices da matriz que atribuíram valor; [...]

[undefined]na verdade se aplica a levantadora no índice (es) de modo que mapirá iterar, enquanto new Array(1)apenas inicializa o índice (es) com um valor padrão de undefinedmodo mapignora-lo.

Eu acredito que isso é o mesmo para todos os métodos de iteração .

Jonathan Lonowski
fonte
8

Na especificação da 6ª edição do ECMAScript.

new Array(3)apenas define propriedade lengthe não define propriedades de índice como {length: 3}. consulte https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-len Etapa 9.

[undefined, undefined, undefined]irá definir propriedades de índice e propriedades de comprimento como {0: undefined, 1: undefined, 2: undefined, length: 3}. consulte https://www.ecma-international.org/ecma-262/6.0/index.html#sec-runtime-semantics-arrayaccumulation ElementList Etapa 5.

métodos map, every, some, forEach, slice, reduce, reduceRight, filterda matriz irá verificar a propriedade índice pelo HasPropertymétodo interno, então new Array(3).map(v => 1)não irá invocar o retorno de chamada.

para obter mais detalhes, consulte https://www.ecma-international.org/ecma-262/6.0/index.html#sec-array.prototype.map

Como consertar?

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
fonte
Explicação muito boa.
Dheeraj Rao 23/01
7

Eu acho que a melhor maneira de explicar isso é olhar para a maneira como o Chrome lida com isso.

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

Então, o que realmente está acontecendo é que o novo Array () está retornando um array vazio com um comprimento de 3, mas sem valores. Portanto, quando você executa x.mapem uma matriz tecnicamente vazia, não há nada a ser definido.

O Firefox apenas 'preenche' esses slots vazios undefinedmesmo que não tenha valores.

Eu não acho que isso seja explicitamente um bug, apenas uma maneira pobre de representar o que está acontecendo. Suponho que o Chrome seja "mais correto" porque mostra que não há realmente nada na matriz.

helloandre
fonte
4

Apenas corri para isso. Com certeza seria conveniente poder usar Array(n).map.

Array(3) produz aproximadamente {length: 3}

[undefined, undefined, undefined]cria as propriedades numeradas:
{0: undefined, 1: undefined, 2: undefined, length: 3}.

A implementação map () atua apenas nas propriedades definidas.

Vezquex
fonte
3

Não é um bug. É assim que o construtor Array é definido para funcionar.

Do MDC:

Ao especificar um único parâmetro numérico com o construtor Array, você especifica o comprimento inicial da matriz. O código a seguir cria uma matriz de cinco elementos:

var billingMethod = new Array(5);

O comportamento do construtor Array depende se o único parâmetro é um número.

O .map()método inclui apenas nos elementos de iteração da matriz que explicitamente tiveram valores atribuídos. Mesmo uma atribuição explícita de undefinedfará com que um valor seja considerado elegível para inclusão na iteração. Isso parece estranho, mas é essencialmente a diferença entre uma undefinedpropriedade explícita em um objeto e uma propriedade ausente:

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

O objeto xnão possui uma propriedade chamada "z" e o objeto ypossui. No entanto, nos dois casos, parece que o "valor" da propriedade é undefined. Em uma matriz, a situação é semelhante: o valor de lengthrealiza implicitamente uma atribuição de valor a todos os elementos de zero a zero length - 1. A .map()função, portanto, não fará nada (não chamará o retorno de chamada) quando chamada em uma matriz recém-construída com o construtor Array e um argumento numérico.

Pontudo
fonte
Está definido para ser quebrado? Ele foi projetado para produzir uma série de três elementos que sempre serão undefinedpara sempre?
Corridas de leveza em órbita
Sim, está correto, exceto pela parte "para sempre". Posteriormente, você pode atribuir valores aos elementos.
precisa
3
É por isso que você deve usar em x = []vez dex = new Array()
Rocket Hazmat 31/03
3

Se você está fazendo isso para preencher facilmente uma matriz com valores, não pode usar o preenchimento por razões de suporte ao navegador e realmente não deseja executar um loop for, também pode fazerx = new Array(3).join(".").split(".").map(... que fornecerá uma matriz de dados vazios cordas.

Bastante feio, devo dizer, mas pelo menos o problema e a intenção são claramente comunicados.

Alex
fonte
1

Como a pergunta é por que, isso tem a ver com a forma como o JS foi projetado.

Existem 2 razões principais em que posso pensar para explicar esse comportamento:

  • Desempenho: fornecido x = 10000e new Array(x)é aconselhável que o construtor evite o loop de 0 a 10000 para preencher a matriz com undefinedvalores.

  • Implicitamente "indefinido": Dê a = [undefined, undefined]e b = new Array(2), a[1]e b[1]irão devolver undefined, mas a[8]e b[8]também retornará undefinedmesmo se eles estão fora de alcance.

Por fim, a notação empty x 3é um atalho para evitar a configuração e a exibição de uma longa lista de undefinedvalores que são undefinedassim mesmo porque não são declarados explicitamente.

Nota: Dada a matriz a = [0]e a[9] = 9, console.log(a)retornará (10) [0, empty x 8, 9], preenchendo a lacuna automaticamente retornando a diferença entre os dois valores declarados explicitamente.

BPS Julien
fonte
1

Por motivos detalhados em outras respostas, Array(n).mapnão funciona. No entanto, no ES2015 Array.fromaceita uma função 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
fonte
0

Aqui está um método utilitário simples como solução alternativa:

MapFor simples

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]

Exemplo completo

Aqui está um exemplo mais completo (com verificações de integridade) que também permite especificar um í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]

Contagem decrescente

A manipulação do índice passado para o retorno de chamada permite a contagem inversa:

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