The authors introduce

Ziyi (interested students can click to check Ziyi’s gold digging homepage, there are a lot of excellent articles inside), a member of the proprietary dingding front-end team, responsible for the development of engineering, on-end application and on-end module plug-in of the proprietary Dingding PC client.

background

This article mainly describes the running characteristics of JavaScript language, which is helpful for in-depth understanding of JavaScript. Through this article can also guide code design principles, improve the performance of the project. This paper is mainly divided into the following parts:

  • Hidden classes
  • The element type
  • Naming attribute

Tips: “A Brief Introduction to Chrome V8 compilation” is the theoretical support of this article. If you are not clear about the principle of JavaScript compilation, you can read this article first.

Debug the sample

If you want to learn about compile time and runtime information for JavaScript in V8, you can use the debugging tool D8. D8 is the command line Shell of V8 engine, you can view AST generation, intermediate code ByteCode, optimized code, de-optimized code, optimized compiler statistics, code GC and so on. There are many ways to install the D8, including:

  • Method 1: Download and compile the toolchain according to V8 official documents Using D8 and Building V8 with GN
  • Method 2: use someone else has compiled D8 tools, the version may have lag, such as the Mac version
  • Method 3: Use a JavaScript engine version management tool, such as JSVU

This document uses method 3 to install the D8 tool. After the installation is complete, run v8-debug –help to view specific command information:

V8 -debug --help Synopsis: shell [options] [--shell] [<file>...]  d8 [options] [-e <string>] [--shell] [[--module|--web-snapshot] <file>...]  -e execute a string in V8 --shell run an interactive JavaScript shell --module execute a file as a JavaScript module --web-snapshot execute a file as a web snapshot SSE3=1 SSSE3=1 SSE4_1=1 SSE4_2=1 SAHF=1 AVX=1 AVX2=1 FMA3=1 BMI1=1 BMI2=1 LZCNT=1 POPCNT=1 ATOM=0 The following syntax for options is accepted (both '-' and '--' are ok): --flag (bool flags only) --no-flag (bool flags only) --flag=value (non-bool flags only, no spaces around '=') --flag value (non-bool flags only) -- (captures all remaining args in JavaScript) Options: # print-bytecode (print bytecode generated by ignition interpreter) type: bool default: --noprint-bytecode # Trace optimized compilation --trace-opt (Trace Optimized compilation) type: bool default: --notrace-opt --trace-opt-verbose (extra verbose optimized compilation tracing) type: bool default: --notrace-opt-verbose --trace-opt-stats (trace optimized compilation statistics) type: bool default: --trace-deopt (trace deoptimization) type: bool default: --notrace-deopt --log-deopt (log deoptimization) type: bool default: --nolog-deopt --trace-deopt-verbose (extra verbose deoptimization tracing) type: bool default: --notrace-deopt-verbose --print-deopt-stress (print number of possible deopt points) # Source AST) type: bool default: --noprint-ast # check generated code --print-code (print generated code) type: bool default: --print-opt-code (print optimized code) type: bool default: --noprint-opt-code # Allow native API syntax provided by V8 in source --allow-natives-syntax (allow natives syntax) type: bool default: --noallow-natives-syntaxCopy the code

After installing V8-DEBUG using jSVU, create a new index.js file and write the following code:

Class Test {constructor(length) {// Name attribute for (let I = 0; i < length; i++) { this[`string${i}`] = `string${i}`; } // For (let I = 0; i < length; i++) { this[i] = `number${i}`; } } } const test = new Test(15); // Built-in API provided by V8, you need to go to --allow-natives-syntax to use %DebugPrint(test);Copy the code

Note: %DebugPrint is a built-in API provided by V8. See built-in Functions for an introduction to V8 built-in apis. For more information about the built-in API, see SRC/Runtime /runtime.h.

After you run the v8-debug –allow-natives-syntax./index.js command, the following information is displayed:

DebugPrint: 0x2CD20810A655: [JS_OBJECT_TYPE] 0x2CD2082C7e61 <Map(HOLEY_ELEMENTS)> [FastProperties] 0x2CD20810a589 <Test map = 0x2CD2082C7e89 > 0x2CD20810AD61 <FixedArray[17]> [HOLEY_ELEMENTS] 0x2CD20810AD05 <PropertyArray[6]> - All own properties (excluding elements): { in-object 0x2cd2082936d5: [String] in OldSpace: #string0: 0x2cd20810a69d <String[7]: "string0"> (const data field 0), location: in-object 0x2cd20829370d: [String] in OldSpace: #string1: 0x2cd20810a6fd <String[7]: "string1"> (const data field 1), location: in-object 0x2cd208293731: [String] in OldSpace: #string2: 0x2cd20810a74d <String[7]: "string2"> (const data field 2), location: in-object 0x2cd208293755: [String] in OldSpace: #string3: 0x2cd20810a7a9 <String[7]: "string3"> (const data field 3), location: in-object 0x2cd208293779: [String] in OldSpace: #string4: 0x2cd20810a811 <String[7]: "string4"> (const data field 4), location: in-object 0x2cd20829379d: [String] in OldSpace: #string5: 0x2cd20810a885 <String[7]: "string5"> (const data field 5), location: in-object 0x2cd2082937c1: [String] in OldSpace: #string6: 0x2cd20810a905 <String[7]: "string6"> (const data field 6), location: in-object 0x2cd2082937e5: [String] in OldSpace: #string7: 0x2cd20810a991 <String[7]: "string7"> (const data field 7), location: in-object 0x2cd208293809: [String] in OldSpace: #string8: 0x2cd20810aa29 <String[7]: "string8"> (const data field 8), location: in-object 0x2cd20829382d: [String] in OldSpace: #string9: 0x2CD20810AAD9 <String[7]: "string9"> (const data field 9), location: in-object properties[0] 0x2cd208293851: [String] in OldSpace: #string10: 0x2cd20810ab01 <String[8]: "string10"> (const data field 10), location: properties[0] 0x2cd208293875: [String] in OldSpace: #string11: 0x2cd20810abdd <String[8]: "string11"> (const data field 11), location: properties[1] 0x2cd208293899: [String] in OldSpace: #string12: 0x2cd20810ac05 <String[8]: "string12"> (const data field 12), location: properties[2] 0x2cd2082938bd: [String] in OldSpace: #string13: 0x2cd20810acf1 <String[8]: "string13"> (const data field 13), location: properties[3] 0x2cd2082938e1: [String] in OldSpace: #string14: 0x2CD20810AD39 <String[8]: "string14"> (const data field 14), location: properties[4]} 0x2cd20810ad61 <FixedArray[17]> { 0: 0x2cd20810ad4d <String[7]: "number0"> 1: 0x2cd20810adad <String[7]: "number1"> 2: 0x2cd20810adc1 <String[7]: "number2"> 3: 0x2cd20810ae09 <String[7]: "number3"> 4: 0x2cd20810ae1d <String[7]: "number4"> 5: 0x2cd20810ae31 <String[7]: "number5"> 6: 0x2cd20810ae45 <String[7]: "number6"> 7: 0x2cd20810ae59 <String[7]: "number7"> 8: 0x2cd20810ae6d <String[7]: "number8"> 9: 0x2cd20810ae81 <String[7]: "number9"> 10: 0x2cd20810ae95 <String[8]: "number10"> 11: 0x2cd20810aea9 <String[8]: "number11"> 12: 0x2cd20810aebd <String[8]: "number12"> 13: 0x2cd20810aed1 <String[8]: "number13"> 14: 0x2cd20810aee5 <String[8]: "Number14 "> 15-16: 0x2CD20800242D <the_hole>} // Hide class details 0x2CD2082C7e61: [Map] -type: js_object_type-instance size: 52 - inobject properties: 10 - elements kind: HOLEY_ELEMENTS - unused property fields: 1 - enum length: invalid - stable_map - back pointer: 0x2cd2082c7e39 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x2cd208293705 <Cell value= 1> - instance descriptors (own) #15: 0x2cd20810ac19 <DescriptorArray[15]> - prototype: 0x2cd20810a589 <Test map = 0x2cd2082c7e89> - constructor: 0x2cd20810a569 <JSFunction Test (sfi = 0x2cd208293469)> - dependent code: 0x2cd2080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 6Copy the code

Based on the above printed information, the object attribute structure compiled by V8 can be simply sorted out, as shown in the figure below:

JavaScript objects are collections of attributes and values, which look very similar to dictionaries. However, in the actual storage process, various storage structures are used to complete the storage of object attributes for the consideration of performance. As shown in the figure above, the JavaScript object contains:

  • Elements: Array indexed properties are typically stored in a separate Elements store in a linear data structure, mainly via theArray.prototypeMethods provided (e.gpopslice) to efficiently access attribute values of consecutive addresses. It is important to note that in some cases the structure of the storage may be converted to dictionary form, such as sparse arrays with large array indexes that suddenly appear, thereby saving the memory overhead associated with continuous storage
  • Properties: Named Properties are usually stored in the Properties store, unlike elements, It is not possible to simply infer the location of their properties in the properties array from their keys, and some additional meta information is often required. In V8, hidden classes are used to associate named attributes of objects with shapes of their values, and in complex cases dictionaries are used instead of simple arrays to store data

Tips: Map and Proto shown in the figure are hidden classes and prototype object properties respectively. Elements and Properties can either be arrays or dictionaries. Learn more about linear and nonlinear storage structures, including linked lists, stacks, queues, trees, and graphs.

Hidden classes

In a static language such as c + + language statement need to define the object of the specific shape of an object (the attribute structure), the code before execution often need to compile, compiled because of the shape of the object is fixed, fixed pointer offset can attribute to quickly query attribute values, this is one of the reasons of high static language performance. In JavaScript dynamic language, attributes can be added and deleted dynamically during the execution of an object. Once the shape of an object is modified, it is often impossible to find the attribute value of the object by offset, so the search for the attribute value of the object becomes more complicated. V8 used in the design process of hidden classes preset JavaScript objects as a static object to handle (assuming that objects are static, object creation after will not add new attributes, also won’t attribute deletion), and set the value for each object attribute pointer offset information, which can quickly find the object’s property values. The property descriptor of an object contains a lot of information, including Writable, Enumerable, and Configurable, which is fixed and changes without any additional information. The hidden class works without any additional information. Thus, the memory space is saved to a certain extent in the language design.

From the debugging example, it can be seen that the hidden class mainly stores object-related meta-information, including the number of attributes of the object, the prototype attribute information and the pointer information of the attribute descriptor array. Hidden classes can be understood as identifiers for object shapes in V8 and are an important part of TurboFan, the V8 optimized compiler, as well as inline caches. The descriptor array for the hidden class contains information about named attributes (not element attributes, note), including the name of the attribute and the pointer offset of the attribute value relative to the object itself. The pointer address offset information of an attribute helps V8 to quickly find the value of the attribute by hiding the class, thus improving the efficiency of finding the named attribute of the object. Finally, the optimization compiler TurboFan has direct access to inline properties, allowing you to hide classes to ensure structure-compatible objects, quickly find object property values based on structure-fixed property offset Pointers, and determine how to execute optimized machine code to improve performance.

Adding named Attributes

To better understand the runtime nature of the hidden class itself, the code in index.js is adjusted as follows:

const obj1 = { a: 100, b: 200 };
const obj2 = { a: 100, b: 200 };
const obj3 = { a: 100, b: 200, c: 300 };

%DebugPrint(obj1);
%DebugPrint(obj2);
%DebugPrint(obj3);

obj1.c = 300;
%DebugPrint(obj1);

obj2.d = 400;
%DebugPrint(obj2);
Copy the code

The following information is displayed after the command is compiled:

// 执行 index.js 脚本
v8-debug --allow-natives-syntax  ./index.js

// obj1 的打印信息
DebugPrint: 0x146c0810a53d: [JS_OBJECT_TYPE]
 // obj1 的隐藏类指针信息:0x146c082c7b91
 - map: 0x146c082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - elements: 0x146c0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x146c0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x146c0808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x146c0808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
 }
0x146c082c7b91: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x146c082c7b69 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x146c082044fd <Cell value= 1>
 - instance descriptors (own) #2: 0x146c0810a56d <DescriptorArray[2]>
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - constructor: 0x146c08283e59 <JSFunction Object (sfi = 0x146c0820b0f1)>
 - dependent code: 0x146c080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

// obj2 的打印信息
DebugPrint: 0x146c0810a595: [JS_OBJECT_TYPE]
 // obj2 的隐藏类指针信息:0x146c082c7b91(和 obj1 的指针相同)
 - map: 0x146c082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - elements: 0x146c0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x146c0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x146c0808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x146c0808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
 }
0x146c082c7b91: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x146c082c7b69 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x146c082044fd <Cell value= 1>
 - instance descriptors (own) #2: 0x146c0810a56d <DescriptorArray[2]>
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - constructor: 0x146c08283e59 <JSFunction Object (sfi = 0x146c0820b0f1)>
 - dependent code: 0x146c080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

// obj3 的打印信息
DebugPrint: 0x146c0810a5a9: [JS_OBJECT_TYPE]
 // obj3 的隐藏类指针信息:0x146c082c7c31(和 obj1、obj2 的指针不同)
 - map: 0x146c082c7c31 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - elements: 0x146c0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x146c0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x146c0808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x146c0808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
    0x146c082933f5: [String] in OldSpace: #c: 300 (const data field 2), location: in-object
 }
