Como inicializar o comprimento de uma matriz em JavaScript?

542

A maioria dos tutoriais que li sobre matrizes em JavaScript (incluindo w3schools e devguru ) sugerem que você pode inicializar uma matriz com um certo comprimento passando um número inteiro ao construtor Array usando a var test = new Array(4);sintaxe.

Depois de usar essa sintaxe liberalmente nos meus arquivos js, executei um dos arquivos através do jsLint , e ele se assustou:

Erro: Problema no caractere da linha 1 22: Esperado ')' e viu '4'.
teste var = nova matriz (4);
Problema na linha 1, caractere 23: Esperado ';' e viu ')'.
teste var = nova matriz (4);
Problema no caractere 23 da linha 1: esperava um identificador e viu ')'.

Após ler a explicação do comportamento de jsLint, parece que o jsLint não gosta muito da new Array()sintaxe e prefere []ao declarar matrizes.

Então, eu tenho algumas perguntas:

Primeiro porque? Estou correndo algum risco usando a new Array()sintaxe? Existem incompatibilidades de navegador que eu devo estar ciente?

E segundo, se eu alternar para a sintaxe entre colchetes, existe alguma maneira de declarar uma matriz e definir seu comprimento em uma linha ou preciso fazer algo assim:

var test = [];
test.length = 4;
Michael Martin-Smucker
fonte
js padrão também aconselham contra o usonew Array()em geral, mas está tudo bem com a especificação de tamanho. Eu acho que tudo se resume à consistência do código em todo o contexto.
Cregox 01/04
Para aqueles que procuram pré-alocar estruturas de matriz mais rígidas, existem matrizes digitadas . Note-se que os benefícios de desempenho podem variar
rovyko

Respostas:

398
  1. Por que você deseja inicializar o comprimento? Teoricamente, não há necessidade disso. Pode até resultar em comportamento confuso, porque todos os testes que usam o lengthpara descobrir se uma matriz está vazia ou não relatam que a matriz não está vazia.
    Alguns testes mostram que definir o comprimento inicial de matrizes grandes pode ser mais eficiente se a matriz for preenchida posteriormente, mas o ganho de desempenho (se houver) parece diferir de navegador para navegador.

  2. O jsLint não gosta new Array()porque o construtor é ambíguo.

    new Array(4);

    cria uma matriz vazia de comprimento 4. Mas

    new Array('4');

    cria uma matriz contendo o valor '4' .

Sobre o seu comentário: Em JS, você não precisa inicializar o comprimento da matriz. Cresce dinamicamente. Você pode simplesmente armazenar o comprimento em alguma variável, por exemplo

var data = [];
var length = 5; // user defined length

for(var i = 0; i < length; i++) {
    data.push(createSomeObject());
}
Felix Kling
fonte
13
O número de objetos na matriz é definido pelo usuário; portanto, deixei o usuário escolher um número e, em seguida, inicializei a matriz com tantos slots. Em seguida, corro um forloop que itera sobre o comprimento da matriz e o preenche. Eu acho que haveria outras maneiras de fazer isso no JavaScript, então a resposta real para "Por que eu quero fazer isso?" é "Por causa de velhos hábitos que se formaram durante a programação em outras linguagens". :)
Michael Martin-Smucker
4
@mlms Apenas deixe o loop for iterar sobre o número definido pelo usuário, em vez de ter que definir o comprimento da matriz e, em seguida, iterar sobre ele. Isso não faz mais sentido para você?
Šime Vidas 31/01
151
<blockquote> Por que você deseja inicializar o comprimento? Teoricamente, não há necessidade disso. E todos os testes que usam o comprimento para descobrir se uma matriz está vazia ou não falharão. </blockquote> Hum, desempenho , talvez? É mais rápido definir um elemento preexistente de uma matriz do que adicioná-lo rapidamente.
codehead
45
O comentário "hora de abandonar velhos hábitos de programação" é realmente desnecessário. línguas devidamente estruturados será sempre outperform ECMAScript mombo jambo
user151496
10
@codehead: em resposta a esta citação: "É mais rápido definir um elemento preexistente de uma matriz do que adicioná-lo em tempo real.": observe que new Array(10)não cria uma matriz com 10 elementos indefinidos. Ele simplesmente cria uma matriz vazia com um comprimento de 10. Veja esta resposta para a verdade desagradável: stackoverflow.com/questions/18947892/…
Milimetric
598
  • Array(5) fornece uma matriz com comprimento 5, mas sem valores; portanto, você não pode iterar sobre ela.

  • Array.apply(null, Array(5)).map(function () {}) fornece uma matriz com comprimento 5 e indefinida como valores, agora pode ser iterada.

  • Array.apply(null, Array(5)).map(function (x, i) { return i; }) fornece uma matriz com comprimento 5 e valores 0,1,2,3,4.

  • Array(5).forEach(alert)não faz nada, Array.apply(null, Array(5)).forEach(alert)dá 5 alertas

  • ES6nos dá Array.fromentão agora você também pode usarArray.from(Array(5)).forEach(alert)

  • Se você quiser inicializar com um determinado valor, estes são bons para sabe ...
    Array.from('abcde'), Array.from('x'.repeat(5))
    ouArray.from({length: 5}, (v, i) => i) // gives [0, 1, 2, 3, 4]

