JavaScript "new Array(n)" и "Array.prototype.map" странность

Я наблюдал это в Firefox-3.5.7/Firebug-1.5.3 и Firefox-3.6.16/Firebug-1.6.2

Когда я запускаю 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]

Что тут происходит? Это ошибка, или я неправильно понимаю, как использовать new Array(3)?

13 ответов

Решение

Похоже, что первый пример

x = new Array(3);

Создает массив с неопределенными указателями.

А вторая создает массив с указателями на 3 неопределенных объекта, в этом случае сами указатели НЕ являются неопределенными, только те объекты, на которые они указывают.

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

Поскольку карта запускается в контексте объектов в массиве, я считаю, что первая карта вообще не запускает функцию, в то время как вторая удается запустить.

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

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

Чтобы быстро создать массив следующим образом:

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

Но это не сработало, потому что: см. Ответ Джонатана Лоновски на несколько ответов выше.

Решением может быть заполнение элементов массива любым значением (даже неопределенным) с помощью 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));

Обновить

Другое решение может быть:

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

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

С ES6 вы можете сделать [...Array(10)].map((a, b) => a), быстро и просто!

Решение ES6:

[...Array(10)]

Не работает на машинописи (2.3), хотя

Со страницы MDC для map:

[...] callback вызывается только для индексов массива, которым присвоено значение; [...]

[undefined] на самом деле применяет установщик на индекс (ы), так что map будет повторяться, тогда как new Array(1) просто инициализирует индекс (ы) со значением по умолчанию undefined так map пропускает это

Я считаю, что это одинаково для всех итерационных методов.

По причинам, подробно объясненным в других ответах, Array(n).mapне работает. Однако в ES2015 Array.from принимает функцию карты:

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

Массивы разные. Разница в том, что new Array(3) создает массив длиной три, но без свойств, в то время как [undefined, undefined, undefined] создает массив длиной три и три свойства с именами "0", "1" и "2", каждое со значением undefined, Вы можете увидеть разницу, используя in оператор:

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

Это вытекает из слегка запутанного факта, что если вы попытаетесь получить значение несуществующего свойства любого нативного объекта в JavaScript, он возвращает undefined (вместо того, чтобы выдавать ошибку, как это происходит, когда вы пытаетесь сослаться на несуществующую переменную), что совпадает с тем, что вы получаете, если ранее для свойства было явно установлено значение undefined,

В спецификации ECMAScript 6-е издание.

new Array(3) только определить свойство length и не определяйте свойства индекса, такие как {length: 3}, см. https://www.ecma-international.org/ecma-262/6.0/index.html Шаг 9.

[undefined, undefined, undefined] определит свойства индекса и свойства длины как {0: undefined, 1: undefined, 2: undefined, length: 3}, см. https://www.ecma-international.org/ecma-262/6.0/index.html ElementList Шаг 5

методы map, every, some, forEach, slice, reduce, reduceRight, filter Array проверит свойство индекса HasProperty внутренний метод, так new Array(3).map(v => 1) не будет вызывать обратный вызов.

для получения более подробной информации см. https://www.ecma-international.org/ecma-262/6.0/index.html.

Как исправить?

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

Я думаю, что лучший способ объяснить это - посмотреть, как Chrome справляется с этим.

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

Так что на самом деле происходит то, что new Array() возвращает пустой массив, имеющий длину 3, но без значений. Поэтому, когда вы запускаете x.map в технически пустом массиве нечего настраивать.

Firefox просто "заполняет" эти пустые слоты undefined хотя у него нет ценностей.

Я не думаю, что это явно ошибка, просто плохой способ представить, что происходит. Я полагаю, что Chrome "более правильный", потому что он показывает, что в массиве на самом деле ничего нет.

Не ошибка Вот как конструктор Array определен для работы.

От MDC:

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

var billingMethod = new Array(5);

Поведение конструктора Array зависит от того, является ли один параметр числом.

.map() Метод включает в итерацию только те элементы массива, которым явно присвоены значения. Даже явное назначение undefined приведет к тому, что значение будет считаться подходящим для включения в итерацию. Это кажется странным, но по сути это разница между явным undefined свойство объекта и отсутствующее свойство:

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

Предмет x не имеет свойства с именем "z", а объект y делает. Тем не менее, в обоих случаях представляется, что "значение" свойства undefined, В массиве ситуация аналогична: значение length неявно выполняет присваивание значения всем элементам от нуля до length - 1, .map() Поэтому функция не будет ничего делать (не будет вызывать обратный вызов) при вызове массива, вновь созданного с помощью конструктора Array и числового аргумента.

Просто столкнулся с этим. Конечно, было бы удобно иметь возможность использовать Array(n).map,

Array(3) дает примерно {length: 3}

[undefined, undefined, undefined] создает пронумерованные свойства:
{0: undefined, 1: undefined, 2: undefined, length: 3},

Реализация map() действует только на определенные свойства.

Если вы делаете это для того, чтобы легко заполнить массив значениями, не можете использовать заливку из соображений поддержки браузера и действительно не хотите делать цикл for, вы также можете сделать x = new Array(3).join(".").split(".").map(... который даст вам массив пустых строк.

Я должен сказать, что это ужасно, но, по крайней мере, проблема и намерение достаточно четко изложены.

Поскольку вопрос в том, почему, это связано с тем, как был разработан JS.

Есть две основные причины, по которым я могу объяснить такое поведение:

  • Производительность: Учитывая x = 10000 а также new Array(x) конструктору рекомендуется избегать цикла от 0 до 10000, чтобы заполнить массив undefined ценности.

  • Неявно "undefined": Дайте a = [undefined, undefined] а также b = new Array(2), a[1] а также b[1] оба вернутся undefined, но a[8] а также b[8] также вернется undefined даже если они вне досягаемости.

В конечном итоге обозначение empty x 3 это ярлык, позволяющий избежать установки и отображения длинного списка undefined ценности, которые undefined в любом случае, потому что они не объявлены явно.

Примечание: данный массив a = [0] а также a[9] = 9, console.log(a) вернусь (10) [0, empty x 8, 9], автоматически заполняя пробел, возвращая разницу между двумя явно объявленными значениями.

Вот простой метод утилиты в качестве обходного пути:

Простая карта для

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]

Полный пример

Вот более полный пример (с проверками работоспособности), который также позволяет указать необязательный начальный индекс:

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]

Отсчет

Управление индексом, переданным обратному вызову, позволяет считать в обратном направлении:

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

В Chrome, если я сделаю new Array(3) я получил [], так что я думаю, что вы столкнулись с ошибкой браузера.

Другие вопросы по тегам