0x146c082c7c31: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 24
 - inobject properties: 3
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x146c082c7c09 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x146c082044fd <Cell value= 1>
 - instance descriptors (own) #3: 0x146c0810a605 <DescriptorArray[3]>
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - constructor: 0x146c08283e59 <JSFunction Object (sfi = 0x146c0820b0f1)>
 - dependent code: 0x146c080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

// obj1 添加属性 c 后的打印信息
DebugPrint: 0x146c0810a53d: [JS_OBJECT_TYPE]
 // obj1 的隐藏类指针信息:0x146c082c7c59(和 obj1 原有的隐藏类指针地址不同)
 - map: 0x146c082c7c59 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - elements: 0x146c0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x146c0810a66d <PropertyArray[3]>
 - All own properties (excluding elements): {
    0x146c0808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x146c0808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
    0x146c082933f5: [String] in OldSpace: #c: 300 (const data field 2), location: properties[0]
 }
0x146c082c7c59: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 2
 - enum length: invalid
 - stable_map
 // obj1 新的隐藏类的尾指针指向了 obj1 旧的隐藏类的指针地址 0x146c082c7b91
 - back pointer: 0x146c082c7b91 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x146c08293601 <Cell value= 0>
 - instance descriptors (own) #3: 0x146c0810a639 <DescriptorArray[3]>
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - constructor: 0x146c08283e59 <JSFunction Object (sfi = 0x146c0820b0f1)>
 - dependent code: 0x146c080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

// obj2 添加属性 d 后的打印信息
DebugPrint: 0x146c0810a595: [JS_OBJECT_TYPE]
 // obj1 的隐藏类指针信息:0x146c082c7c81(和 obj2 原有的隐藏类指针地址不同)
 - map: 0x146c082c7c81 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - elements: 0x146c0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x146c0810a6b5 <PropertyArray[3]>
 - All own properties (excluding elements): {
    0x146c0808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x146c0808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
    0x146c0829341d: [String] in OldSpace: #d: 400 (const data field 2), location: properties[0]
 }
0x146c082c7c81: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 2
 - enum length: invalid
 - stable_map
 // obj2 新的隐藏类的尾指针指向了 obj2 旧的隐藏类的指针地址 0x146c082c7b91
 - back pointer: 0x146c082c7b91 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x146c08293601 <Cell value= 0>
 - instance descriptors (own) #3: 0x146c0810a681 <DescriptorArray[3]>
 - prototype: 0x146c08284245 <Object map = 0x146c082c21b9>
 - constructor: 0x146c08283e59 <JSFunction Object (sfi = 0x146c0820b0f1)>
 - dependent code: 0x146c080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
Copy the code

The hidden classes of obj1 and obj 2 look something like the following:

Tips: In the figure, HC N refers to the pointer address information of the NTH hidden class, and the arrow refers to the association between two HCS. For example, HC 0 points to HC 1. Note HC 1’s back pointer (address of the last hidden class in the hidden class list) is the pointer address of HC 0 (0x146C082C7b69).

Through the above figure and the printed information, we can finally draw the following conclusions:

  • Each JavaScript object has a corresponding hidden class that records the object’s shape information
  • JavaScript objects of the same shape can share the same hidden class (saving storage space and creation times for hidden classes)
  • Each time a new property is added to an object, a new hidden class is created, and the tail pointer of the new hidden class points to the previous hidden class
  • V8 saves information structure data for hidden classes by creating a Transition Tree that links all hidden classes together

Deleting named Attributes

In the process of actually designing code, it is often possible to delete properties of obj1 and obj2, for example:

const obj1 = { a: 100, b: 200 };
%DebugPrint(obj1);

obj1.c = 300;
%DebugPrint(obj1);

delete obj1.b
%DebugPrint(obj1);
Copy the code

The following information is displayed after the compilation:

V8-debug --allow-natives-syntax./index.js // Obj1 prints DebugPrint: 0x3E900810a4cd: [JS_OBJECT_TYPE] -map: 0x3e90082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x3e9008284245 <Object map = 0x3e90082c21b9> - elements: 0x3e900800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x3e900800222d <FixedArray[0]> - All own properties (excluding elements): { 0x3e900808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object 0x3e900808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object } 0x3e90082c7b91: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x3e90082c7b69 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x3e90082044fd <Cell value= 1> - instance descriptors (own) #2: 0x3e900810a4fd <DescriptorArray[2]> - prototype: 0x3e9008284245 <Object map = 0x3e90082c21b9> - constructor: 0x3e9008283e59 <JSFunction Object (sfi = 0x3e900820b0f1)> - dependent code: 0x3E90080021B9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - Construction Counter: 0 0x3e900810a4cd: [JS_OBJECT_TYPE] - map: 0x3e90082c7bb9 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x3e9008284245 <Object map = 0x3e90082c21b9> - elements: 0x3e900800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x3e900810a559 <PropertyArray[3]> - All own properties (excluding elements): { 0x3e900808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object 0x3e900808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object 0x3e90082933ed: [String] in OldSpace: #c: 300 (const data field 2), location: properties[0] } 0x3e90082c7bb9: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 2 - enum length: invalid - stable_map - back pointer: 0x3e90082c7b91 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x3e900829354d <Cell value= 0> - instance descriptors (own) #3: 0x3e900810a525 <DescriptorArray[3]> - prototype: 0x3e9008284245 <Object map = 0x3e90082c21b9> - constructor: 0x3e9008283e59 <JSFunction Object (sfi = 0x3e900820b0f1)> - dependent code: 0x3E90080021B9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - Construction Counter: 0 0x3e900810a4cd: [JS_OBJECT_TYPE] - map: 0x3e90082c56d9 <Map(HOLEY_ELEMENTS)> [DictionaryProperties] - prototype: 0x3e9008284245 <Object map = 0x3e90082c21b9> - elements: 0x3e900800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x3e900810a56d <NameDictionary[29]> - All own properties (excluding elements): { a: 100 (data, dict_index: 1, attrs: [WEC]) c: 300 (data, dict_index: 3, attrs: [WEC]) } 0x3e90082c56d9: [Map] - type: JS_OBJECT_TYPE - instance size: 12 - inobject properties: 0 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - dictionary_map - may_have_interesting_symbols - back pointer: 0x3e90080023b5 <undefined> - prototype_validity cell: 0x3e90082044fd <Cell value= 1> - instance descriptors (own) #0: 0x3e90080021c1 <Other heap object (STRONG_DESCRIPTOR_ARRAY_TYPE)> - prototype: 0x3e9008284245 <Object map = 0x3e90082c21b9> - constructor: 0x3e9008283e59 <JSFunction Object (sfi = 0x3e900820b0f1)> - dependent code: 0x3e90080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0Copy the code

Rebuild the hidden class diagram as follows:

As can be seen from the above figure, deleting an attribute will create new hidden class information, which has no association with the previous hidden class. Therefore, deleting an attribute may affect the performance of the code. Note that if obj1 removes the property C that was just added, it will fall back to the hidden class pointed to by 0x3E90082C7b91 (where the back Pointer address is used to roll back the hidden class) and will not completely create a new hidden class.

Sets the value of the named property to NULL

If the attribute is set to null instead of being deleted:

const obj1 = { a: 100, b: 200 };
%DebugPrint(obj1);

obj1.b = null;
%DebugPrint(obj1);
Copy the code

The following information is displayed:

.jsvu % v8-debug --allow-natives-syntax  ./index.js

DebugPrint: 0x21f60810a4ad: [JS_OBJECT_TYPE]
 - map: 0x21f6082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x21f608284245 <Object map = 0x21f6082c21b9>
 - elements: 0x21f60800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x21f60800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x21f60808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x21f60808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
 }
0x21f6082c7b91: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x21f6082c7b69 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x21f6082044fd <Cell value= 1>
 - instance descriptors (own) #2: 0x21f60810a4dd <DescriptorArray[2]>
 - prototype: 0x21f608284245 <Object map = 0x21f6082c21b9>
 - constructor: 0x21f608283e59 <JSFunction Object (sfi = 0x21f60820b0f1)>
 - dependent code: 0x21f6080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x21f60810a4ad: [JS_OBJECT_TYPE]
 - map: 0x21f6082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x21f608284245 <Object map = 0x21f6082c21b9>
 - elements: 0x21f60800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x21f60800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x21f60808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x21f60808ff41: [String] in ReadOnlySpace: #b: 0x21f608002235 <null> (data field 1), location: in-object
 }
0x21f6082c7b91: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x21f6082c7b69 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x21f6082044fd <Cell value= 1>
 - instance descriptors (own) #2: 0x21f60810a4dd <DescriptorArray[2]>
 - prototype: 0x21f608284245 <Object map = 0x21f6082c21b9>
 - constructor: 0x21f608283e59 <JSFunction Object (sfi = 0x21f60820b0f1)>
 - dependent code: 0x21f6080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
Copy the code

As you can see from the above print, setting the field of the named attribute to NULL does not change the original hidden class, so if you want to reset the attribute, setting the attribute to NULL can improve JavaScript performance compared to deleting the attribute.

Add element attributes

If you operate on an object with an attribute that is not a named attribute, but an element attribute, for example:

const obj1 = { a: 100, b: 200 };
%DebugPrint(obj1);

obj1[0] = 300;
obj1[1] = 400;
%DebugPrint(obj1);
Copy the code
v8-debug --allow-natives-syntax  ./index.js

DebugPrint: 0x3d260810a4bd: [JS_OBJECT_TYPE]
 - map: 0x3d26082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x3d2608284245 <Object map = 0x3d26082c21b9>
 - elements: 0x3d260800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x3d260800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x3d260808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x3d260808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
 }
0x3d26082c7b91: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x3d26082c7b69 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x3d26082044fd <Cell value= 1>
 - instance descriptors (own) #2: 0x3d260810a4ed <DescriptorArray[2]>
 - prototype: 0x3d2608284245 <Object map = 0x3d26082c21b9>
 - constructor: 0x3d2608283e59 <JSFunction Object (sfi = 0x3d260820b0f1)>
 - dependent code: 0x3d26080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x3d260810a4bd: [JS_OBJECT_TYPE]
 - map: 0x3d26082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x3d2608284245 <Object map = 0x3d26082c21b9>
 - elements: 0x3d260810a515 <FixedArray[17]> [HOLEY_ELEMENTS]
 - properties: 0x3d260800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x3d260808fea5: [String] in ReadOnlySpace: #a: 100 (const data field 0), location: in-object
    0x3d260808ff41: [String] in ReadOnlySpace: #b: 200 (const data field 1), location: in-object
 }
 - elements: 0x3d260810a515 <FixedArray[17]> {
           0: 300
           1: 400
        2-16: 0x3d260800242d <the_hole>
 }
0x3d26082c7b91: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x3d26082c7b69 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x3d26082044fd <Cell value= 1>
 - instance descriptors (own) #2: 0x3d260810a4ed <DescriptorArray[2]>
 - prototype: 0x3d2608284245 <Object map = 0x3d26082c21b9>
 - constructor: 0x3d2608283e59 <JSFunction Object (sfi = 0x3d260820b0f1)>
 - dependent code: 0x3d26080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
Copy the code

As you can see from the printed output, adding element attributes does not create new hidden class information for the object.

Create objects using a class or factory function

Suppose you use factory functions to create objects during development, for example:

Function createFactory(givenName, familyName) {// Return {givenName, familyName, name: return {givenName, familyName, name: givenName + ' ' + familyName } } const benny = createFactory('jack', 'Benny'); %DebugPrint(benny); const bruce = createFactory('jack', ' Bruce'); %DebugPrint(bruce); const black = createFactory('jack', ' Black'); %DebugPrint(black);Copy the code
v8-debug --allow-natives-syntax  ./index.js

DebugPrint: 0xdb10810a769: [JS_OBJECT_TYPE]
 - map: 0x0db1082c7be1 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x0db108284245 <Object map = 0xdb1082c21b9>
 - elements: 0x0db10800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x0db10800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0xdb1082933e1: [String] in OldSpace: #givenName: 0x0db108293425 <String[4]: #jack> (const data field 0), location: in-object
    0xdb1082933f9: [String] in OldSpace: #familyName: 0x0db108293435 <String[5]: #Benny> (const data field 1), location: in-object
    0xdb108004dfd: [String] in ReadOnlySpace: #name: 0x0db10810a80d <String[10]: "jack Benny"> (const data field 2), location: in-object
 }
0xdb1082c7be1: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 24
 - inobject properties: 3
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x0db1082c7bb9 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x0db1082044fd <Cell value= 1>
 - instance descriptors (own) #3: 0x0db10810a7c5 <DescriptorArray[3]>
 - prototype: 0x0db108284245 <Object map = 0xdb1082c21b9>
 - constructor: 0x0db108283e59 <JSFunction Object (sfi = 0xdb10820b0f1)>
 - dependent code: 0x0db1080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0xdb10810a825: [JS_OBJECT_TYPE]
 - map: 0x0db1082c7be1 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x0db108284245 <Object map = 0xdb1082c21b9>
 - elements: 0x0db10800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x0db10800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0xdb1082933e1: [String] in OldSpace: #givenName: 0x0db108293425 <String[4]: #jack> (const data field 0), location: in-object
    0xdb1082933f9: [String] in OldSpace: #familyName: 0x0db108293475 <String[6]: # Bruce> (const data field 1), location: in-object
    0xdb108004dfd: [String] in ReadOnlySpace: #name: 0x0db10810a851 <String[11]: "jack  Bruce"> (const data field 2), location: in-object
 }