Ruben Stolk
fonte
11
Era isso que eu estava procurando. Eu queria aplicar um mapa em uma sequência lógica; Isso deve servir. Obrigado!
jedd.ahyoung
3
por que Array.apply () atribui-o indefinido como valor?
Wdanxna
5
@wdanxna, quando Arrayrecebe vários argumentos, itera sobre o argumentsobjeto e aplica explicitamente cada valor à nova matriz. Quando você chama Array.applycom uma matriz ou um objeto com uma propriedade length, Arrayusa o comprimento para definir explicitamente cada valor da nova matriz. É por isso que Array(5)fornece uma matriz de 5 elisões, enquanto Array.apply(null, Array(5))fornece uma matriz de 5 não definidas. Para mais informações, consulte esta resposta.
KylePlusPlus
2
existe diferença entre a matriz (5) e a nova matriz (5)?
Petr Hurtak
2
Também Array.apply(null, Array(5)) pode ser escrito como Array.apply(null, {length: 5}). Não há realmente muita diferença, mas o último é inequivocamente claro que a intenção é criar uma matriz de length 5, e não um array contendo5
AlexMorley-Finch
272

Com o ES2015,.fill() agora você pode simplesmente fazer:

// `n` is the size you want to initialize your array
// `0` is what the array will be filled with (can be any other value)
Array(n).fill(0)

O que é muito mais conciso do que Array.apply(0, new Array(n)).map(i => value)

É possível soltar o 0in .fill()e executar sem argumentos, o que preencherá a matriz undefined. ( No entanto, isso falhará no texto datilografado )

AP.
fonte
1
@AralRoca Você sempre pode usar o Polyfill fornecido na página MDN ou considerar o Modernizr .
AP.
4
Sim, tente antes de comentar
AP.
1
@GarretWilson e @Christian Está na especificação . When Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments
AP.
1
@AP., O zero é redundante. DoArray(n).fill()
Pacerier
2
@ Pacerier Não acho que o 0 seja redundante. De acordo com a definição de tipo es6, esta é a assinatura da função: fill (valor: T, start ?: number, end ?: number): this; Portanto, o valor de preenchimento não parece opcional. A compilação de texto datilografado falhará como a chamada de função está escrita acima.
Micha Schwab
151
[...Array(6)].map(x => 0);
// [0, 0, 0, 0, 0, 0]

OU

Array(6).fill(0);
// [0, 0, 0, 0, 0, 0]

Nota: você não pode repetir slots vazios, ou seja Array(4).forEach(() => …)


OU

( seguro datilografado )

Array(6).fill(null).map((_, i) => i);
// [0, 1, 2, 3, 4, 5]

OU

Método clássico usando uma função (funciona em qualquer navegador)

function NewArray(size) {
    var x = [];
    for (var i = 0; i < size; ++i) {
        x[i] = i;
        return x;
    }
}

var a = NewArray(10);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Criando matrizes aninhadas

