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

209

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

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

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

колокольчик-рапунцель
источник
Я не получаю те же результаты, которые вы видите из буквенной нотации массива. Я все еще получаю неопределенное значение вместо 0. Я получаю результат 0, только если я установил что-то вроде var y = x.map(function(){return 0; });, и я получаю это как для нового метода Array (), так и для литерала массива. Я тестировал в Firefox 4 и Chrome.
RussellUresti
также отключено в Chrome, это может быть определено в языке, хотя это не имеет смысла, поэтому я действительно надеюсь, что это не так
Hashbrown

Ответы:

125

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

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 = [,,,];

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

Давид Мортенссон
источник
86
Из MDC (выделено мной): " mapвызывает предоставленную функцию обратного вызова один раз для каждого элемента в массиве, по порядку и создает новый массив из результатов. callbackВызывается только для индексов массива, которые имеют присвоенные значения ; она не вызывается для индексов, которые были удалены или которым никогда не присваивались значения. " В этом случае xзначения не имеют явно назначенных значений, тогда как значения yбыли назначены, даже если это было значение undefined.
Мартин
2
Так ли это JavaScript, что невозможно проверить, является ли это неопределенным указателем или указателем на неопределенный? Я имею в виду (new Array(1))[0] === [undefined][0].
Тревор Норрис
Ну, массив undefined отличается от массива указателей на неопределенные объекты. Массив undefines был бы похож на массив нулевых значений, [null, null, null], а массив указателей на undefined был бы похож на [343423, 343424, 343425], указывающий на нуль, ноль и ноль. Вторые решения имеют реальные указатели, указывающие на адреса памяти, а первые никуда не указывают. Если это неудача JS, вероятно, вопрос обсуждения, но не здесь;)
David Mårtensson
4
@TrevNorris, вы можете легко это проверить, hasOwnPropertyесли только у вас hasOwnPropertyнет ошибки: (new Array(1)).hasOwnProperty(0) === falseи [undefined].hasOwnProperty(0) === true. На самом деле, вы можете сделать то же самое с in: 0 in [undefined] === trueи 0 in new Array(0) === false.
squid314
3
Разговор о «неопределенных указателях» в JavaScript сбивает с толку проблему. Термин, который вы ищете, это «выбор» . x = new Array(3);эквивалентно x = [,,,];, нет x = [undefined, undefined, undefined].
Мэтт Кантор
118

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

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

Обновить

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

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

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

cstuncsik
источник
28
Стоит отметить, что вам не нужно указывать undefinedв .fill()методе, слегка упрощая код доlet arr = new Array(10).fill().map((val,idx) => idx);
Янн Ив,
Точно так же вы можете использоватьArray.from(Array(10))
84

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

Мануэль Бодру
источник
9
Pre-ES6 вы можете использовать new Array(10).fill(). Тот же результат, что и[...Array(10)]
Molomby
С большими массивами синтаксис распространения создает проблемы, поэтому их лучше избегать
или[...Array(10).keys()]
Чунгзувалла
25

Решение ES6:

[...Array(10)]

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

Серж Интерн
источник
6
Array(10).fill("").map( ...это то, что работало для меня с Typescript 2.9
ibex
19

Массивы разные. Разница в том, что 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.

Тим Даун
источник
17

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

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

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

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

Джонатан Лоновски
источник
8

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

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

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

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

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

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

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
источник
Очень хорошее объяснение.
Джерадж Рао
7

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

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

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

Firefox просто «заполняет» эти пустые слоты, undefinedдаже если у него нет значений.

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

helloandre
источник
4

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

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

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

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

Vezquex
источник
3

Не ошибка Вот как конструктор 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 и числового аргумента.

Заостренный
источник
Это определено, чтобы быть сломанным? Он предназначен для создания массива из трех элементов, которые всегда будут undefinedнавсегда?
Гонки легкости на орбите
Да, это правильно, за исключением «вечной» части. Впоследствии вы можете присваивать значения элементам.
Заостренный
3
Вот почему вы должны использовать x = []вместоx = new Array()
Rocket Hazmat
3

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

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

Alex
источник
1

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

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

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

  • Неявно "undefined": Give 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], автоматически заполняя пробел, возвращая разницу между двумя значениями, объявленными явно.

БПС Жюльен
источник
1

По причинам, подробно изложенным в других ответах, 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

Иден Ландау
источник
0

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

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

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]
DJDaveMark
источник