0xdb1082c7be1: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 24
 - inobject properties: 3
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x0db1082c7bb9 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x0db1082044fd <Cell value= 1>
 - instance descriptors (own) #3: 0x0db10810a7c5 <DescriptorArray[3]>
 - prototype: 0x0db108284245 <Object map = 0xdb1082c21b9>
 - constructor: 0x0db108283e59 <JSFunction Object (sfi = 0xdb10820b0f1)>
 - dependent code: 0x0db1080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0xdb10810a869: [JS_OBJECT_TYPE]
 - map: 0x0db1082c7be1 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x0db108284245 <Object map = 0xdb1082c21b9>
 - elements: 0x0db10800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x0db10800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0xdb1082933e1: [String] in OldSpace: #givenName: 0x0db108293425 <String[4]: #jack> (const data field 0), location: in-object
    0xdb1082933f9: [String] in OldSpace: #familyName: 0x0db10829349d <String[6]: # Black> (const data field 1), location: in-object
    0xdb108004dfd: [String] in ReadOnlySpace: #name: 0x0db10810a895 <String[11]: "jack  Black"> (const data field 2), location: in-object
 }
0xdb1082c7be1: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 24
 - inobject properties: 3
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x0db1082c7bb9 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x0db1082044fd <Cell value= 1>
 - instance descriptors (own) #3: 0x0db10810a7c5 <DescriptorArray[3]>
 - prototype: 0x0db108284245 <Object map = 0xdb1082c21b9>
 - constructor: 0x0db108283e59 <JSFunction Object (sfi = 0xdb10820b0f1)>
 - dependent code: 0x0db1080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
Copy the code

Printed information can be found through output, using a factory function generated by multiple objects hidden all point to the same class, in the process of generating objects, using the same object literal initialization naming attribute (the same name, the same attribute the same properties, add a sequence number), only generates a hidden classes.

Code Practice Guide

  • If possible, use object literals to initialize complete object properties at once (create a hidden class instead of a hidden class chain)
  • Do not delete named attributes using delete. You can reset named attributes using NULL instead
  • If there are many identical or similar objects to be generated, the consistency of object structure should be guaranteed as much as possible. Factory functions are recommended to reduce the number of hidden classes to be generated

Naming attribute

For the sake of performance and memory, JavaScript uses different storage methods for object attributes in different situations. In-depth understanding of how object attributes are optimized according to their usage can help us improve the optimization of object read and write performance. Consider the following data declarations:

  class Foo {
    constructor(length) {
      for (let i = 0; i < length; i++) { this[i] = `number${i}` } 
      for (let i = 0; i < length; i++) { this[`string${i}`] = `string${i}` } 
    }
  }
  const foo = new Foo(15)
Copy the code

In the description of hidden classes and element attributes, we use V8 for debugging. In fact, if you feel jSUV installation is relatively difficult, you can also use Chrome (81.0.4044.138) developer tools for Memory snapshot. Then search for the Foo constructor to see the details (which may not be as detailed as v8 debugging), as shown below:

When named properties are accessed through properties, a secondary access is often required. This is because the object needs to be indexed by properties first and then indexed by properties a second time. To speed up access, V8 supports in-object properties that are stored directly on the object itself, such as the properties shown above (string0 to string9). Note that the number of properties in an object is pre-determined by the initial size of the object. Chrome DevTools debugging shows that the number in the figure above is 10 (in most cases), while string10 ~ String 14 is stored in properties.

In addition, it can be seen from the storage structure of string10 to string14 that, when the amount of data is relatively small, the linear storage structure of continuous addresses is adopted by default, and the information structure of object attributes is often maintained by hiding classes. If the object has a lot of to add or remove attributes operation, the need to produce a lot of time and memory overhead to maintain the property descriptors and hidden information, therefore the V8 will change for the dictionary storage, storage way meta information of the properties and now not maintain in the attributes of the object hidden information structure, but directly stored in the dictionary. It is important to note that inline caching does not apply to dictionary properties, so the performance stored in dictionaries is generally lower than that of linear storage. Properties with dictionary storage (nonlinear storage) are officially called slow properties, while properties with linear storage structure are called fast properties. Since slow attributes degrade code performance, V8 tries to avoid slow attributes for attribute value storage.

Three types of named attributes

Named properties can be divided into in-object properties and common properties. In-object properties do not require secondary access and are directly stored In the object itself. The access speed is the fastest, but the number of storage is limited and is usually determined according to the initial size of the object (basically, most development can meet this size requirement). Ordinary properties need to be indexed first, so they require a secondary access and lower performance than in-object properties. In addition, common properties can be divided into Fast properties and Slow properties. Fast attributes use a linear storage structure, and attribute meta-information such as the pointer offset address of the attribute value is stored in the descriptor array of the hidden class (often shared by objects of the same structure). Slow attributes are stored in nonlinear dictionary mode, and the meta information of all attributes is no longer stored in the hidden class, but stored separately in attribute bytes.

Add a lot of attributes

When the number of named attributes is small, the hidden class will be used to maintain the meta information of the attributes. At this time, the attributes are FastProperties and adopt a linear storage structure:

const obj = { a: 1, b: 2};
%DebugPrint(obj);

for(let i=0; i<10; i++) {
    obj[`string${i}`] = i;
}
%DebugPrint(obj);
Copy the code
.jsvu % v8-debug --allow-natives-syntax ./index.js DebugPrint: 0xf7a0810a4cd: [JS_OBJECT_TYPE] // FastProperties - map: 0x0f7a082C7b91 < map (HOLEY_ELEMENTS)> [FastProperties] - prototype: prototype 0x0f7a08284245 <Object map = 0xf7a082c21b9> - elements: 0x0f7a0800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x0f7a0800222d <FixedArray[0]> - All own properties (excluding elements): { 0xf7a0808fea5: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object 0xf7a0808ff41: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object } 0xf7a082c7b91: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x0f7a082c7b69 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x0f7a082044fd <Cell value= 1> - instance descriptors (own) #2: 0x0f7a0810a4fd <DescriptorArray[2]> - prototype: 0x0f7a08284245 <Object map = 0xf7a082c21b9> - constructor: 0x0f7a08283e59 <JSFunction Object (sfi = 0xf7a0820b0f1)> - dependent code: 0x0f7a080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 DebugPrint: 0xf7a0810a4cd: [JS_OBJECT_TYPE] - map: 0x0f7a082c7d21 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x0f7a08284245 <Object map = 0xf7a082c21b9> - elements: 0x0f7a0800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x0f7a0810a961 <PropertyArray[12]> - All own properties (excluding elements): { // location: In-object Object Attribute 0xf7A0808FeA5: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: In-object // Location: In-object Obj. b Is an In-Object Attribute 0xf7A0808FF41: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object 0xf7a08293545: [String] in OldSpace: #string0: 0 (const data field 2), location: properties[0] 0xf7a08293561: [String] in OldSpace: #string1: 1 (const data field 3), location: properties[1] 0xf7a08293585: [String] in OldSpace: #string2: 2 (const data field 4), location: properties[2] 0xf7a082935a9: [String] in OldSpace: #string3: 3 (const data field 5), location: properties[3] 0xf7a082935cd: [String] in OldSpace: #string4: 4 (const data field 6), location: properties[4] 0xf7a082935f1: [String] in OldSpace: #string5: 5 (const data field 7), location: properties[5] 0xf7a08293615: [String] in OldSpace: #string6: 6 (const data field 8), location: properties[6] 0xf7a08293639: [String] in OldSpace: #string7: 7 (const data field 9), location: properties[7] 0xf7a0829365d: [String] in OldSpace: #string8: 8 (const data field 10), location: properties[8] 0xf7a08293681: [String] in OldSpace: #string9: 9 (const data field 11), location: properties[9] } 0xf7a082c7d21: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 2 - enum length: invalid - stable_map - back pointer: 0x0f7a082c7cf9 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x0f7a08293559 <Cell value= 0> - instance descriptors (own) #12: 0x0f7a0810a8ad <DescriptorArray[12]> - prototype: 0x0f7a08284245 <Object map = 0xf7a082c21b9> - constructor: 0x0f7a08283e59 <JSFunction Object (sfi = 0xf7a0820b0f1)> - dependent code: 0x0f7a080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0Copy the code

When a large number of named attributes are added, the attribute will change from the fast attribute to the slow attribute (dictionary attribute). At the same time, observe the instance descriptors in the hidden class information carefully. At this time, the number is 0, indicating that the meta information of the named attribute is no longer stored in the hidden class. When converted to slow attributes, named attributes are stored in nonlinear dictionaries:

const obj = { a: 1, b: 2};
%DebugPrint(obj);
for(let i=0; i<100; i++) {
    obj[`string${i}`] = i;
}
%DebugPrint(obj);
Copy the code
v8-debug --allow-natives-syntax ./index.js

DebugPrint: 0x19ac0810a4cd: [JS_OBJECT_TYPE]
 - map: 0x19ac082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x19ac08284245 <Object map = 0x19ac082c21b9>
 - elements: 0x19ac0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x19ac0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x19ac0808fea5: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x19ac0808ff41: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object
 }
0x19ac082c7b91: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 20
 - inobject properties: 2
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x19ac082c7b69 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x19ac082044fd <Cell value= 1>
 - instance descriptors (own) #2: 0x19ac0810a4fd <DescriptorArray[2]>
 - prototype: 0x19ac08284245 <Object map = 0x19ac082c21b9>
 - constructor: 0x19ac08283e59 <JSFunction Object (sfi = 0x19ac0820b0f1)>
 - dependent code: 0x19ac080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x19ac0810a4cd: [JS_OBJECT_TYPE]
 // DictionaryProperties 字典属性
 - map: 0x19ac082c56d9 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
 - prototype: 0x19ac08284245 <Object map = 0x19ac082c21b9>
 - elements: 0x19ac0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - properties: 0x19ac0810bc35 <NameDictionary[773]>
 - All own properties (excluding elements): {
   string24: 24 (data, dict_index: 27, attrs: [WEC])
   string57: 57 (data, dict_index: 60, attrs: [WEC])
   string94: 94 (data, dict_index: 97, attrs: [WEC])
   string22: 22 (data, dict_index: 25, attrs: [WEC])
   string6: 6 (data, dict_index: 9, attrs: [WEC])
   string2: 2 (data, dict_index: 5, attrs: [WEC])
   string85: 85 (data, dict_index: 88, attrs: [WEC])
   string15: 15 (data, dict_index: 18, attrs: [WEC])
   string38: 38 (data, dict_index: 41, attrs: [WEC])
   string59: 59 (data, dict_index: 62, attrs: [WEC])
   string66: 66 (data, dict_index: 69, attrs: [WEC])
   string3: 3 (data, dict_index: 6, attrs: [WEC])
   string13: 13 (data, dict_index: 16, attrs: [WEC])
   string84: 84 (data, dict_index: 87, attrs: [WEC])
   string62: 62 (data, dict_index: 65, attrs: [WEC])
   string91: 91 (data, dict_index: 94, attrs: [WEC])
   string8: 8 (data, dict_index: 11, attrs: [WEC])
   string43: 43 (data, dict_index: 46, attrs: [WEC])
   string34: 34 (data, dict_index: 37, attrs: [WEC])
   string5: 5 (data, dict_index: 8, attrs: [WEC])
   string27: 27 (data, dict_index: 30, attrs: [WEC])
   string80: 80 (data, dict_index: 83, attrs: [WEC])
   string72: 72 (data, dict_index: 75, attrs: [WEC])
   string97: 97 (data, dict_index: 100, attrs: [WEC])
   string55: 55 (data, dict_index: 58, attrs: [WEC])
   string78: 78 (data, dict_index: 81, attrs: [WEC])
   string75: 75 (data, dict_index: 78, attrs: [WEC])
   string69: 69 (data, dict_index: 72, attrs: [WEC])
   string53: 53 (data, dict_index: 56, attrs: [WEC])
   string73: 73 (data, dict_index: 76, attrs: [WEC])
   string42: 42 (data, dict_index: 45, attrs: [WEC])
   string48: 48 (data, dict_index: 51, attrs: [WEC])
   string81: 81 (data, dict_index: 84, attrs: [WEC])
   string68: 68 (data, dict_index: 71, attrs: [WEC])
   string46: 46 (data, dict_index: 49, attrs: [WEC])
   string88: 88 (data, dict_index: 91, attrs: [WEC])
   string95: 95 (data, dict_index: 98, attrs: [WEC])
   string28: 28 (data, dict_index: 31, attrs: [WEC])
   string51: 51 (data, dict_index: 54, attrs: [WEC])
   string63: 63 (data, dict_index: 66, attrs: [WEC])
   string23: 23 (data, dict_index: 26, attrs: [WEC])
   string32: 32 (data, dict_index: 35, attrs: [WEC])
   string9: 9 (data, dict_index: 12, attrs: [WEC])
   string98: 98 (data, dict_index: 101, attrs: [WEC])
   string44: 44 (data, dict_index: 47, attrs: [WEC])
   string11: 11 (data, dict_index: 14, attrs: [WEC])
   string30: 30 (data, dict_index: 33, attrs: [WEC])
   string76: 76 (data, dict_index: 79, attrs: [WEC])
   string36: 36 (data, dict_index: 39, attrs: [WEC])
   string18: 18 (data, dict_index: 21, attrs: [WEC])
   string0: 0 (data, dict_index: 3, attrs: [WEC])
   string25: 25 (data, dict_index: 28, attrs: [WEC])
   string49: 49 (data, dict_index: 52, attrs: [WEC])
   string37: 37 (data, dict_index: 40, attrs: [WEC])
   string61: 61 (data, dict_index: 64, attrs: [WEC])
   string14: 14 (data, dict_index: 17, attrs: [WEC])
   string21: 21 (data, dict_index: 24, attrs: [WEC])
   string45: 45 (data, dict_index: 48, attrs: [WEC])
   string77: 77 (data, dict_index: 80, attrs: [WEC])
   string58: 58 (data, dict_index: 61, attrs: [WEC])
   string71: 71 (data, dict_index: 74, attrs: [WEC])
   string89: 89 (data, dict_index: 92, attrs: [WEC])
   string16: 16 (data, dict_index: 19, attrs: [WEC])
   string33: 33 (data, dict_index: 36, attrs: [WEC])
   string4: 4 (data, dict_index: 7, attrs: [WEC])
   string19: 19 (data, dict_index: 22, attrs: [WEC])
   string99: 99 (data, dict_index: 102, attrs: [WEC])
   string65: 65 (data, dict_index: 68, attrs: [WEC])
   string74: 74 (data, dict_index: 77, attrs: [WEC])
   string56: 56 (data, dict_index: 59, attrs: [WEC])
   string60: 60 (data, dict_index: 63, attrs: [WEC])
   string7: 7 (data, dict_index: 10, attrs: [WEC])
   string86: 86 (data, dict_index: 89, attrs: [WEC])
   string39: 39 (data, dict_index: 42, attrs: [WEC])
   string40: 40 (data, dict_index: 43, attrs: [WEC])
   string54: 54 (data, dict_index: 57, attrs: [WEC])
   string83: 83 (data, dict_index: 86, attrs: [WEC])
   string92: 92 (data, dict_index: 95, attrs: [WEC])
   string93: 93 (data, dict_index: 96, attrs: [WEC])
   string64: 64 (data, dict_index: 67, attrs: [WEC])
   string79: 79 (data, dict_index: 82, attrs: [WEC])
   string10: 10 (data, dict_index: 13, attrs: [WEC])
   string52: 52 (data, dict_index: 55, attrs: [WEC])
   string20: 20 (data, dict_index: 23, attrs: [WEC])
   string50: 50 (data, dict_index: 53, attrs: [WEC])
   string70: 70 (data, dict_index: 73, attrs: [WEC])
   string17: 17 (data, dict_index: 20, attrs: [WEC])
   string12: 12 (data, dict_index: 15, attrs: [WEC])
   string26: 26 (data, dict_index: 29, attrs: [WEC])
   string90: 90 (data, dict_index: 93, attrs: [WEC])
   string31: 31 (data, dict_index: 34, attrs: [WEC])
   string96: 96 (data, dict_index: 99, attrs: [WEC])
   string29: 29 (data, dict_index: 32, attrs: [WEC])
   a: 1 (data, dict_index: 1, attrs: [WEC])
   string87: 87 (data, dict_index: 90, attrs: [WEC])
   string35: 35 (data, dict_index: 38, attrs: [WEC])
   string67: 67 (data, dict_index: 70, attrs: [WEC])
   string82: 82 (data, dict_index: 85, attrs: [WEC])
   string41: 41 (data, dict_index: 44, attrs: [WEC])
   string47: 47 (data, dict_index: 50, attrs: [WEC])
   b: 2 (data, dict_index: 2, attrs: [WEC])
   string1: 1 (data, dict_index: 4, attrs: [WEC])
 }
0x19ac082c56d9: [Map]
 - type: JS_OBJECT_TYPE
 - instance size: 12
 - inobject properties: 0
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - dictionary_map
 - may_have_interesting_symbols
 - back pointer: 0x19ac080023b5 <undefined>
 - prototype_validity cell: 0x19ac082044fd <Cell value= 1>
 // 隐藏类信息中属性描述符数组的个数为 0 
 - instance descriptors (own) #0: 0x19ac080021c1 <Other heap object (STRONG_DESCRIPTOR_ARRAY_TYPE)>
 - prototype: 0x19ac08284245 <Object map = 0x19ac082c21b9>
 - constructor: 0x19ac08283e59 <JSFunction Object (sfi = 0x19ac0820b0f1)>
 - dependent code: 0x19ac080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