Ao criar uma matriz 2D com o fillintuitivamente deve criar novas instâncias. Mas o que realmente vai acontecer é o mesmo array que será armazenado como referência.

var a = Array(3).fill([6]);
// [  [6], [6], [6]  ]

a[0].push(9);
// [  [6, 9], [6, 9], [6, 9]  ]

Solução

var a = [...Array(3)].map(x => []);

a[0].push(4, 2);
// [  [4, 2], [], []  ]

Portanto, uma matriz 3x2 terá a seguinte aparência:

[...Array(3)].map(x => Array(2).fill(0));
// [  [0, 0], [0, 0], [0, 0]  ]

Matriz N-dimensional

function NArray(...dimensions) {
    var index = 0;
    function NArrayRec(dims) {
        var first = dims[0], next = dims.slice().splice(1); 
        if(dims.length > 1) 
            return Array(dims[0]).fill(null).map((x, i) => NArrayRec(next ));
        return Array(dims[0]).fill(null).map((x, i) => (index++));
    }
    return NArrayRec(dimensions);
}

var arr = NArray(3, 2, 4);
// [   [  [ 0,  1,  2,  3 ] , [  4,  5,  6,  7]  ],
//     [  [ 8,  9,  10, 11] , [ 12, 13, 14, 15]  ],
//     [  [ 16, 17, 18, 19] , [ 20, 21, 22, 23]  ]   ]

Inicializar um tabuleiro de xadrez

var Chessboard = [...Array(8)].map((x, j) => {
    return Array(8).fill(null).map((y, i) => {
        return `${String.fromCharCode(65 + i)}${8 - j}`;
    });
});

// [ [A8, B8, C8, D8, E8, F8, G8, H8],
//   [A7, B7, C7, D7, E7, F7, G7, H7],
//   [A6, B6, C6, D6, E6, F6, G6, H6],
//   [A5, B5, C5, D5, E5, F5, G5, H5],
//   [A4, B4, C4, D4, E4, F4, G4, H4],
//   [A3, B3, C3, D3, E3, F3, G3, H3],
//   [A2, B2, C2, D2, E2, F2, G2, H2],
//   [A1, B1, C1, D1, E1, F1, G1, H1] ]
Vlad
fonte
1
a[0].push(9); // [ [6, 9], [6], [6] ] deve ser a[0].push(9); // [ [6, 9], [6, 9], [6, 9] ] porque cada matriz aninhada é armazenada como referência, portanto, a mutação de uma matriz afeta o restante. Você pode ter digitado errado, embora eu pense :)
Alex_Zhong
@Alex_Zhong tru que, obrigado, perdeu isso em uma edição
Vlad
68

O mais curto:

