preface

What do you think the following 🌰 will output?

Const obj1 = {100, 'one hundred', 2, '2', 7: 'seven'} const obj2 = {a: 'one hundred', b: '2', c: 'seven'} const obj3 = {1.3: 'one hundred', '1' : '2', 2: '7'} console.log('obj1 key', object.keys (obj1)) console.log('obj2 key',Object.keys(obj2)) console.log('obj3 ') key',Object.keys(obj3))Copy the code

Object.keys()In what order are the values returned?

First the conclusion:

Keys internally performs a different sort logic depending on the type of the attribute name key. There are three cases:

  1. If the property name is of typeNumber, thenObject.keysThe return value is based onkeySort from smallest to largest
  1. If the property name is of typeString, thenObject.keysThe return value is sorted in ascending order by the time the property was created.
  1. If the property name is of typeSymbolSo the logic is the sameStringThe same

What happens behind the call to object.keys

Call link analysis

First look at MDN:

The object.keys () method returns an array of a given Object’s own enumerable properties in the same order as the property names would be returned if the Object were iterated through normally. I didn’t say in what order, but click on the MDN API link to see the specification definition

The specification defines it as follows:

  1. Call ToObject(O) to assign the result to the variable obj

  2. Call EnumerableOwnPropertyNames (obj, “key”) results will be assigned to the variable nameList

  3. Call CreateArrayFromList(nameList) to get the final result

Object property list is passedEnumerableOwnPropertyNamesTo obtain,OwnPropertyKeysEventually returnedOrdinaryOwnPropertyKeys

The sort of key is inOrdinaryOwnPropertyKeys, here is a summary:

  1. Create an empty list to hold keys

  2. Stores all valid array indexes in ascending order

    1. Valid array index values are positive integers, negative numbers and floating-point numbers when handled as strings
  3. Store all string type indexes in ascending order by attribute creation time

  4. Store all Symbol type indexes in ascending order by attribute creation time

  5. Returns the keys

For example

Const testObj = {} testObj [1] = 'testObj [1] =' testObj [1.1] = 'testObj (' 2') = 'testObj/' c' = 'testObj = [' b']  '' testObj['a'] = '' testObj[Symbol(1)] = '' testObj[Symbol('a')] = '' testObj[Symbol('b')] = '' testObj['d'] = '' // 1, 2, 1,1.1, c, b, a, d the console. The log (Object. The keys (testObj))Copy the code

V8 handling of object properties

Fast Properties in V8 explains in great detail how V8 handles JavaScript object properties internally. Here are the main references for this section:

View a memory snapshot in Chrome

Run a program on the console and hit Memory-dot to get a snapshot

Structure of objects in V8

An object consists of three main parts: the hidden class, Property, and Element

Hidden classes are used to describe the structure of an object.Property 和 ElementUsed to store attributes of an object. They differ mainly in whether the key name can be indexed.

The Property and the Element

Const nameObj = {1:'a',1:'b'} // Named attributes are stored in the area pointed to by the Properties pointer {'name':'banggan','age':22}Copy the code

In fact, it was designed to meet the ECMA specification requirements. As described in the specification, indexable attributes should be sorted in ascending order by index value size, while named attributes are sorted in ascending order by the order in which they were created.

For example, 🌰

var a = { 1: "a", 2: "b", "first": 1, 3: "c", "second": 2 }

var b = { "second": 2, 1: "a", 3: "c", 2: "b", "first": 1 }

console.log(a) 
// { 1: "a", 2: "b", 3: "c", first: 1, second: 2 }

console.log(b)
// { 1: "a", 2: "b", 3: "c", second: 2, first: 1 }
Copy the code

As can be seen from the above example:

  • The attributes of the index are sorted in ascending order by index value size, while the named attributes are sorted in ascending order by the order they were created.
  • Whether indexable attributes or named attributes are declared first, they always appear in the same order in the console

Take a look at using snapshots as an example

Function Foo1() {} var b1 = new Foo1() var b1 = new Foo1() a1.name = 'a1.name' B1. text = 'BBB text' a1[1] = 'aaa' a1[2] = 'aaa 'Copy the code

Both a and B have named attributes of name and text, but A also has two indexable attributes. As can be seen from the snapshot, the indexable attributes are stored in elements, while A and B have the same structure

Different ways to store named attributes