Copy the code

Warm tip: in this example, the test adds more than 15 named attributes, attributes will be changed from fast attribute type to dictionary type, the specific critical value in the source code should be a set of algorithm rules, here is not a close look, interested students can study.

Deleting named Attributes

In the case of naming attribute is fast attribute, delete named attributes and adding the similar, can produce a lot of time and memory overhead to maintain the hidden classes and descriptor array, the V8 will quickly downgraded to slow attribute, at this time due to the need to update the hidden class information, thus slow attribute to delete and add tend to be more efficient, But access is slower than fast properties and in-object properties:

const obj = { a: 1, b: 2};
%DebugPrint(obj);

for(let i=0; i<10; i++) {
    obj[`string${i}`] = i;
}
%DebugPrint(obj);

delete obj['string1'];
%DebugPrint(obj);
Copy the code
v8-debug --allow-natives-syntax ./index.js DebugPrint: 0x38e80810a4f5: [JS_OBJECT_TYPE] - map: 0x38e8082c7b91 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x38e808284245 <Object map = 0x38e8082c21b9> - elements: 0x38e80800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x38e80800222d <FixedArray[0]> - All own properties (excluding elements): { 0x38e80808fea5: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object 0x38e80808ff41: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object } 0x38e8082c7b91: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x38e8082c7b69 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x38e8082044fd <Cell value= 1> - instance descriptors (own) #2: 0x38e80810a525 <DescriptorArray[2]> - prototype: 0x38e808284245 <Object map = 0x38e8082c21b9> - constructor: 0x38e808283e59 <JSFunction Object (sfi = 0x38e80820b0f1)> - dependent code: 0x38e8080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 DebugPrint: 0x38e80810a4f5: [JS_OBJECT_TYPE] - map: 0x38e8082c7d21 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x38e808284245 <Object map = 0x38e8082c21b9> - elements: 0x38e80800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x38e80810a989 <PropertyArray[12]> - All own properties (excluding elements): { 0x38e80808fea5: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object 0x38e80808ff41: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object 0x38e80829356d: [String] in OldSpace: #string0: 0 (const data field 2), location: properties[0] 0x38e8082933ed: [String] in OldSpace: #string1: 1 (const data field 3), location: properties[1] 0x38e808293599: [String] in OldSpace: #string2: 2 (const data field 4), location: properties[2] 0x38e8082935bd: [String] in OldSpace: #string3: 3 (const data field 5), location: properties[3] 0x38e8082935e1: [String] in OldSpace: #string4: 4 (const data field 6), location: properties[4] 0x38e808293605: [String] in OldSpace: #string5: 5 (const data field 7), location: properties[5] 0x38e808293629: [String] in OldSpace: #string6: 6 (const data field 8), location: properties[6] 0x38e80829364d: [String] in OldSpace: #string7: 7 (const data field 9), location: properties[7] 0x38e808293671: [String] in OldSpace: #string8: 8 (const data field 10), location: properties[8] 0x38e808293695: [String] in OldSpace: #string9: 9 (const data field 11), location: properties[9] } 0x38e8082c7d21: [Map] - type: JS_OBJECT_TYPE - instance size: 20 - inobject properties: 2 - elements kind: HOLEY_ELEMENTS - unused property fields: 2 - enum length: invalid - stable_map - back pointer: 0x38e8082c7cf9 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x38e808293581 <Cell value= 0> - instance descriptors (own) #12: 0x38e80810a8d5 <DescriptorArray[12]> - prototype: 0x38e808284245 <Object map = 0x38e8082c21b9> - constructor: 0x38e808283e59 <JSFunction Object (sfi = 0x38e80820b0f1)> - dependent code: 0x38e8080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 DebugPrint: 0x38e80810a4f5: [JS_OBJECT_TYPE] // Demote fast attributes to dictionary attributes (slow attributes) after delete attribute operation -map: 0x38e8082c56d9 <Map(HOLEY_ELEMENTS)> [DictionaryProperties] - prototype: 0x38e808284245 <Object map = 0x38e8082c21b9> - elements: 0x38e80800222d <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x38e80810a9c1 <NameDictionary[101]> - All own properties (excluding elements): { string2: 2 (data, dict_index: 5, attrs: [WEC]) string8: 8 (data, dict_index: 11, attrs: [WEC]) string5: 5 (data, dict_index: 8, attrs: [WEC]) string7: 7 (data, dict_index: 10, attrs: [WEC]) string0: 0 (data, dict_index: 3, attrs: [WEC]) string6: 6 (data, dict_index: 9, attrs: [WEC]) string9: 9 (data, dict_index: 12, attrs: [WEC]) a: 1 (data, dict_index: 1, attrs: [WEC]) string3: 3 (data, dict_index: 6, attrs: [WEC]) string4: 4 (data, dict_index: 7, attrs: [WEC]) b: 2 (data, dict_index: 2, attrs: [WEC]) } 0x38e8082c56d9: [Map] - type: JS_OBJECT_TYPE - instance size: 12 - inobject properties: 0 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - dictionary_map - may_have_interesting_symbols - back pointer: 0x38e8080023b5 <undefined> - prototype_validity cell: 0x38e8082044fd <Cell value= 1> - instance descriptors (own) #0: 0x38e8080021c1 <Other heap object (STRONG_DESCRIPTOR_ARRAY_TYPE)> - prototype: 0x38e808284245 <Object map = 0x38e8082c21b9> - constructor: 0x38e808283e59 <JSFunction Object (sfi = 0x38e80820b0f1)> - dependent code: 0x38e8080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0Copy the code

When we do not delete the named attribute, but instead set the attribute value to null or undefined, we do not change the way the attribute is stored.

Code best Practices

  • Try not to delete named attributes. Instead, set the attribute value tonullorundefinedTo deal with
  • When allocating objects, try to carry out fine-grained partitioning (do not add a large number of attributes to an object) to ensure that in-object properties can meet the needs of development and improve performance

Element attributes

Element attributes and named attributes are stored in separate data structures, so they have different performance when adding, deleting, and accessing attributes. Elements (arrays in this article can be understood as elements) can use various array. prototype methods (such as POP and slice), and these methods are generally suitable for accessing attributes of contiguous addresses, so elements are generally stored in linear arrays in V8. The storage mode of linear structure can make the access of attributes quickly find the pointer address of the attribute value through a one-to-one linear relationship, so the element attributes usually do not need to hide the class to maintain the pointer offset address information of attributes like named attributes. Of course, because there are many types of elements, not all elements adopt a linear storage structure, which needs to be analyzed according to the specific type of elements.

Reminder: The linear structure here refers to the one-to-one linear relationship between elements and data, such as arrays (sequential storage structure), linked lists (linked storage structure), queues and stacks. Nonlinear structure refers to the one-to-many relationship between elements and data, such as two-dimensional or multidimensional array, tree, graph, HashMap (combination of array and linked list, using array outside and linked list inside), HashTable, etc. Most of the nonlinear structures that we’ll talk about in JavaScript are hashes. If you don’t know Hash, check out the comics: What is a HashMap?

Element type

Let’s look at some sample code:

const array = [1, 2, 3]; %DebugPrint(array); Array. Push (4.56); %DebugPrint(array); array.push('x'); %DebugPrint(array); // Convert to sparse array array[10] = 10; %DebugPrint(array); Delete array[10]; %DebugPrint(array);Copy the code

The following information is displayed:

v8-debug --allow-natives-syntax ./index.js

DebugPrint: 0x1f660810a519: [JSArray]
 - map: 0x1f66082c3a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x1f660828c139 <JSArray[0]>
 - elements: 0x1f660829345d <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 3
 - properties: 0x1f660800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x1f6608004bb5: [String] in ReadOnlySpace: #length: 0x1f6608204255 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x1f660829345d <FixedArray[3]> {
           0: 1
           1: 2
           2: 3
 }
0x1f66082c3a41: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 // const array = [1, 2, 3];
 // 元素种类:PACKED_SMI_ELEMENTS
 - elements kind: PACKED_SMI_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x1f66080023b5 <undefined>
 - prototype_validity cell: 0x1f66082044fd <Cell value= 1>
 - instance descriptors #1: 0x1f660828c5ed <DescriptorArray[1]>
 - transitions #1: 0x1f660828c609 <TransitionArray[4]>Transition array #1:
     0x1f66080057c9 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x1f66082c3ab9 <Map(HOLEY_SMI_ELEMENTS)>

 - prototype: 0x1f660828c139 <JSArray[0]>
 - constructor: 0x1f660828bed5 <JSFunction Array (sfi = 0x1f6608210501)>
 - dependent code: 0x1f66080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x1f660810a519: [JSArray]
 - map: 0x1f66082c3ae1 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x1f660828c139 <JSArray[0]>
 - elements: 0x1f660810a589 <FixedDoubleArray[22]> [PACKED_DOUBLE_ELEMENTS]
 - length: 4
 - properties: 0x1f660800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x1f6608004bb5: [String] in ReadOnlySpace: #length: 0x1f6608204255 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x1f660810a589 <FixedDoubleArray[22]> {
           0: 1
           1: 2
           2: 3
           3: 4.56
        4-21: <the_hole>
 }
