前言
最近一直在做Quill的项目,有时间也研究了一下Quill的源码,从中也学到一些知识,今天把这些知识总结下,算是对这些知识的巩固。
正文
事件
quill的api有提供3个事件,editor-change
,text-change
,selection-change
。
editor-change
editor-change
是总事件,就是触发text-change
或selection-change
事件之前要先触发editor-change
,然后从里面再分发出去。
let args = [Emitter.events.SELECTION_CHANGE, clone(this.lastRange), clone(oldRange), source];
this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args); // 触发`editor-change`事件
if (source !== Emitter.sources.SILENT) {
this.emitter.emit(...args); // 再触发`selection-change`事件
}
可以看到触发SELECTION_CHANGE
(也就是selection-change
)事件之前,先触发 EDITOR_CHANGE
事件,并把Emitter.events.SELECTION_CHANGE
等参数传入。
selection-change
selection-change
就是选择事件,只要你聚焦或者失焦,或者选择文本,就会触发该事件。
text-change
然后文本改变的时候会触发text-change
事件。比如正常输入,工具栏设置格式,快捷键操作,只要来源于用户,就会触发。
那么是怎么监听这些操作的呢?
我去看了开发者工具的element的事件绑定,发现它们监听了以下事件
第一个是绑定的空函数,可以排除掉
this.domNode.addEventListener('DOMNodeInserted', function() {});
第二个和第三个这两个事件比较少用,查了一下资料,这里可以排除。
第四个keydown事件 是监听按键,设置格式,比如ctrl + B
是加粗等等
第五个scroll事件,也可以排除掉。
那么可以得出结论,它不是通过监听事件触发的。
思索无果,只能打断点来调试一下了。
可以看到它的调用栈,它是有包装了一个modify方法,通过modify方法触发事件,然后继续看最前面的调用栈,终于发现了答案。
最前面的调用栈,可以看到是通过MutationObserver
实现的。
下面总结下上面的知识点。
compositionstart/compositionend
这两个事件是文本合成事件,如果你输入中文的时候,一开始就会触发compositionstart事件,输入结束选择中文后,触发compositionend事件。
这两个事件在中文输入时非常实用,正常我们在input框是要监听input事件,但是你可以看看
触发input事件是实时输出,而不是选择了中文再输出。
如果我们使用了compositionstart/compositionend
事件,就可以解决。
let composing = false
input.addEventListener('compositionstart',(e) =>{
composing = true
})
input.addEventListener('compositionend',(e) =>{
composing = false
console.log('compositionend',e.target.value)
})
input.addEventListener('input',(e) =>{
if(composing) return
console.log('input', e.target.value)
})
compositionstart只会在刚输入中文时触发,这时可以设置个标志位,阻止input事件,然后在compositionend事件触发的时候,输出value。
因为compositionstart/compositionend
事件只在输入中文时触发,如果输入字母,数字则不会触发,所以要配合input事件一起实现,可以拿到输入的value。
另外要注意⚠️:输入时先触发compositionstart事件,接着是input事件,再触发compositionend事件。
上面的图片input事件最后才从触发,是因为前面有return。
但是现在一般不需要这样写,因为现在都会用vue,它会在v-model的语法糖里做处理,拿到输入后的value再传给使用者。
const vModelText = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
// ...省略部分代码
if (!lazy) {
addEventListener(el, 'compositionstart', onCompositionStart);
addEventListener(el, 'compositionend', onCompositionEnd);
}
}
};
function onCompositionStart(e) {
e.target.composing = true;
}
function onCompositionEnd(e) {
const target = e.target;
if (target.composing) {
target.composing = false;
target.dispatchEvent(new Event('input'));
}
}
MutationObserver
这个api是监听dom的变化的,比如属性变化,文本内容变化,子节点变化,都能监听到。
所以quill是使用了MutationObserver来触发事件。
而且,同时改变内容或者属性,不会触发多次,只会触发一次,然后以数组的形式返回变化的值。
语法:
const observer = new MutationObserver(callback)
observer.observe(dom, option) // 监听
dom为你监听的目标,option传对象,可以传:
- childList 监听子节点变化,比如插入子节点,删除子节点
- attributes 监听属性变化
- characterData 监听文本内容变化, 监听元素节点不会触发,需要监听文本节点,可以配合textContent使用
- subtree 所有后代节点的变化
- attributeOldValue 是否监听attributes的旧值
- characterDataOldValue 是否监听characterData的旧值
- attributeFilter 监听特定的属性,数组形式,比如['id','class'],不填则监听所有属性
dom只要有变化,就会执行callback,参数有2个,一个是以数组形式的变化,一个是实例对象
举个例子,监听子节点变化
const observer = new MutationObserver((mutationsList, observer) => {
console.log(mutationsList, observer)
})
observer.observe($0, {
childList: true
})
$0.innerHTML = '答案cp3' // 触发callback
监听dom文本变化
const observer = new MutationObserver((mutationsList, observer) => {
console.log(mutationsList, observer)
})
observer.observe($0.firstChild, {
characterData: true
})
$0.firstChild.textContent = '答案cp3' // 触发callback
除了上面observe
方法,还有disconnect
方法,这个是断开监听方法,一旦断开,后面dom再改变就不会触发callback
。
observer.disconnect()
$0.innerHTML = 'cp3' // 不会触发callback
总结
看源码的过程虽然有时候很枯燥,但是也能学到很多东西,遇到不懂的地方,多去查查资料,记笔记,总结,很快你就会有进步。
继续加油⛽️