文章介绍了这个工具库中的第二个依赖,这个工具库主要完成了一个简易的事件订阅发布器。这次介绍一下clipboard.js
源码中的最后一个依赖的轻型工具库,这个工具库主要用来对dom
的事件绑定进行一层封装,支持事件委托delegate
,和jquery
的写法非常类似,源码简洁且清晰易懂,对理解事件绑定模型和原理非常有帮助。
快速用法
const listen = require('good-listener')
good-listener
支持常见的三种方式来绑定事件
-
node
节点事件绑定
var logo = document.getElementById('logo');listen(logo, 'click', function(e) { console.log(e);});
-
nodeList
多个节点遍历事件绑定
var anchors = document.querySelectorAll('a');listen(anchors, 'click', function(e) { console.log(e);});
- 字符串形式的委托事件绑定(默认委托的对象
document.body
)
listen('.btn', 'click', function(e) { console.log(e);});
代码实现
good-listener
的实现大致如下
function listen(target, type, callback) { if (is.node(target)) { return listenNode(target, type, callback); } else if (is.nodeList(target)) { return listenNodeList(target, type, callback); } else if (is.string(target)) { return listenSelector(target, type, callback); } else { throw new TypeError(`argument must be a String, HTMLElement, HTMLCollection, or NodeList`); }}
对于node
和nodeList
节点的实现比较简单,一笔带过,这里主要分析一下delegate
委托的实现。
node节点事件绑定
判断一个元素是否是node
节点,是通过构造函数constructor
和nodeType
属性来判断的。
value !== undefined && value instanceof HTMLElement && value.nodeType === 1;
listenNode
实现,返回了一个对象,为事件绑定扩展了一个取消绑定的方法destroy
function listenNode(node, type, callback) { node.addEventListener(type, callback); return { destroy: function() { node.removeEventListener(type, callback); } }}
nodeList多个节点遍历事件绑定
判断一个元素是否是nodeList
节点,是通过构造函数constructor
和length
属性来判断的。且需要保证类数组中的元素都是有效的dom
节点
var type = Object.prototype.toString.call(value)value !== undefined && (type === '[object NodeList]' || type === '[object HTMLCollection]') && ('length' in value) && (value.length === 0 || exports.node(value[0]))
listenNodeList
实现, 遍历一下类数组,一次执行事件绑定即可
function listenNodeList(nodeList, type, callback) { Array.prototype.forEach.call(nodeList, function(node) { node.addEventListener(type, callback); }); return { destroy: function() { Array.prototype.forEach.call(nodeList, function(node) { node.removeEventListener(type, callback); }); } }}
事件委托的实现
简单回顾一下事件委托,比如有以下场景
1
2
3
4
需要把p
标签的点击事件委托到父元素div
上面,jquery
中的写法是
$('.wrapper').on('click', '.delegate', function(e) {...})
如果我们自己去实现,是不能单纯的去判断点击的target
是不是包含delegate
,因为点击的元素有可能是子元素span
,所以子元素可以通过冒泡找到delegate
,那么委托也是应该可以触发的。
再介绍一个方法,下面要用到,Element
的原型上有一个方法,接受一个selector
字符串,如果element
元素被指定的字符串选择,那么返回true
.
1.首先来模拟这个冒泡的过程
// document节点const DOCUMENT_NODE_TYPE = 9function closet (element, selector) { while(element && element.nodeType !== DOCUMENT_NODE_TYPE) { if (typeof element.matches === 'function' && element.matches(selector)) { return element } element = element.parentNode }}
这个函数就是判断点击的元素是否能够向上冒泡(不断的获取父元素)匹配到指定的委托元素。
结合上面例子就是span
能否向上找到delegate
2.使用代理模式为事件的回调函数封装一层判断逻辑,当符合委托逻辑的时候,才去执行回调函数
并且为event
对象添加一个属性delegateTarget
,这样在event
中可以拿到对应的三个对象 -
this
和e.currentTarget
是被委托的元素 -
e.delegateTarget
是委托元素 -
e.target
是点击元素
function listener(element, selector, type, callback) { return function(e) { e.delegateTarget = closest(e.target, selector); if (e.delegateTarget) { callback.call(element, e); } }}
3.delegate内部封装
let _delegate = (element, selector, type, callback, useCapture) => { // 将element和selector封装一层 let listenerFn = listener.apply(this, arguments) element.addEventListener(type, listenerFn, useCapture) return { destroy () { element.removeListener(type, listenerFn, useCapture) } }}
4.delegate
分为两种情况,被委托可以是一个dom
元素,也可以是css
选择器字符串,如下所示 delegate(document.body, '.btn', 'click', function(e) { console.log(e.delegateTarget);}, false);
delegate('.container', '.btn', 'click', function(e) { console.log(e.delegateTarget);}, false);
分别处理即可
let delegate = (elements, selector, type, callback, useCapture) => { if (typeof elements.addEventListener === 'function') { return _delegate.apply(null, arguments) } if (typeof elements === 'string') { elements = document.querySelectorAll(elements) } return Array.prototype.map.call(elements, function (element) { return _delegate(element, selector, type, callback, useCapture) })}
结语
本次介绍的good-listener
这个工具库的用法和源码分析,介绍了node
,nodeList
的事件绑定封装的实现,尤其探求了一下委托
的实现方法,用不多的代码实现了兼容性很好的delegate
,至此clipboard.js
的依赖都介绍完了,下一篇文章会整合代码,将完整的clipboard.js
实现展现出来。