Explore the order of Object.keys from the Quill source code

Foreword

Recently, I was in charge of the Quill project. There was a requirement for the product to initiate a request when the editor presses the enter key to change the line, and at the same time record the line where the current cursor is located.

But once Quill presses the enter key to change the line, the cursor changes, and the line before the line break cannot be found. So we have to ask us to send the request before the new line, and then record the line, which can be processed later.

A problem occurred

Quill can add keyboard processing functions, through the quill.addBinding function or in quill’s keyboard configuration, such as listening to the enter key

new Quill('#editor', {<!-- -->
  modules: {<!-- -->
    keyboard: {<!-- -->
      bindings: {<!-- -->
        'enter':{<!-- -->
           key: 'enter',
           handler () {<!-- -->
             //todo
           }
         }
      }
    }
  }
})

The official documentation explains

image.png

This added custom keyboard event will be inserted after the current default keyboard event.

If you want to insert before the current default keyboard event, what should you do?

After reading the official documentation, I couldn’t find any instructions.

So, let’s take a look at the source code to see how it is added.

class Keyboard extends Module {<!-- -->
  constructor(quill, options) {<!-- -->
    super(quill, options);
    this.bindings = {<!-- -->};
    Object.keys(this.options.bindings).forEach((name) => {<!-- --> // Important
      if (name === 'list autofill' & amp; & amp;
          quill.scroll.whitelist != null & &
          !quill.scroll.whitelist['list']) {<!-- -->
        return;
      }
      if (this. options. bindings[name]) {<!-- -->
        this.addBinding(this.options.bindings[name]);
      }
    })
    // ...omitting part of the code
   }
   addBinding(key, context = {<!-- -->}, handler = {<!-- -->}) {<!-- -->
    let binding = normalize(key);
    // ...omitting part of the code
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }
 }

Here Quill uses the Object.keys method to get all the keys, and then calls the addBinding method to push the key value to bindings.

so i was thinking

Are the keys of the array returned by Object.keys in order?

Is it returned in the order defined?

Is it possible to change the order to achieve my needs?

Object.keys

The description above on mdn:

Object.keys() will only traverse the properties that can be enumerated by itself, and return an array. The order of the array properties is the same as the order returned when looping through the object normally.

This order is consistent to make people more confused? In what order are they returned?

Continue to check the information, we open the ecma262 standard document and find the Object.keys part

Object.keys (?`O`?)

1. Let obj be ? ToObject(O).
2. Let keyList be ? EnumerableOwnProperties(obj, key).
3. Return CreateArrayFromList(keyList).

First try to convert the parameter into an object, then call the EnumerableOwnProperties method to pass in the object, and return the keyList, which should be the list returned by the key.

Continue to see the definition of the EnumerableOwnProperties method

EnumerableOwnProperties ( O, kind )

1. Let ownKeys be ? O.[[OwnPropertyKeys]]().
2. Omit part of the code

The EnumerableOwnProperties method continues to call the object’s O.[[OwnPropertyKeys]] method internally, returning ownKeys.

Then continue to look at the definition of [[OwnPropertyKeys]], and the OrdinaryOwnPropertyKeys method is called internally.

It’s really layer upon layer, layer upon layer.

[[OwnPropertyKeys]] ( )

1. Return OrdinaryOwnPropertyKeys(O).

Finally, we saw the definition of OrdinaryOwnPropertyKeys and found the internal logic.

OrdinaryOwnPropertyKeys ( O )

1. Let keys be a new empty List.
2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
a. Append P to keys.
3. For each own property key P of O such that P is a String and P is not an array index, in ascending chronological order of property creation, do
a. Append P to keys.
4. For each own property key P of O such that P is a Symbol, in ascending chronological order of property creation, do
a. Append P to keys.
5. Return keys.

I simply translate:

The process is roughly like this:

  1. First define an empty array called keys.
  2. Then traverse the object, if the key is an array index, push these indexes into the array in ascending order (not the defined order)
  3. If the key is a string and not an index of an array, it will be pushed into the array according to the order defined when it was created
  4. If the key is a Symbol, it will be pushed into the array in the order defined when it was created
  5. Return keys.

You can see that if the document key is an index of an array, that is, a positive integer, it will be sorted first, followed by strings and Symbols.

Because Object.keys does not return the Symbol type, we will not discuss it here.

We can see by example

Object.keys({<!-- -->name: 'answer cp3', age:18, gender: 'boy'}) // ['name', 'age\ ', 'gender']
Object.keys({<!-- -->name: 'answer cp3', 13:18, gender: 'boy'}) // ['13', 'name', \ 'gender']
Object.keys({<!-- -->name: 'answer cp3', 13:18, '6': 'boy'}) // ['6', '13 ', 'name']

If your keys are all strings and not numbers, return them in the defined order. If there are numbers, including string numbers, return the numbers first, and then return the defined order.

But here is one thing to pay attention to: The key is required to be an index of the array, so it must be a positive integer. If you are a floating point number, it will be treated as a string, which will follow the definition returned in order.

Object.keys({<!-- -->name: 'answer cp3', 13:18, 6.1: 'boy'}) // ['13', 'name\ ', '6.1']

13 is still at the front, but name and 6.1 are returned in the order defined, so pay attention here.

Problem Solved

So we want to execute the enter key function defined by ourselves before the default keyboard event, just change the key to positive integer.

new Quill('#editor', {<!-- -->
  modules: {<!-- -->
    keyboard: {<!-- -->
      bindings: {<!-- -->
        13:{<!-- -->
           key: 'enter',
           handler () {<!-- -->
             //todo
           }
         }
      }
    }
  }
})

Summary

I always thought that the return of Object.keys was out of order. This time, I learned the order of return of Object.keys by looking at the Quill source code, and then solved the demand problem. The process It’s okay, keep going.

Finally, summarize the order rules returned by Object.keys:

  1. If there is a number and it is a positive integer, it will be returned first
  2. Other string types (including floats) are returned in the order defined
  3. The last is the Symbol type, which is also returned in the order defined. But Object.keys does not return the Symbol type, which can be ignored here.

Thanks for reading.