Как инициализировать длину массива в JavaScript?

542

Большинство руководств, которые я читал по массивам в JavaScript (включая w3schools и devguru ), предполагают, что вы можете инициализировать массив определенной длины, передавая целое число в конструктор Array, используя var test = new Array(4);синтаксис.

После широкого использования этого синтаксиса в моих файлах js я запустил один из файлов через jsLint , и он пришел в ужас :

Ошибка: проблема в строке 1, символ 22: ожидаемый «)», вместо этого «4».
var test = new Array (4);
Проблема в строке 1, символ 23: ожидается ';' и вместо этого увидел ')'.
var test = new Array (4);
Проблема в строке 1, символ 23: ожидал идентификатор и вместо этого увидел ')'.

После прочтения объяснения jsLint о его поведении , похоже, что jsLint не очень нравится new Array()синтаксис, а вместо этого предпочитает []при объявлении массивов.

Итак, у меня есть пара вопросов:

Во-первых, почему? Я рискую, используя new Array()вместо этого синтаксис? Существуют ли несовместимости браузеров, о которых мне следует знать?

И во-вторых, если я переключусь на синтаксис в квадратных скобках, есть ли способ объявить массив и задать его длину в одной строке, или я должен сделать что-то вроде этого:

var test = [];
test.length = 4;
Майкл Мартин-Смукер
источник
стандартные JS также советуют против использованияnew Array()в целом, но это нормальнос указанием размера. Я думаю, что все сводится к согласованности кода во всем контексте.
Cregox
Для тех, кто хочет предварительно выделить более жесткие массивы, есть Typed Arrays . Обратите внимание, что выигрыш в производительности может отличаться
rovyko

Ответы:

397
  1. Почему вы хотите инициализировать длину? Теоретически в этом нет необходимости. Это может даже привести к сбивающему с толку поведению, потому что все тесты, которые используют, lengthчтобы выяснить, является ли массив пустым, сообщат, что массив не пуст.
    Некоторые тесты показывают, что установка начальной длины больших массивов может быть более эффективной, если массив заполняется впоследствии, но прирост производительности (если таковой имеется), кажется, отличается от браузера к браузеру.

  2. jsLint не нравится, new Array()потому что конструктор неоднозначен.

    new Array(4);

    создает пустой массив длиной 4. Но

    new Array('4');

    создает массив, содержащий значение '4' .

Что касается вашего комментария: в JS вам не нужно инициализировать длину массива. Динамично растет. Вы можете просто сохранить длину в некоторой переменной, например

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

for(var i = 0; i < length; i++) {
    data.push(createSomeObject());
}
Феликс Клинг
источник
13
Количество объектов в массиве определяется пользователем, поэтому я позволил пользователю выбрать число, а затем инициализировал массив с таким количеством слотов. Затем я запускаю forцикл, который перебирает всю длину массива и заполняет его. Я предполагаю, что были бы другие способы сделать это в JavaScript, поэтому реальный ответ на вопрос «Почему я хочу это сделать?» «Из-за старых привычек, которые сформировались при программировании на других языках». :)
Майкл Мартин-Смукер
4
@mlms Просто позвольте циклу for выполнять итерацию по определенному пользователем номеру вместо того, чтобы устанавливать длину массива, а затем выполнять итерацию по нему. Разве это не имеет больше смысла для вас?
Шиме Видас
151
<blockquote> Почему вы хотите инициализировать длину? Теоретически в этом нет необходимости. И все тесты, которые используют длину, чтобы выяснить, является ли массив пустым или нет, потерпят неудачу. </ Blockquote> Хм, производительность может быть? Быстрее установить ранее существующий элемент массива, чем добавить его на лету.
codehead
45
«время отказаться от старых привычек программирования» действительно не нужно. правильно структурированные языки всегда будут превосходить ecmascript mombo jambo
user151496
10
@codehead: в ответ на эту цитату: «Устанавливать ранее существующий элемент массива быстрее, чем добавлять его на лету.»: обратите внимание, что new Array(10)не создается массив с 10 неопределенными элементами. Он просто создает пустой массив длиной 10. Посмотрите этот ответ для противной правды: stackoverflow.com/questions/18947892/…
Milimetric
599
  • Array(5) дает вам массив с длиной 5, но без значений, поэтому вы не можете перебирать его.

  • Array.apply(null, Array(5)).map(function () {}) дает вам массив длиной 5 и неопределенный как значения, теперь он может быть повторен.

  • Array.apply(null, Array(5)).map(function (x, i) { return i; }) выдает массив длиной 5 и значениями 0,1,2,3,4.

  • Array(5).forEach(alert)ничего не делает, Array.apply(null, Array(5)).forEach(alert)дает 5 предупреждений

  • ES6дает нам, Array.fromтак что теперь вы также можете использоватьArray.from(Array(5)).forEach(alert)

  • Если вы хотите инициализировать с определенным значением, это хорошо знает ...
    Array.from('abcde'), Array.from('x'.repeat(5))
    илиArray.from({length: 5}, (v, i) => i) // gives [0, 1, 2, 3, 4]