[...Array(1000)]
Michael Mammoliti
fonte
11
Voto a favor, mas com uma ressalva, você absolutamente não deve iniciar uma linha em JS com a [sem usar a, ;pois ele tentará desreferenciar a linha acima. Portanto, uma maneira segura de usar essa linha previsivelmente em qualquer parte do código seria:;[...Array(1000)]//.whateverOpsNeeded()
AP.
Na verdade não. tente nas ferramentas do desenvolvedor. [...Array(5)].map((item, index) => ({ index })) tente também:[...Array(5)]; console.log('hello');
Michael Mammoliti
2
Tente usá-lo em um arquivo js de várias linhas. Se sua primeira linha é const a = 'a'e a próxima linha [...Array(5)].//irrelevent. O que você acha que a primeira linha resolveria? Seria o const a = 'a'[...Array(5)]que resultaria em:Uncaught SyntaxError: Unexpected token ...
AP.
7
Isso é verdade, mas, neste caso, eu diria que o problema está em outro lugar, ponto e vírgula e fiapos são importantes hoje em dia.
Michael Mammoliti
8
@AP esse exemplo é exatamente o motivo pelo qual muitos guias de estilo determinam o término de cada declaração com ponto e vírgula. const a = 'a'haverá um erro de cotão nesse caso. De qualquer forma, realmente não tem nada a ver com esta resposta.
graup 17/07
41

O ES6 apresenta o Array.fromque permite criar um objeto a Arraypartir de qualquer objeto "semelhante ao array" ou iterável :

Array.from({length: 10}, (x, i) => i);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Nesse caso, {length: 10}representa a definição mínima de um objeto "semelhante a uma matriz" : um objeto vazio com apenas uma lengthpropriedade definida.

Array.from permite que um segundo argumento seja mapeado sobre a matriz resultante.

Alexander Shutau
fonte
2
@dain Eu acho que [... Array (10)] é melhor stackoverflow.com/a/47929322/4137472
Alexander Shutau
1
Por que melhor? O uso do construtor Array é desencorajado, e alguns linter também poderia reclamar
Cristian Traina
26

Isso inicializará a propriedade length para 4:

var x = [,,,,];
Šime Vidas
fonte
3
Isso é esperto. Ele não tem a flexibilidade do construtor, porque você não pode usar uma variável para definir o comprimento, mas responde à minha pergunta como eu a expressei originalmente, então +1.
Michael Martin-Smucker 31/01
12
Imagine fazer isso para 300 itens quando o desempenho realmente importa!
Marco Luglio
9
Provavelmente, não há muito ganho de desempenho ao pré-alocar uma matriz com tamanho tão pequeno. A diferença de desempenho será melhor percebida ao criar matrizes maiores. E nesses casos, inicializá-los com vírgulas seria um pouco esmagador.
Marco Luglio
6
Eu acho que isso vai produzir resultados diferentes em diferentes navegadores por causa da vírgula final, às vezes 4 e, por vezes, 5, veja stackoverflow.com/questions/7246618/...
jperelli
1
Que tal var size = 42; var myArray = eval("["+",".repeat(size)+"]");? (Não é tão grave;)
xoxox
15

Estou surpreso que não tenha sido sugerida uma solução funcional que permita definir o comprimento em uma linha. O seguinte é baseado no UnderscoreJS:

var test = _.map(_.range(4), function () { return undefined; });
console.log(test.length);

Pelas razões mencionadas acima, eu evitaria fazer isso, a menos que desejasse inicializar a matriz com um valor específico. É interessante notar que existem outras bibliotecas que implementam o intervalo, incluindo Lo-dash e Lazy, que podem ter características de desempenho diferentes.

christang
fonte
A sublinhada magia negra realmente não é necessária aqui. Dependências são como açúcar, parece doce no começo, mas antes que você perceba, você tem diabetes.
Romain Vincent
9

Por favor, as pessoas ainda não abandonam seus velhos hábitos. Há uma grande diferença na velocidade entre alocar memória e trabalhar com as entradas nessa matriz (como antigamente) e alocá-la várias vezes à medida que uma matriz cresce (o que é inevitavelmente o que o sistema faz sob o capô com outros métodos sugeridos) .

Nada disso é claro, até que você queira fazer algo bacana com matrizes maiores. Então faz.

Visto que ainda parece não haver opção no JS no momento para definir a capacidade inicial de uma matriz, eu uso o seguinte ...

var newArrayWithSize = function(size) {
  this.standard = this.standard||[];
  for (var add = size-this.standard.length; add>0; add--) {
   this.standard.push(undefined);// or whatever
  }
  return this.standard.slice(0,size);
}

Existem tradeoffs envolvidos:

  • Esse método leva tanto tempo quanto os outros para a primeira chamada para a função, mas muito pouco tempo para as chamadas posteriores (a menos que solicite uma matriz maior).
  • A standardmatriz reserva permanentemente tanto espaço quanto a maior matriz solicitada.

Mas se ele se encaixa no que você está fazendo, pode haver uma recompensa. Tempo informal coloca

for (var n=10000;n>0;n--) {var b = newArrayWithSize(10000);b[0]=0;}

bem rápido (cerca de 50ms para os 10000, considerando que com n = 1000000 demorou cerca de 5 segundos) e

for (var n=10000;n>0;n--) {
  var b = [];for (var add=10000;add>0;add--) {
    b.push(undefined);
  }
}

em mais de um minuto (cerca de 90 segundos para o 10000 no mesmo console cromado ou cerca de 2000 vezes mais lento). Essa não será apenas a alocação, mas também os 10000 toques, para o loop, etc.

wils
fonte
se toda vez que você chega ao final da matriz, você a duplica, a complexidade de inserir n valores ainda é O (n), portanto não há diferença na complexidade (mas é mais lenta).
Tomer Wolberg
Bem capturado @TomerWolberg, mudei de redação. É uma questão de saber se o método push tem maior complexidade do que a inicialização / cópia de um membro individual por fatia. AFAIK que eles são tempo constante, pelo menos eles poderiam ser, então eu usei a palavra 'velocidade'.
wils 26/10/19
8

(provavelmente foi melhor como comentário, mas demorou demais)

Então, depois de ler isso, fiquei curioso para saber se a pré-alocação era realmente mais rápida, porque em teoria deveria ser. No entanto, este blog deu algumas dicas desaconselhando http://www.html5rocks.com/en/tutorials/speed/v8/ .

Então, ainda não tenho certeza, coloquei-o à prova. E, como se vê, parece de fato ser mais lento.

var time = Date.now();
var temp = [];
for(var i=0;i<100000;i++){
    temp[i]=i;
}
console.log(Date.now()-time);


var time = Date.now();
var temp2 = new Array(100000);
for(var i=0;i<100000;i++){
    temp2[i] = i;
}
console.log(Date.now()-time); 

Esse código produz o seguinte após algumas execuções casuais:

$ node main.js 
9
16
$ node main.js 
8
14
$ node main.js 
7
20
$ node main.js 
9
14
$ node main.js 
9
19
j03m
fonte
3
Interessante, isso parecia inesperado, então fiz um teste JSPerf . Os resultados do Chrome correspondem aos testes do Node, mas o Firefox e o IE são um pouco mais rápidos quando você pré-aloca espaço para a matriz.
Michael Martin-Smucker
1
@ MichaelMartin-Smucker Espere ... isso significa que o V8 não é totalmente otimizado e perfeito?!?!? : P
Zeb McCorkle
1
@ MichaelMartin-Smucker - Acabei de executar seu jsperf no Chrome Versão 42.0.2311.90 (Para ser específico - Teste no Chrome 42.0.2311.90 de 32 bits no Windows Server 2008 R2 / 7 de 64 bits) e a matriz de tamanho dinâmico foi 69% mais lenta .
Yellen #
1
Idem. (Eu escrevi o teste do nó original) No chrome 42.0.2311.90 no Windows de tamanho dinâmico, foi 81% mais lento :). Mas nossos testes originais foram realizados há mais de um ano. O tempo continua a escorregar, escorregar ....
j03m
1
Fascinante ... pelos resultados no jsperf, parece que o pré-alocado recebeu um grande impulso no Chrome 38. O futuro!
precisa saber é o seguinte
8
var arr=[];
arr[5]=0;
alert("length="+arr.length); // gives 6
user1067305
fonte
2
Sim, no entanto, o console.log (arr) fornece [5: 0]uma matriz esparsa e provavelmente não é o que se deseja.
dreftymac
7