0x1f66082c3ae1: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 // array.push(4.56);
 // 元素种类:PACKED_DOUBLE_ELEMENTS
 - elements kind: PACKED_DOUBLE_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x1f66082c3ab9 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x1f66082044fd <Cell value= 1>
 - instance descriptors #1: 0x1f660828c5ed <DescriptorArray[1]>
 - transitions #1: 0x1f660828c639 <TransitionArray[4]>Transition array #1:
     0x1f66080057c9 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x1f66082c3b09 <Map(HOLEY_DOUBLE_ELEMENTS)>

 - prototype: 0x1f660828c139 <JSArray[0]>
 - constructor: 0x1f660828bed5 <JSFunction Array (sfi = 0x1f6608210501)>
 - dependent code: 0x1f66080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x1f660810a519: [JSArray]
 - map: 0x1f66082c3b31 <Map(PACKED_ELEMENTS)> [FastProperties]
 - prototype: 0x1f660828c139 <JSArray[0]>
 - elements: 0x1f660810a641 <FixedArray[22]> [PACKED_ELEMENTS]
 - length: 5
 - properties: 0x1f660800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x1f6608004bb5: [String] in ReadOnlySpace: #length: 0x1f6608204255 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x1f660810a641 <FixedArray[22]> {
           0: 0x1f660810a6c5 <HeapNumber 1.0>
           1: 0x1f660810a6b9 <HeapNumber 2.0>
           2: 0x1f660810a6ad <HeapNumber 3.0>
           3: 0x1f660810a6a1 <HeapNumber 4.56>
           4: 0x1f66082933f1 <String[1]: #x>
        5-21: 0x1f660800242d <the_hole>
 }
0x1f66082c3b31: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 // array.push('x');
 // 元素种类: PACKED_ELEMENTS
 - elements kind: PACKED_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x1f66082c3b09 <Map(HOLEY_DOUBLE_ELEMENTS)>
 - prototype_validity cell: 0x1f66082044fd <Cell value= 1>
 - instance descriptors #1: 0x1f660828c5ed <DescriptorArray[1]>
 - transitions #1: 0x1f660828c669 <TransitionArray[4]>Transition array #1:
     0x1f66080057c9 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x1f66082c3b59 <Map(HOLEY_ELEMENTS)>

 - prototype: 0x1f660828c139 <JSArray[0]>
 - constructor: 0x1f660828bed5 <JSFunction Array (sfi = 0x1f6608210501)>
 - dependent code: 0x1f66080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x1f660810a519: [JSArray]
 - map: 0x1f66082c3b59 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x1f660828c139 <JSArray[0]>
 - elements: 0x1f660810a641 <FixedArray[22]> [HOLEY_ELEMENTS]
 - length: 11
 - properties: 0x1f660800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x1f6608004bb5: [String] in ReadOnlySpace: #length: 0x1f6608204255 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x1f660810a641 <FixedArray[22]> {
           0: 0x1f660810a6c5 <HeapNumber 1.0>
           1: 0x1f660810a6b9 <HeapNumber 2.0>
           2: 0x1f660810a6ad <HeapNumber 3.0>
           3: 0x1f660810a6a1 <HeapNumber 4.56>
           4: 0x1f66082933f1 <String[1]: #x>
         5-9: 0x1f660800242d <the_hole>
          10: 10
       11-21: 0x1f660800242d <the_hole>
 }
0x1f66082c3b59: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 // array[10] = 10;
 // 元素种类:HOLEY_ELEMENTS
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x1f66082c3b31 <Map(PACKED_ELEMENTS)>
 - prototype_validity cell: 0x1f66082044fd <Cell value= 1>
 - instance descriptors (own) #1: 0x1f660828c5ed <DescriptorArray[1]>
 - prototype: 0x1f660828c139 <JSArray[0]>
 - constructor: 0x1f660828bed5 <JSFunction Array (sfi = 0x1f6608210501)>
 - dependent code: 0x1f66080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x1f660810a519: [JSArray]
 - map: 0x1f66082c3b59 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x1f660828c139 <JSArray[0]>
 - elements: 0x1f660810a641 <FixedArray[22]> [HOLEY_ELEMENTS]
 - length: 11
 - properties: 0x1f660800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x1f6608004bb5: [String] in ReadOnlySpace: #length: 0x1f6608204255 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x1f660810a641 <FixedArray[22]> {
           0: 0x1f660810a6c5 <HeapNumber 1.0>
           1: 0x1f660810a6b9 <HeapNumber 2.0>
           2: 0x1f660810a6ad <HeapNumber 3.0>
           3: 0x1f660810a6a1 <HeapNumber 4.56>
           4: 0x1f66082933f1 <String[1]: #x>
        5-21: 0x1f660800242d <the_hole>
 }
0x1f66082c3b59: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 // delete array[10];
 //  元素种类:HOLEY_ELEMENTS
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x1f66082c3b31 <Map(PACKED_ELEMENTS)>
 - prototype_validity cell: 0x1f66082044fd <Cell value= 1>
 - instance descriptors (own) #1: 0x1f660828c5ed <DescriptorArray[1]>
 - prototype: 0x1f660828c139 <JSArray[0]>
 - constructor: 0x1f660828bed5 <JSFunction Array (sfi = 0x1f6608210501)>
 - dependent code: 0x1f66080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
Copy the code

From the printed information, you can see that the following element types already appear in the code:

  • PACKED_SMI_ELEMENTS: Small Intergers
  • PACKED_DOUBLE_ELEMENTS: double floating-point numbers (those familiar with C should be familiar with float and double)
  • PACKED_ELEMENTS: regular elements (cannot represent any other type)
  • HOLEY_ELEMENTS: Sparse regular elements

On top of that, V8 actually has 21 element types, and the optimizations for each type of element may vary. It can also be found from the printed information above:

  • Chrome V8 assigns an element type to each element
  • The types of arrays can be divided into sparse arrays (PACKED) and dense arrays (HOLEY)
  • Element types can be changed during operation, but can only be converted from a specific type to a general type, and the conversion process is irreversible

For example, after delete array[10], the sparse array above becomes a dense array again, but the element type is still HOLEY_ELEMENTS and will not revert to PACKED_ELEMENTS. HOLEY is more specific to PACKED, and SMI is more specific to DOUBLE.

PACKED, HOLEY and DICTIONARY elements

Sparse elements are less efficient than dense elements, as in the following example:

const array = [1, 2, 3];
%DebugPrint(array);