Рубен Столк
источник
11
Это то, что я искал. Я хотел применить карту над логической последовательностью; это должно сделать это. Спасибо!
jedd.ahyoung
3
почему Array.apply () дает ему неопределенное значение?
wdanxna
5
@wdanxna, когда Arrayему дается несколько аргументов, он перебирает argumentsобъект и явно применяет каждое значение к новому массиву. Когда вы вызываете Array.applyс массивом или объектом со свойством length, Arrayсобираетесь использовать длину, чтобы явно установить каждое значение нового массива. Вот почему Array(5)дает массив из 5 значений, а Array.apply(null, Array(5))массив из 5 неопределенных. Для получения дополнительной информации см. Этот ответ.
KylePlusPlus
2
Есть ли разница между массивом (5) и новым массивом (5)?
Петр Хуртак
2
Array.apply(null, Array(5)) Также можно записать в виде Array.apply(null, {length: 5}). Разницы на самом деле не так много, но последнее однозначно ясно, что цель состоит в том, чтобы создать массив length 5, а не массив, содержащий5
AlexMorley-Finch
272

С ES2015.fill() вы можете теперь просто сделать:

// `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)

Который намного лаконичнее, чем Array.apply(0, new Array(n)).map(i => value)

Можно опускать 0в .fill()и запустить без аргументов, которые будут заполнять массив с undefined. ( Тем не менее, это потерпит неудачу в Typescript )

AP.
источник
1
@AralRoca Вы всегда можете использовать Polyfill, предоставленный на странице MDN, или рассмотреть возможность использования Modernizr .
AP.
4
Да, пожалуйста, попробуйте, прежде чем комментировать
AP.
1
@GarretWilson и @Christian Это в спецификации . 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., Ноль избыточен. DoArray(n).fill()
Pacerier
2
@Pacerier Я не думаю, что 0 был излишним. Согласно определению типа es6, это сигнатура функции: fill (значение: T, начало ?: число, конец ?: число): this; Следовательно, значение заполнения не кажется необязательным. Компиляция Typescript не удастся, так как вызов функции написан выше.
Миха Шваб,
152
[...Array(6)].map(x => 0);
// [0, 0, 0, 0, 0, 0]

ИЛИ

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

Примечание: вы не можете зациклить пустые слоты, т.е. Array(4).forEach(() => …)


ИЛИ

( машинописный сейф )

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

ИЛИ

Классический метод с использованием функции (работает в любом браузере)

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]

Создание вложенных массивов

При создании 2D массива fillследует интуитивно создавать новые экземпляры. Но то, что на самом деле произойдет, это тот же массив, который будет храниться в качестве ссылки.

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

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

Решение

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

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

Таким образом, массив 3х2 будет выглядеть примерно так:

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

N-мерный массив

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