Aqui está outra solução

var arr = Array.apply( null, { length: 4 } );
arr;  // [undefined, undefined, undefined, undefined] (in Chrome)
arr.length; // 4

O primeiro argumento de apply()é uma ligação deste objeto, com a qual não nos importamos aqui, portanto, definimos como null.

Array.apply(..)está chamando a Array(..)função e espalhando o { length: 3 }valor do objeto como seus argumentos.

zangw
fonte
Eu não entendo por que essa resposta recebeu um voto negativo. Na verdade, é um bom truque. new Array(n)Isso significa que você não pode usar para inicializar uma matriz como essa new Array(n).map(function(notUsed,index){...}), mas com essa abordagem, como o @zangw mencionou, você pode fazê-lo. +1 de mim.
precisa saber é o seguinte
3

O construtor do array tem uma sintaxe ambígua , e o JSLint apenas prejudica seus sentimentos, afinal.

Além disso, seu código de exemplo está quebrado, a segunda varinstrução irá gerar a SyntaxError. Você está definindo a propriedade lengthda matriz test, portanto não há necessidade de outra var.

No que diz respeito às suas opções, array.lengthé a única opção "limpa". A pergunta é: por que você precisa definir o tamanho em primeiro lugar? Tente refatorar seu código para se livrar dessa dependência.