There are three different stores of named attributes in V8: in-object, fast, and slow.

In-object property

Can pay attention to every visit to a property, all need to pass through a layer of indirection to access, it reduces the access efficiency, in order to solve this problem, the V8 and introduces a called object attribute, as the name implies, it will certain attributes directly stored in the first layer of the object, its access is the fastest, as shown in the figure below:

Note, however, that the properties in the object only store the general properties, the sorting properties remain the same. The number of general attributes is less than a certain number, depending on the size of the object when it is initialized.

Fast attributes require an additional addressing time for properties, followed by linear lookups consistent with attributes within the object.

Slow attribute

In addition to the in-object properties, fast properties, there is also a slow property.

Why does it have the slow property? Fast attribute to visit soon, but if you want to add or delete a large number of attributes, from the object, may produce a lot of time and memory overhead to maintain the hidden class, so much or adding, deleting, repeatedly attribute will be regular properties of storage model from linear structure into a dictionary, is reduced to slow properties, Because the information for a slow attribute is no longer stored in a hidden class, it is slower to access than a fast attribute, but can be added and removed efficiently.

An 🌰

function Foo2() {}

var a2 = new Foo2()
var b2 = new Foo2()
var c2 = new Foo2()

for (var i = 0; i < 10; i ++) {
  a2[new Array(i+'a).join('a')] = 'aaa'
}

for (var i = 0; i < 12; i ++) {
  b2[new Array(i+2).join('b')] = 'bbb'
}

for (var i = 0; i < 30; i ++) {
  c2[new Array(i+2).join('c')] = 'ccc'
}
Copy the code

A2, B2, and C2 have 10, 12, and 30 properties, respectively: When the object is full, it will be stored in the order of creation under Properties in the form of quick properties. Fast attributes require one more property addressing time than in-object attributes, followed by a linear lookup consistent with in-object attributes. In contrast to B (fast), the indexes in Properties become random numbers, meaning that the object has become a hash access structure.

Hidden classes

Because JavaScript is can modify the attributes of the object at runtime, so at the time of the query will be slow, each time you access an attribute need to pass an extra layer of access, and static language such as c + + before the statement object need to define the structure of this object (shape), compiled after the shape of each object are fixed, Therefore, the access will be faster because the offset of the property is known.

V8 took the idea of applying this mechanism to JavaScript objects, so it introduced the mechanism of hidden classes. It is easy to understand that hidden classes describe the shape of the object, including the location of each attribute, so that queries are much faster

A few things to add about hidden classes:

  1. The first field of the object points to its hidden class.
  1. If two objects are exactly the same shape, they share the same hidden class.
  1. When attributes are added to or removed from an object, a new corresponding hidden class is created and redirected to it.
  1. V8 has a transformation tree mechanism to create hidden classes, but this article doesn’t cover that, so you can read it here.

The magic delete operation
function Foo3() {}

var a3 = new Foo3()
var b3 = new Foo3()

for (var i = 1; i < 8; i ++) {
  a3[new Array(i+1).join('a')] = 'aaa'
  b3[new Array(i+1).join('b')] = 'bbb'
}

delete a3.a
Copy the code

A3 and B3 are themselves in-object properties. As you can see from the snapshot, after a3. A is removed, A3 becomes slow and is returned to hash storage.

However, if we were to remove the attributes in the reverse order in which they were added, the situation would be different.

Now we delete b3.bbbbbbb in reverse order. As you can see, B3 did not return the hash store. This is why using DELETE to delete an object property is not recommended in practice, as it is easy to change the way objects are stored in V8, resulting in performance degradation.

conclusion

  • Attributes are classified into named attributes and indexable attributes, and named attributes are stored inPropertiesIndexable attributes are stored inElementsIn the.
  • Named attributes can be stored in three different ways: in-object attributes, fast attributes, and slow attributes. The former two are accessed through linear lookup, and the slow attributes are accessed through hash storage.
  • Always initialize object members in the same order to take advantage of the same hidden classes, thereby improving performance.
  • Adding or deleting indexable attributes does not cause hidden class changes, and sparse indexable attributes are degraded to hash storage.
  • The delete operation may change the structure of the object, causing the engine to downgrade the object to hash storage, which is not good for V8 optimization and should be avoided whenever possible (objects do not degrade to hash storage when attributes are deleted in the opposite direction of attribute addition).