Инициализировать шахматную доску

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] ]
Влад
источник
1
a[0].push(9); // [ [6, 9], [6], [6] ] должно быть a[0].push(9); // [ [6, 9], [6, 9], [6, 9] ] потому, что каждый вложенный массив хранится как ссылка, поэтому изменение одного массива влияет на остальные. Вы, возможно, только что опечатали это, хотя я думаю :)
Alex_Zhong
@Alex_Zhong Tru, что, спасибо, потерял это в редактировании
Влад
68

Кратчайший:

[...Array(1000)]
Майкл Маммолити
источник
11
Выраженный голосом, но одно предостережение, вы не должны начинать строку в JS с a [без использования, ;так как она будет пытаться разыменовать строку выше. Поэтому безопасным способом предсказуемо использовать эту строку в любой части кода будет:;[...Array(1000)]//.whateverOpsNeeded()
AP.
на самом деле, нет. попробуйте в инструментах разработчика. [...Array(5)].map((item, index) => ({ index })) попробуйте и это:[...Array(5)]; console.log('hello');
Майкл Маммолити
2
Попробуйте использовать его в многострочном js-файле. Если ваша первая строка const a = 'a'и следующая строка [...Array(5)].//irrelevent. Как вы думаете, к чему приведет первая строка? Это будет, const a = 'a'[...Array(5)]что приведет к:Uncaught SyntaxError: Unexpected token ...
AP.
7
Это правда, но в этом случае я бы сказал, что проблема где-то еще, точки с запятой и линтинг важны в наши дни.
Майкл Маммолити
8
@AP именно этот пример является именно той причиной, по которой многие руководства по стилю обязывают заканчивать каждое утверждение точкой с запятой. const a = 'a'будет ошибка Lint в этом случае. Во всяком случае, это действительно не имеет ничего общего с этим ответом.
выпускной
41

ES6 представляет, Array.fromкоторый позволяет вам создавать Arrayиз любых «подобных массиву» или итерируемых объектов:

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

В этом случае {length: 10}представляет собой минимальное определение «подобного массиву» объекта: пустой объект с только определенным lengthсвойством.

Array.from позволяет использовать второй аргумент для отображения результирующего массива.

Александр Шутау
источник
2
@dain Я думаю [... Array (10)] лучше stackoverflow.com/a/47929322/4137472
Александр Шутау
1
Почему лучше? Использование конструктора Array не рекомендуется, и некоторые из них могут также жаловаться
Cristian Traìna
26

Это инициализирует свойство длины 4:

var x = [,,,,];
Шиме Видас
источник
3
Это умно. У него нет гибкости конструктора, потому что вы не можете использовать переменную для установки длины, но он отвечает на мой вопрос, как я его первоначально сформулировал, так что +1.
Майкл Мартин-Смукер
12
Представьте, что нужно делать 300 вещей, когда производительность действительно имеет значение!
Марко Луглио
9
Вероятно, не так много прироста производительности при предварительном распределении массива с таким маленьким размером. Разница в производительности будет лучше восприниматься при создании больших массивов. И в этих случаях инициализация их запятыми была бы немного подавляющей.
Марко Луглио
6
Я думаю , что это будет давать разные результаты в разных браузерах из - за окончательной запятой, иногда 4, иногда 5 см stackoverflow.com/questions/7246618/...
jperelli
1
Как насчет var size = 42; var myArray = eval("["+",".repeat(size)+"]");? (Не так серьезно;)
xoxox
15

Я удивлен, что не было предложено функционального решения, позволяющего установить длину в одну строку. Следующее основано на UnderscoreJS:

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

По причинам, указанным выше, я бы избегал этого, если бы я не хотел инициализировать массив определенным значением. Интересно отметить, что есть другие библиотеки, которые реализуют диапазон, включая Lo-dash и Lazy, которые могут иметь разные характеристики производительности.

christang
источник
Подчеркивание черной магии здесь действительно не нужно. Зависимости похожи на сахар, поначалу они кажутся сладкими, но, прежде чем вы это узнаете, у вас диабет.
Ромен Винсент
9

Пожалуйста, люди пока не отказываются от ваших старых привычек. Существует большая разница в скорости между выделением памяти один раз, затем работой с записями в этом массиве (как в старом), и распределением ее много раз по мере роста массива (что неизбежно происходит в системе с помощью других предлагаемых методов). ,

Конечно, все это не имеет значения, пока вы не захотите сделать что-нибудь классное с большими массивами. Тогда это так.

Поскольку в данный момент в JS все еще нет возможности установить начальную емкость массива, я использую следующее ...

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);
}

