An arbitrary string can be used as a property name for a JavaScript object. But for some special subsets of names, it makes sense to do special optimizations in JavaScript engines. One such case is numeric array indices .
Although in most cases these properties behave indistinguishable from any other, the V8 engine, for optimization purposes, stores them separately from the rest and processes them in a special way. Inside V8 such properties are called elements ( elements ) of the object. Pretty logical: objects have properties that are accessible by name, and arrays have elements that are accessible by index.
Basic element grades
During JavaScript code execution, V8 keeps track of the sort of elements of each array - which elements it stores. This information allows the engine to better optimize array operations. For example, built-in functions like map
, reduce
or are forEach
specialized for each kind of element.
Consider, for example, an array like this:
const array = [1, 2, 3];
What elements does it contain? From the operator's point of view, typeof
everything is simple - they are type elements number
. And this is all that can be said about them from within JavaScript: the language does not distinguish int
, float
and double
. However, there are differences at the engine level. The sort of elements of this array is PACKED_SMI _ELEMENTS
. In V8 terms, SMI is a special format for storing small integers. Which means PACKED
, we'll look at a bit later.
Adding a fractional number to an array makes its elements more general:
const array = [1, 2, 3];
// : PACKED_SMI_ELEMENTS
array.push(4.56);
// : PACKED_DOUBLE_ELEMENTS
Adding a line there makes the sort of elements even more general:
const array = [1, 2, 3];
// : PACKED_SMI_ELEMENTS
array.push(4.56);
// : PACKED_DOUBLE_ELEMENTS
array.push('x');
// : PACKED_ELEMENTS
There are three main sorts of elements in this code:
SMI_ELEMENTS
- for small integersDOUBLE_ELEMENTS
- for floating point numbers and integers too large forSMI
ELEMENTS
β ,SMI
DOUBLE
, . , PACKED_ELEMENTS
PACKED_DOUBLE_ELEMENTS
.
:
- V8 .
- β , .
- .
PACKED
HOLEY
(dense), (packed), . "" ( , ) (sparse), "" (holey):
const array = [1, 2, 3, 4.56, 'x'];
// : PACKED_ELEMENTS
array.length; // 5
array[9] = 1; // array[5] array[9]
// : HOLEY_ELEMENTS
V8 , . , , .
(SMI_ELEMENTS
, DOUBLE_ELEMENTS
ELEMENTS
) (PACKED
), (HOLEY
), . , PACKED_SMI_ELEMENTS
PACKED_DOUBLE_ELEMENTS
, HOLEY_SMI_ELEMENTS
.
:
- (
PACKED
), (HOLEY
). - , .
-
PACKED
-HOLEY
- ( ).
. , DOUBLE
. , DOUBLE
. , , - HOLEY
, PACKED
.
, , . , . .
, . , V8 , .
, , . , , . , , array[42]
, array.length === 5
. 42
, , . , V8 , , , .
, :
for (let i = 0, item; (item = items[i]) != null; i++) {
doSomething(item);
}
items[items.length]
, .
:
for (let index = 0; index < items.length; index++) {
const item = items[index];
doSomething(item);
}
, items
β iterable- ( , ), for-of
:
for (const item of items) {
doSomething(item);
}
forEach
:
items.forEach((item) => {
doSomething(item);
});
, for-of
forEach
for
.
, ! :
function maximum(array) {
let max = 0;
for (let i = 0; i <= array.length; i++) { //
if (array[i] > max) max = array[i];
}
return max;
}
array[array.length]
, , : , , V8 undefined
. , .
, , , V8 .
, . , -0
PACKED_DOUBLE_ELEMENTS
.
const array = [3, 2, 1, +0];
// PACKED_SMI_ELEMENTS
array.push(-0);
// PACKED_DOUBLE_ELEMENTS
, , .
-0
, -0
+0
(, , ).
NaN
Infinity
. , , SMI_ELEMENTS
DOUBLE_ELEMENTS
.
const array = [3, 2, 1];
// PACKED_SMI_ELEMENTS
array.push(NaN, Infinity);
// PACKED_DOUBLE_ELEMENTS
, . , PACKED_SMI_ELEMENTS
, .
array-like objects
JavaScript, β , DOM API β , . " " (array-like) :
const arrayLike = {};
arrayLike[0] = 'a';
arrayLike[1] = 'b';
arrayLike[2] = 'c';
arrayLike.length = 3;
length
. . :
Array.prototype.forEach.call(arrayLike, (value, index) => {
console.log(`${ index }: ${ value }`);
});
// : '0: a', '1: b', '2: c'.
forEach
, . , , array-like - , :
const actualArray = Array.prototype.slice.call(arrayLike, 0);
actualArray.forEach((value, index) => {
console.log(`${ index }: ${ value }`);
});
// : '0: a', '1: b', '2: c'.
.
, arguments
β . , , :
const logArgs = function() {
Array.prototype.forEach.call(arguments, (value, index) => {
console.log(`${ index }: ${ value }`);
});
};
logArgs('a', 'b', 'c');
// : '0: a', '1: b', '2: c'.
arguments
rest parameters, , ECMAScript 2015. , .
function logArgs(...args) {
args.forEach((value, index) => {
console.log(`${ index }: ${ value }`);
});
};
logArgs('a', 'b', 'c');
// : '0: a', '1: b', '2: c'.
arguments
.
, array-like , .
, , , . :
const each = (array, callback) => {
for (let index = 0; index < array.length; ++index) {
const item = array[index];
callback(item);
}
};
const doSomething = (item) => console.log(item);
each([], () => {});
each(['a', 'b', 'c'], doSomething);
// `each` `PACKED_ELEMENTS`.
// V8 inline- (inline cache, IC), `each`
// . V8
// , , `array.length`
// `array[index]` - ,
// , .
// `each` .
// `PACKED_ELEMENTS`, V8 . ,
// .
each([1.1, 2.2, 3.3], doSomething);
// `each` `PACKED_DOUBLE_ELEMENTS`.
// - , V8 `each` ,
// `array.length` `array[index]` .
//
// , .
each([1, 2, 3], doSomething);
// `each` `PACKED_SMI_ELEMENTS`.
// `each`,
// .
Array.prototype.forEach
, , , .
. V8, . , :
const array = new Array(3);
// ,
// `HOLEY_SMI_ELEMENTS`, ,
//
array[0] = 'a';
// , , !
// `HOLEY_ELEMENTS`.
array[1] = 'b';
array[2] = 'c';
// ,
// `HOLEY_ELEMENTS`
// `PACKED_ELEMENTS`.
, , β !
:
const array = ['a', 'b', 'c'];
// : PACKED_ELEMENTS
, , push
.
const array = [];
// ...
array.push(someValue);
// ...
array.push(someOtherValue);
, , d8
( jsvu). :
out/x64.debug/d8 --allow-natives-syntax
REPL d8, . %DebutPrint(object)
( elements
):
d8> const array = [1, 2, 3]; %DebugPrint(array);
DebugPrint: 0x1fbbad30fd71: [JSArray]
- map = 0x10a6f8a038b1 [FastProperties]
- prototype = 0x1212bb687ec1
- elements = 0x1fbbad30fd19 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length = 3
- properties = 0x219eb0702241 <FixedArray[0]> {
#length: 0x219eb0764ac9 <AccessorInfo> (const accessor descriptor)
}
- elements= 0x1fbbad30fd19 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
[...]
--trace-elements-transitions
. , V8 .
$ cat my-script.js
const array = [1, 2, 3];
array[3] = 4.56;
$ out/x64.debug/d8 --trace-elements-transitions my-script.js
elements transition [PACKED_SMI_ELEMENTS -> PACKED_DOUBLE_ELEMENTS] in ~+34 at x.js:2 for 0x1df87228c911 <JSArray[3]> from 0x1df87228c889 <FixedArray[3]> to 0x1df87228c941 <FixedDoubleArray[22]>