Смутно поведением `map` на массивах, созданных с использованием` new`
Меня путают результаты map
ping над массивом, созданным с помощью new
:
function returnsFourteen() {
return 14;
}
var a = new Array(4);
> [undefined x 4] in Chrome, [, , , ,] in Firefox
a.map(returnsFourteen);
> [undefined x 4] in Chrome, [, , , ,] in Firefox
var b = [undefined, undefined, undefined, undefined];
> [undefined, undefined, undefined, undefined]
b.map(returnsFourteen);
> [14, 14, 14, 14]
Я ожидал, что a.map(returnsFourteen)
вернет [14, 14, 14, 14]
(то же самое, что и b.map(returnsFourteen)
, потому что согласно странице MDN на массивах:
Если единственный аргумент, переданный конструктору Array, является целым числом между 0 и 2 ** 32-1 (включительно), создается новый массив JavaScript с таким количеством элементов.
Я интерпретирую это как означающее, что a
должен иметь 4 элемента.
Что мне здесь не хватает?
Ответы
Ответ 1
Когда вы создаете такой массив:
var arr1 = new Array( 4 );
вы получаете массив с длиной 4
, но у него нет элементов. Поэтому map
не преобразовывает массив - массив не имеет элементов, которые нужно преобразовать.
С другой стороны, если вы это сделаете:
var arr2 = [ undefined, undefined, undefined, undefined ];
вы получаете и массив, который также имеет длину 4
, но имеет 4 элемента.
Обратите внимание на разницу между отсутствием элементов и наличием элементов, значения которых undefined
. К сожалению, выражение accessor свойства будет оценивать значение undefined
в обоих случаях, поэтому:
arr1[0] // undefined
arr2[0] // undefined
Однако существует способ дифференцировать эти два массива:
'0' in arr1 // false
'0' in arr2 // true
Ответ 2
var a = new Array(4);
Это определяет новый объект массива с явной длиной 4, но без элементов.
var b = [undefined, undefined, undefined, undefined];
Это определяет новый объект массива с неявной длиной 4, с 4 элементами, каждый со значением undefined
.
В docs:
callback вызывается только для индексов массива, которые назначены значения; он не вызывается для индексов, которые были удалены или которые никогда не были назначены значения.
Для массива a
нет элементов, которым были присвоены значения, поэтому он ничего не делает.
Для массива b
есть четыре элемента, которым были присвоены значения (да, undefined
- значение), поэтому он отображает все четыре элемента в число 14
.
Ответ 3
new Array(len)
создает пустой массив и выполняет что-то другое, чем заполнение его значениями undefined
: он устанавливает длину len
. Итак, он переводит на этот код:
var newArr = [];
newArr.length = len;
Давайте немного поиграем с newArr
(предположим, что len = 4
):
newArr.length; //4
newArr[1] === undefined; //true
newArr.hasOwnProperty(1); //false
Это связано с тем, что в то время как 4 элемента длинные, он не содержит ни одного из этих 4 элементов. Представьте себе пустой пуля-клип: у него есть место для, скажем, 20 пуль, но оно не содержит ни одного из них. Они даже не были установлены в значение undefined
, они просто... undefined (что немного запутывает.)
Теперь Array.prototype.map
счастливо ходит по вашему первому массиву, щебетает и свистнет, и каждый раз, когда он видит элемент массива, он вызывает функцию на нем. Но, когда он идет вдоль пустого клипа, он не видит пуль. Конечно, есть место для пуль, но это не делает их существовавшими. Здесь нет значения, потому что ключ, который сопоставляется с этим значением, не существует.
Для второго массива, заполненного значениями undefined
, значение равно undefined
, и это также ключ. Внутри b[1]
или b[3]
есть что-то, но что-то не определено; но Array.prototype.map
не волнует, он будет работать с любым значением, если у него есть ключ.
Для дальнейшего осмотра в спецификации:
Ответ 4
Еще один ответ на поведение console.log
. Простой, выход сахара и технически неправильный.
Давайте рассмотрим этот пример:
var foo = new Array(4),
bar = [undefined, undefined, undefined, undefined];
console.log( Object.getOwnPropertyNames(bar) );
console.log( Object.getOwnPropertyNames(foo) );
Как мы видим в результате, функция .getOwnPropertyNames
возвращает
["length"]
для массива foo
Array/Object, но
["length", "0", "1", "2", "3"]
для массива bar
Array/Object.
Таким образом, console.log
просто обманывает вас при выводе Arrays
, у которого есть только определенный .length
, но нет реальных назначений свойств.