Есть компромиссы:

  • Этот метод занимает столько же времени, сколько и другие для первого вызова функции, но очень мало времени для последующих вызовов (если только не запрашивается больший массив).
  • standardМассив имеет постоянно Оставляет столько пространства , как самый большой массив вы просили.

Но если это согласуется с тем, что вы делаете, это может окупиться. Неофициальные сроки ставят

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

довольно быстро (около 50 мс для 10000, учитывая, что при n = 1000000 это заняло около 5 секунд), и

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

более чем за минуту (около 90 секунд для 10000 на той же хромированной консоли или примерно в 2000 раз медленнее). Это будет не только распределение, но и 10000 нажатий на цикл и т. Д.

Wils
источник
если каждый раз, когда вы достигаете конца массива, вы дублируете его, сложность вставки n значений по-прежнему равна O (n), поэтому в сложности нет разницы (но она медленнее).
Томер Вольберг,
Хорошо поймали @TomerWolberg, я изменил свою формулировку. Вопрос в том, имеет ли метод push более высокую сложность, чем инициализация / копирование отдельного элемента по слайсу. AFAIK, что они оба постоянное время, по крайней мере, они могли бы быть, поэтому я вместо этого использовал слово «скорость».
WILS
8

(это было вероятно лучше как комментарий, но получилось слишком долго)

Итак, после прочтения этого мне было любопытно, было ли предварительное распределение на самом деле быстрее, потому что в теории это должно быть. Тем не менее, этот блог дал несколько советов, советовавших против этого http://www.html5rocks.com/en/tutorials/speed/v8/ .

Так что все еще будучи неуверенным, я проверил это. И, как оказалось, на самом деле все идет медленнее.

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

Этот код выдает следующее после нескольких случайных запусков:

$ 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
источник
3
Интересно, что это казалось неожиданным, поэтому я сделал тест JSPerf . Результаты Chrome соответствуют вашим тестам Node, но Firefox и IE немного быстрее, когда вы предварительно выделяете пространство для массива.
Майкл Мартин-Смукер
1
@ MichaelMartin-Smucker Подождите ... это означает, что V8 на самом деле не полностью оптимизирован и совершенен?!?!? : P
Зеб МакКоркл
1
@ MichaelMartin-Smucker - я только что запустил ваш jsperf в Chrome версии 42.0.2311.90 (точнее, 32-битное тестирование в Chrome 42.0.2311.90 на Windows Server 2008 R2 / 7), а динамический размер массива был на 69% медленнее ,
Йеллен
1
То же самое. (Я написал оригинальный тест узла) В chrome 42.0.2311.90 на окнах динамический размер был на 81% медленнее :). Но наши оригинальные тесты были более года назад. Время держится на
скользком, скользком
1
Захватывающе ... из результатов на jsperf похоже, что предварительно выделенный получил огромный импульс в Chrome 38. Будущее!
Майкл Мартин-Смукер
8
var arr=[];
arr[5]=0;
alert("length="+arr.length); // gives 6
user1067305
источник
2
Да, однако console.log (arr) дает [5: 0]это редкий массив и, вероятно, не то, что нужно.
dreftymac
7

Вот еще одно решение

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

Первый аргумент apply()- это привязка объекта, о которой нам здесь наплевать, поэтому мы установили его на null.

