从Quill源码中去探索Object.keys的顺序

453 阅读4分钟

前言

最近在负责Quill的项目,产品提出有个需求,需要在编辑器按enter键换行的时候发起请求,同时还要记录当前光标所在的行。

但是Quill一旦按enter键换行,光标就变了,就找不到换行前的那行。所以就得要求我们发请求必须在换行之前,然后把该行记录下来,后面就可以处理了。

问题出现

Quill可以添加键盘的处理函数,通过quill.addBinding函数或者在quill的keyboard配置中,比如监听enter

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

官方文档有说明

image.png

这个添加自定义键盘事件是会插入到当前默认的键盘事件后面。

如果想插入到当前默认的键盘事件前面执行,那么应该怎么做?

看了官方文档,没有找到有说明。

于是,就去看看源码看看是怎么添加。

class Keyboard extends Module {
  constructor(quill, options) {
    super(quill, options);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {  // 重点
      if (name === 'list autofill' &&
          quill.scroll.whitelist != null &&
          !quill.scroll.whitelist['list']) {
        return;
      }
      if (this.options.bindings[name]) {
        this.addBinding(this.options.bindings[name]);
      }
    })
    // ...省略部分代码
   }
   addBinding(key, context = {}, handler = {}) {
    let binding = normalize(key);
    // ...省略部分代码
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }
 }   

这里Quill是用Object.keys方法取到所有的键,然后再调用addBinding方法把键值push到bindings中。

所以我就在想

Object.keys返回的数组的key是有顺序的么?

是按照定义的先后顺序返回的么?

能不能改变顺序以实现我的需求?

Object.keys

mdn上面的描述:

Object.keys() 只会遍历自身可以枚举的属性,并返回数组。数组属性的顺序和正常循环遍历该对象时返回的顺序一致。

这个顺序一致让人更加疑惑?到底是以什么顺序返回?

继续查阅资料,我们打开ecma262标准的文档,找到Object.keys部分

Object.keys`O` )

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

首先尝试把参数转成对象,接着调用EnumerableOwnProperties方法把对象传入,返回keyList,这个应该是由key返回的list。

继续看EnumerableOwnProperties方法的定义

EnumerableOwnProperties ( O, kind )

1. Let ownKeys be ? O.[[OwnPropertyKeys]]().
2. 省略部分代码

EnumerableOwnProperties方法内部继续调用了对象的O.[[OwnPropertyKeys]]方法,返回ownKeys

然后继续看[[OwnPropertyKeys]]的定义,内部又调用了OrdinaryOwnPropertyKeys方法。

真的是一层套一层,层层不止呀。

[[OwnPropertyKeys]] ( )

1. Return OrdinaryOwnPropertyKeys(O).

最后我们看到OrdinaryOwnPropertyKeys的定义,找到了内部的逻辑。

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.

我简单翻译一下:

流程大概是这样:

  1. 先定义一个空数组,叫keys
  2. 接着遍历对象,如果key是数组的索引,就升序把这些索引push到数组中(不是定义的顺序)
  3. 如果key是字符串且不是数组的索引,就按照创建时定义的顺序push到数组中
  4. 如果key是个Symbol,就按照创建时定义的顺序push到数组中
  5. 返回keys

可以看到文档如果key是数组的索引,也就是正整数,会优先排序,接着是字符串,Symbol。

因为Object.keys不会返回Symbol类型,这里就不做讨论了。

我们可以通过例子来看看

Object.keys({name: '答案cp3', age:18, gender: 'boy'})  // ['name', 'age', 'gender']
Object.keys({name: '答案cp3', 13:18, gender: 'boy'})  //  ['13', 'name', 'gender']
Object.keys({name: '答案cp3', 13:18, '6': 'boy'})  //  ['6', '13', 'name']

如果你的key都是字符串,且不是数字,就按照定义的顺序返回,如果有数字,包括字符串数字,就优先返回数字,再返回定义的顺序。

但是这里要注意一点: key要求是数组的索引,所以必须要是正整数,如果你是浮点数,则会当作字符串处理,会按照定义的顺序返回。

Object.keys({name: '答案cp3', 13:18, 6.1: 'boy'})  // ['13', 'name', '6.1']  

13仍然排在前面,但是name和6.1是按照定义的顺序返回,这里要注意一下。

问题解决

所以我们想要自己定义的enter键函数在默认的键盘事件前面执行,把key改成正整数即可。

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

总结

我之前一直认为Object.keys的返回是无序的,这次通过看Quill源码学到Object.keys返回的顺序,然后解决了需求问题,过程还行,继续加油。

最后总结一下Object.keys返回的顺序规则:

  1. 如果有数字,且是正整数,则优先返回
  2. 其它字符串类型(包括浮点数)则按照定义的顺序返回
  3. 最后是Symbol类型,也是按照定义的顺序返回。但是Object.keys不返回Symbol类型,这里可以忽略。

感谢你的阅读。