Skip to content

Instantly share code, notes, and snippets.

@monolithed
Forked from subzey/readme.md
Created June 2, 2014 08:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save monolithed/5af3c94eb00e02e28aed to your computer and use it in GitHub Desktop.
Save monolithed/5af3c94eb00e02e28aed to your computer and use it in GitHub Desktop.

Array.prototype.concat и UInt32

У массива в 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 у массива переполняется и становится равным нулю.

В трех случаях из четырех оптимизации кода делают поведение интерпретаторов неочевидным. Граждане, будьте осторожны при работе с очень большими массивами!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment