前言
最近在负责Quill的项目,产品提出有个需求,需要在编辑器按enter键换行的时候发起请求,同时还要记录当前光标所在的行。
但是Quill一旦按enter键换行,光标就变了,就找不到换行前的那行。所以就得要求我们发请求必须在换行之前,然后把该行记录下来,后面就可以处理了。
问题出现
Quill可以添加键盘的处理函数,通过quill.addBinding
函数或者在quill的keyboard
配置中,比如监听enter键
new Quill('#editor', {
modules: {
keyboard: {
bindings: {
'enter':{
key: 'enter',
handler () {
// todo
}
}
}
}
}
})
官方文档有说明
这个添加自定义键盘事件是会插入到当前默认的键盘事件后面。
如果想插入到当前默认的键盘事件前面执行,那么应该怎么做?
看了官方文档,没有找到有说明。
于是,就去看看源码看看是怎么添加。
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.
我简单翻译一下:
流程大概是这样:
- 先定义一个空数组,叫
keys
。 - 接着遍历对象,如果key是数组的索引,就升序把这些索引push到数组中(不是定义的顺序)
- 如果key是字符串且不是数组的索引,就按照创建时定义的顺序push到数组中
- 如果key是个
Symbol
,就按照创建时定义的顺序push到数组中 - 返回
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
返回的顺序规则:
- 如果有数字,且是正整数,则优先返回
- 其它字符串类型(包括浮点数)则按照定义的顺序返回
- 最后是
Symbol
类型,也是按照定义的顺序返回。但是Object.keys
不返回Symbol
类型,这里可以忽略。
感谢你的阅读。