У массива в javascript есть замечательный метод .concat
. Как и следует из названия, он объединяет два (или более) массива в один. Если в качестве аргумента передан массив, в результрующий массив попадут его значения; если не массив, то сам аргумент. Если кратко, то вот этот кусок кода:
[1, 2, 3].concat(4, [5, 6], [7]); // [1,2,3,4,5,6,7]
А что будет, если длина получившегося массива будет равна или превышать 232? Чтобы ответить на этот вопрос обратимся к спецификации. А написано там следующее:
- Массив — это объект. И у него, как у объекта, есть свойства. И каждое свойство имеет свое имя и значение.
- Некоторые из свойств массива — элементы. Свойство массива является элементом, если его имя представляет собой целое число от нуля до 232−2 включительно. Таким образом, у любого массива может быть сколько угодно свойств, но элементов может быть не более 232−1.
- Если свойство массива является элементом, то имя этого свойства называется также индексом (или ключом).
- У массива есть свойство
length
, значение которого всегда больше, чем самый большой индекс. Значение свойстваlength
не может быть больше 232−1.
Теперь, как же работает метод .concat:
- Создаем новый массив
A
нулевой длины и числоn
, равное нулю. - Для
this
и всех аргументов проделываем следующее: если это массив, то копируем все его свойства с именами от нуля доlength - 1
в массивA
под новыми именами, равнымиn
, увеличиваяn
на единицу после каждого копирования; если же это не массив, то копируем само значение под именемn
, также увеличиваяn
на единицу.
В качестве кода это можно представить так:
Array.prototype.concat = function(){
"use strict";
var n = 0,
newArray = [],
objectToCopy,
i,
j;
for (i = -1; i < arguments.length; i++){
if (i < 0 ){
if (this == null){
throw new TypeError("Cannot convert undefined or null to Object");
}
objectToCopy = new Object(this);
} else {
objectToCopy = arguments[i];
}
if (Array.isArray(objectToCopy)){
for (j = 0; j < objectToCopy.length; j++){
newArray[n] = objectToCopy[j];
n++;
}
} else {
newArray[n] = objectToCopy;
n++;
}
}
return newArray;
};
Обратите внимание, значение переменной n
не ограничивается ничем.
Итак, что же мы получим, если к массив максимально возможной длины
var a = Array(0xFFFFFFFF);
попытаемся добавить еще одно значение через метод .concat
var b = a.concat(true);
Ответ: новый массив с установленными свойствами от 0 до 232−1. Причем свойства с именами от 0 до 232−2 будут элементами, а свойство с именем 232−1 (0xFFFFFFFF) — нет, и оно будет равняться true
. Свойство length
получившегося массива будет равняться 232−1.
Это в теории. А на практике? На практике получаем следующее:
b.length | b[0xFFFFFFFF] | |
---|---|---|
Ожидается | 4294967295 | true |
Firefox 10 | 0 | true |
Opera 11.61 | 0 | undefined |
Chrome 17 | 4294967295 | undefined |
Node.js 0.6.12 | 4294967295 | undefined |
IE 9 | 4294967295 | true |
Internet Explorer 9 справился с тестом более, чем достойно. И это уже не первый случай, когда Microsoft удивляет филигранным подходом к узким местам ES.
V8 устанавливает массиву только элементы, игнорируя свойства, которые элементами не являются.
У Firefox и Opera свойство length у массива переполняется и становится равным нулю.
В трех случаях из четырех оптимизации кода делают поведение интерпретаторов неочевидным. Граждане, будьте осторожны при работе с очень большими массивами!