array[6] = 10000;
%DebugPrint(array);
Copy the code
v8-debug --allow-natives-syntax ./index.js DebugPrint: 0x25970810a4a9: [JSArray] - map: 0x2597082c3a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties] - prototype: 0x25970828c139 <JSArray[0]> - elements: 0x25970829344d <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)] - length: 3 - properties: 0x25970800222d <FixedArray[0]> - All own properties (excluding elements): { 0x259708004bb5: [String] in ReadOnlySpace: #length: 0x259708204255 <AccessorInfo> (const accessor descriptor), location: descriptor } - elements: 0x25970829344d <FixedArray[3]> { 0: 1 1: 2 2: 3 } 0x2597082c3a41: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: PACKED_SMI_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x2597080023b5 <undefined> - prototype_validity cell: 0x2597082044fd <Cell value= 1> - instance descriptors #1: 0x25970828c5ed <DescriptorArray[1]> - transitions #1: 0x25970828c609 <TransitionArray[4]>Transition array #1: 0x2597080057c9 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x2597082c3ab9 <Map(HOLEY_SMI_ELEMENTS)> - prototype: 0x25970828c139 <JSArray[0]> - constructor: 0x25970828bed5 <JSFunction Array (sfi = 0x259708210501)> - dependent code: 0x2597080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 DebugPrint: 0x25970810a4a9: [JSArray] - map: 0x2597082c3ab9 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties] - prototype: 0x25970828c139 <JSArray[0]> - elements: 0x25970810a4b9 <FixedArray[26]> [HOLEY_SMI_ELEMENTS] - length: 7 - properties: 0x25970800222d <FixedArray[0]> - All own properties (excluding elements): { 0x259708004bb5: [String] in ReadOnlySpace: #length: 0x259708204255 <AccessorInfo> (const accessor descriptor), location: descriptor } - elements: 0x25970810a4b9 <FixedArray[26]> {0: 1 1: 2 2: 3 // 3-5 is thin, waste memory space 3-5: 0x25970800242D <the_hole> 6:10000 7-25: 0x25970800242d <the_hole> } 0x2597082c3ab9: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: HOLEY_SMI_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x2597082c3a41 <Map(PACKED_SMI_ELEMENTS)> - prototype_validity cell: 0x2597082044fd <Cell value= 1> - instance descriptors #1: 0x25970828c5ed <DescriptorArray[1]> - transitions #1: 0x25970828c621 <TransitionArray[4]>Transition array #1: 0x2597080057c9 <Symbol: (elements_transition_symbol)>: (transition to PACKED_DOUBLE_ELEMENTS) -> 0x2597082c3ae1 <Map(PACKED_DOUBLE_ELEMENTS)> - prototype: 0x25970828c139 <JSArray[0]> - constructor: 0x25970828bed5 <JSFunction Array (sfi = 0x259708210501)> - dependent code: 0x2597080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0Copy the code

Because HOLEY type sparse elements will have extra checks and expensive searches on the prototype chain during traversal, and it can be found that hidden classes maintain the chain structure after the change from dense elements to sparse elements. When the sparser span is large, Chrome V8 will change the way array elements are stored, changing the linear storage structure to a non-linear storage structure to save memory consumption (e.g. 3-5 is wasted memory space above). For example:

const array = [1, 2, 3];
%DebugPrint(array);

array[10000] = 10000;
%DebugPrint(array);
Copy the code
v8-debug --allow-natives-syntax ./index.js DebugPrint: 0x30cc0810a4ad: [JSArray] - map: 0x30cc082c3a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties] - prototype: 0x30cc0828c139 <JSArray[0]> - elements: 0x30cc0829344d <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)] - length: 3 - properties: 0x30cc0800222d <FixedArray[0]> - All own properties (excluding elements): { 0x30cc08004bb5: [String] in ReadOnlySpace: #length: 0x30cc08204255 <AccessorInfo> (const accessor descriptor), location: descriptor } - elements: 0x30cc0829344d <FixedArray[3]> { 0: 1 1: 2 2: 3 } 0x30cc082c3a41: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: PACKED_SMI_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x30cc080023b5 <undefined> - prototype_validity cell: 0x30cc082044fd <Cell value= 1> - instance descriptors #1: 0x30cc0828c5ed <DescriptorArray[1]> - transitions #1: 0x30cc0828c609 <TransitionArray[4]>Transition array #1: 0x30cc080057c9 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x30cc082c3ab9 <Map(HOLEY_SMI_ELEMENTS)> - prototype: 0x30cc0828c139 <JSArray[0]> - constructor: 0x30cc0828bed5 <JSFunction Array (sfi = 0x30cc08210501)> - dependent code: 0x30cc080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 DebugPrint: 0x30cc0810a4ad: [JSArray] - map: 0x30cc082c7b69 <Map(DICTIONARY_ELEMENTS)> [FastProperties] - prototype: 0x30cc0828c139 <JSArray[0]> - elements: 0x30cc0810a505 <NumberDictionary[28]> [DICTIONARY_ELEMENTS] - length: 10001 - properties: 0x30cc0800222d <FixedArray[0]> - All own properties (excluding elements): { 0x30cc08004bb5: [String] in ReadOnlySpace: #length: 0x30cc08204255 <AccessorInfo> (const accessor descriptor), location: Descriptor} // Dictionary storage mode - elements: 0x30CC0810A505 <NumberDictionary[28]> {-max_number_key: 10000 0: 1 (data, dict_index: 0, attrs: [WEC]) 2: 3 (data, dict_index: 0, attrs: [WEC]) 10000: 10000 (data, dict_index: 0, attrs: [WEC]) 1: 2 (data, dict_index: 0, attrs: [WEC]) } 0x30cc082c7b69: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: DICTIONARY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x30cc082c3b59 <Map(HOLEY_ELEMENTS)> - prototype_validity cell: 0x30cc082044fd <Cell value= 1> - instance descriptors (own) #1: 0x30cc0828c5ed <DescriptorArray[1]> - prototype: 0x30cc0828c139 <JSArray[0]> - constructor: 0x30cc0828bed5 <JSFunction Array (sfi = 0x30cc08210501)> - dependent code: 0x30cc080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0Copy the code

You can see that the type of elements has changed from PACKED_SMI_ELEMENTS to DICTIONARY_ELEMENTS, the storage mode has changed from linear continuous address storage to non-linear storage, and the hidden classes have been re-created without any relationship to the original hidden classes of intensive elements. In fact, can be divided into fast element (certain types of elements) and slow element (a dictionary element), the elements in the dictionary will be health, values and descriptor information storage elements (need to note that at this point is not stored in the hidden class attribute in the meta information), in the example above is’ 10000 ‘health elements will set up the default descriptor attribute, So if you use custom descriptors to define element attributes, V8 degrades the element to slow.

It should be noted that the access performance of the two storage modes depends on the number of elements. When the number of elements is large, N operations are required to read the NTH index in the linear storage mode, while the number of operations is less than N in the Hash storage mode. Although it requires a secondary lookup (assuming the data in the hash table is relatively evenly distributed). Of course, in the case of small amounts of data, Hash performance is not as fast as a linear lookup because it requires a second lookup and the first lookup computs the Hash index.

Use descriptors to define element attributes

The sample code looks like this:

const array = [1, 2, 3];
%DebugPrint(array);

Object.defineProperty(array, '4', {
    value: 4,
    enumerable:false,
    configurable: false,
    writable: false
});
%DebugPrint(array);
Copy the code
v8-debug --allow-natives-syntax ./index.js

DebugPrint: 0x2dd0810a511: [JSArray]
 - map: 0x02dd082c3a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x02dd0828c139 <JSArray[0]>
 - elements: 0x02dd08293489 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 3
 - properties: 0x02dd0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x2dd08004bb5: [String] in ReadOnlySpace: #length: 0x02dd08204255 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x02dd08293489 <FixedArray[3]> {
           0: 1
           1: 2
           2: 3
 }
0x2dd082c3a41: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: PACKED_SMI_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x02dd080023b5 <undefined>
 - prototype_validity cell: 0x02dd082044fd <Cell value= 1>
 - instance descriptors #1: 0x02dd0828c5ed <DescriptorArray[1]>
 - transitions #1: 0x02dd0828c609 <TransitionArray[4]>Transition array #1:
     0x02dd080057c9 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x02dd082c3ab9 <Map(HOLEY_SMI_ELEMENTS)>

 - prototype: 0x02dd0828c139 <JSArray[0]>
 - constructor: 0x02dd0828bed5 <JSFunction Array (sfi = 0x2dd08210501)>
 - dependent code: 0x02dd080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

DebugPrint: 0x2dd0810a511: [JSArray]
 - map: 0x02dd082c7c09 <Map(DICTIONARY_ELEMENTS)> [FastProperties]
 - prototype: 0x02dd0828c139 <JSArray[0]>
 - elements: 0x02dd0810a63d <NumberDictionary[28]> [DICTIONARY_ELEMENTS]
 - length: 5
 - properties: 0x02dd0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x2dd08004bb5: [String] in ReadOnlySpace: #length: 0x02dd08204255 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - elements: 0x02dd0810a63d <NumberDictionary[28]> {
   - requires_slow_elements
   2: 3 (data, dict_index: 0, attrs: [WEC])
   0: 1 (data, dict_index: 0, attrs: [WEC])
   1: 2 (data, dict_index: 0, attrs: [WEC])
   4: 4 (data, dict_index: 0, attrs: [___])
 }
0x2dd082c7c09: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: DICTIONARY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x02dd082c3b59 <Map(HOLEY_ELEMENTS)>
 - prototype_validity cell: 0x02dd082044fd <Cell value= 1>
 - instance descriptors (own) #1: 0x02dd0828c5ed <DescriptorArray[1]>
 - prototype: 0x02dd0828c139 <JSArray[0]>
 - constructor: 0x02dd0828bed5 <JSFunction Array (sfi = 0x2dd08210501)>
 - dependent code: 0x02dd080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
Copy the code

It can be found that defining element attributes by attribute descriptors will change the storage structure of elements from linear structure to nonlinear storage structure, and create new hidden class information.

Tip: Imagine the side effects of using Object.defineProperty on array elements in Vue 2.

Code Practice Guide

Due to limited energy, there is no more research on the type conversion characteristics of element attributes. Here are the following instructions based on the above forms of expression:

  • Avoid creating sparse arrays, which can lead to the performance penalty of prototype chain searches during traversal, and sparse arrays are always marked HOLEY_? _ELEMENTS, which adversely affects V8 optimization
  • Try to avoid element type conversions and try to keep element types specific, such as PACKED_SMI_ELEMENTS, which will help V8 do better performance optimization. Examples include the TypeFeedback technique described earlier and the Inline Cache technique described later
  • Some dense array-like objects can be converted to real arrays for processingArray.prototype.slice.call(arrayLike, 0). Before actually callingArray.prototype.forEachV8 will be highly optimized for elements.