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)
я получил []
, так что я думаю, что вы столкнулись с ошибкой браузера.