Ответ 1
В игру вступают всевозможные факторы, в большинстве реализаций JS используется плоский массив, который преобразуется в разреженное хранилище, если это понадобится позже.
В основном решение стать разреженным является эвристикой, основанной на том, какие элементы устанавливаются, и сколько пространства будет потрачено впустую, чтобы оставаться плоской.
В вашем случае вы сначала устанавливаете последний элемент, что означает, что движок JS увидит массив, длина которого должна быть n
, а только один элемент. Если n
достаточно велико, это немедленно сделает массив редким массивом - в большинстве движков это означает, что все последующие вставки будут занимать случай медленного разреженного массива.
Вы должны добавить дополнительный тест, в котором вы заполняете массив от индекса 0 до индекса n-1 - он должен быть намного быстрее.
В ответ на @Christoph и из желания затягивать, здесь описание того, как массивы (как правило) реализованы в JS-специфике, варьируются от JS-движка до JS-движка, но общий принцип тот же.
Все JS Object
(а не строки, числа, true, false, undefined
или null
) наследуются от базового типа объекта - точная реализация меняется, это может быть наследование С++ или вручную C (есть преимущества для этого в любом случае) - базовый тип объекта определяет методы доступа по умолчанию по умолчанию, например.
interface Object {
put(propertyName, value)
get(propertyName)
private:
map properties; // a map (tree, hash table, whatever) from propertyName to value
}
Этот тип объекта обрабатывает всю стандартную логику доступа к объекту, цепочку прототипов и т.д. Тогда реализация массива станет
interface Array : Object {
override put(propertyName, value)
override get(propertyName)
private:
map sparseStorage; // a map between integer indices and values
value[] flatStorage; // basically a native array of values with a 1:1
// correspondance between JS index and storage index
value length; // The `length` of the js array
}
Теперь, когда вы создаете Array в JS, движок создает нечто похожее на вышеуказанную структуру данных. Когда вы вставляете объект в экземпляр Array, метод Array put проверяет, является ли имя свойства целочисленным (или может быть преобразовано в целое число, например "121", "2341" и т.д.) Между 0 и 2 ^ 32 -1 (или, возможно, 2 ^ 31-1, я точно забыл). Если это не так, то метод put пересылается в базовую реализацию Object, а стандартная логика [[Put]] выполняется. В противном случае значение будет помещено в собственное хранилище Array, если данные будут достаточно компактными, тогда двигатель будет использовать хранилище с плоскими массивами, и в этом случае вставка (и извлечение) будет просто стандартной операции индексации массива, в противном случае движок преобразует массив к разреженному хранилищу и поместите/получите карту, чтобы получить от свойстваName значение location.
Я честно не уверен, что какой-либо движок JS теперь конвертируется из разреженного в плоское хранилище после этого преобразования.
Anyhoo, это довольно высокий уровень обзора того, что происходит, и не учитывает ряд более неприятных деталей, но общий шаблон реализации. Специфика того, как отправлено дополнительное хранилище, и как put/get отправляется, отличается от движка к движку - но это яснее, я могу действительно описать дизайн/реализацию.
Небольшая добавочная точка, в то время как спецификация ES ссылается на propertyName
, поскольку строковые JS-двигатели имеют тенденцию специализироваться на поиске по целому числу, поэтому someObject[someInteger]
не будет преобразовывать целое число в строку, если вы смотрите на объект, который имеет целочисленные свойства, например. Array, String и DOM (NodeList
s и т.д.).