Ivo Wetzel
fonte
Uau, bom olho nesse segundo var test. Essa foi uma cópia e colagem desleixada da minha parte.
Michael Martin-Smucker
@IvoWetzel, você deseja definir o tamanho para melhorar o desempenho. Matrizes JS são matrizes dinâmicas . Se você não definir o tamanho (ou melhor, a capacidade), o JS alocará uma matriz de tamanho padrão. Se você adicionar mais elementos do que o necessário, ele terá que aumentar a matriz, o que significa que internamente alocará uma nova matriz e copiará todos os elementos.
Domi
3

Supondo que o comprimento da matriz seja constante. Em Javascript, é isso que fazemos:

const intialArray = new Array(specify the value);

mohan babu
fonte
2

Como explicado acima, o uso new Array(size)é um pouco perigoso. Em vez de usá-lo diretamente, coloque-o dentro de uma "função criadora de matriz". Você pode facilmente garantir que esta função esteja livre de erros e evite o perigo de ligar new Array(size)diretamente. Além disso, você pode atribuir a ele um valor inicial padrão opcional. Esta createArrayfunção faz exatamente isso:

function createArray(size, defaultVal) {
    var arr = new Array(size);
    if (arguments.length == 2) {
        // optional default value
        for (int i = 0; i < size; ++i) {
            arr[i] = defaultVal;
        }
    }
    return arr;
}
Domi
fonte
1

Na maioria das respostas, é recomendado para filla matriz porque, caso contrário, "você não pode iterar sobre ela" , mas isso não é verdade. Você pode iterar uma matriz vazia, mas não com forEach. Loops while, for of loops e for i loops funcionam bem.

const count = Array(5);

Não funciona.

console.log('---for each loop:---');
count.forEach((empty, index) => {
    console.log(`counting ${index}`);
});

Estes trabalhos:

console.log('---for of loop:---');
for (let [index, empty] of count.entries()) {
  console.log(`counting for of loop ${index}`);
}

console.log('---for i loop:---');
for (let i = 0, il = count.length; i < il; ++i) {
  console.log(`counting for i loop ${i}`);
}

console.log('---while loop:---');
let index = 0;
while (index < count.length) { 
  console.log(`counting while loop ${index}`); 
  index++; 
}

Verifique este violino com os exemplos acima.

Também os angulares *ngForfuncionam bem com uma matriz vazia:

<li *ngFor="let empty of count; let i = index" [ngClass]="
  <span>Counting with *ngFor {{i}}</span>
</li>
Wilt
fonte
-1

Você pode definir o comprimento da matriz usando array.length = youValue

Então seria

var myArray = [];
myArray.length = yourValue;
ajoposor
fonte
-3

O motivo pelo qual você não deve usar new Arrayé demonstrado por este código:

var Array = function () {};

var x = new Array(4);

alert(x.length);  // undefined...

Algum outro código pode mexer com a variável Array. Eu sei que é um pouco absurdo que alguém escreva esse código, mas ainda assim ...

Além disso, como Felix King disse, a interface é um pouco inconsistente e pode levar a alguns bugs muito difíceis de rastrear.

Se você deseja uma matriz com comprimento = x, preenchida com indefinida (como new Array(x)faria), você pode fazer o seguinte:

var x = 4;
var myArray = [];
myArray[x - 1] = undefined;

alert(myArray.length); // 4
nickf
fonte
48
De acordo com esse raciocínio, você não deve usar alerte undefined, porque algum outro código pode mexer com eles. O segundo exemplo é menos legível do que new Array(4)e não oferece o mesmo resultado: jsfiddle.net/73fKd
Alexey Lebedev
25
Esta resposta é bizarra
Marco Demaio 14/04
6
Você não pode evitar pessoas brincando com objetos globais em JavaScript; simplesmente não faça isso ou use estruturas onde as pessoas fazem isso.
Richard Connamacher
No chrome repl atualmente: j = new Array (4); comprimento; // resulta em 4
Parris