Array.apply(..)вызывает Array(..)функцию и распространяет { length: 3 }значение объекта в качестве аргументов.

zangw
источник
Я не понимаю, почему этот ответ получил отрицательную оценку. На самом деле это хороший хак. Это означает, что вы не можете использовать new Array(n)для инициализации массива, как это new Array(n).map(function(notUsed,index){...}), но с этим подходом, как упомянул @zangw, вы можете сделать это. +1 от меня.
Victor.Palyvoda
3

Конструктор массива имеет неоднозначный синтаксис , и JSLint в конце концов только ранит ваши чувства.

Кроме того, ваш пример кода не работает, второй varоператор вызовет a SyntaxError. Вы устанавливаете свойство lengthмассива test, поэтому нет необходимости в другомvar .

Что касается ваших возможностей, array.lengthэто единственный «чистый» вариант . Вопрос в том, зачем вам устанавливать размер в первую очередь? Попробуйте изменить код, чтобы избавиться от этой зависимости.

Иво Ветцель
источник
Woops, хорошо следите за этой секундой var test. Это было неаккуратное копирование и вставка с моей стороны.
Майкл Мартин-Смукер
@IvoWetzel, вы хотите установить размер для повышения производительности. JS-массивы - это динамические массивы . Если вы не установите размер (точнее, его емкость), JS выделит массив размера по умолчанию. Если затем вы добавите больше элементов, чем можете уместить, он должен будет увеличить массив, что означает, что он внутренне выделит новый массив, а затем скопирует все элементы.
Доми
3

Предполагая, что длина массива постоянна. В Javascript это то, что мы делаем:

const intialArray = new Array(specify the value);

мохан бабу
источник
2

Как объяснено выше, использование new Array(size)несколько опасно. Вместо непосредственного использования поместите его в «функцию создания массива». Вы можете легко убедиться, что эта функция не содержит ошибок, и вы избежите опасности new Array(size)прямого вызова . Кроме того, вы можете задать ему необязательное начальное значение по умолчанию. Эта createArrayфункция делает именно это:

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;
}
Доми
источник
1

В большинстве ответов это рекомендуется для fillмассива, потому что в противном случае «вы не можете итерировать по нему» , но это не так. Вы можете перебрать пустой массив, только не с forEach. Циклы while, для циклов и для циклов i работают нормально.

const count = Array(5);

Не работает.

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

Эти работы:

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++; 
}

Проверьте эту скрипку с приведенными выше примерами.

Также angulars *ngForотлично работает с пустым массивом:

<li *ngFor="let empty of count; let i = index" [ngClass]="
  <span>Counting with *ngFor {{i}}</span>
</li>
Уилт
источник
-1

Вы можете установить длину массива, используя array.length = youValue

Так было бы

var myArray = [];
myArray.length = yourValue;
ajoposor
источник
-3

Причина, по которой вы не должны использовать new Array, демонстрируется этим кодом:

var Array = function () {};

var x = new Array(4);

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

Какой-то другой код может связываться с переменной Array. Я знаю, что это немного надумано, что кто-нибудь написал бы такой код, но все же ...

Кроме того, как сказал Феликс Кинг, интерфейс немного непоследователен и может привести к некоторым очень трудным для отслеживания ошибкам.

Если вам нужен массив с длиной = x, заполненный неопределенным (как это new Array(x)будет сделано), вы можете сделать это:

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

alert(myArray.length); // 4
nickf
источник
48
В соответствии с этим рассуждением вы не должны использовать alertи undefined, потому что какой-то другой код может связываться с ними. Второй пример менее читабелен, чем new Array(4)и не дает того же результата: jsfiddle.net/73fKd
Алексей Лебедев
25
Этот ответ странный
Марко Демайо
6
Вы не можете избежать людей, возиться с глобальными объектами в JavaScript; просто не делайте этого или используйте фреймворки, в которых люди это делают.
Ричард Коннахахер
В настоящее время в Chrome Repl: J = новый массив (4); j.length; // результаты в 4
Parris