使用 Element.matches 精确匹配
如果改变下 HTML 成:
- item 1
- item 2
- item 3
- ......
- item n
这里,我们想把 #list 元素下的 li 元素(并且它的 class 为 class-1)的点击事件委托代理到 #list 之上;
如果通过上述的方法我们还需要在 if (target.nodeName.toLocaleLowerCase === 'li')
判断之中在加入一个判断 target.nodeName.className === 'class-1'
;
但是如果想像 CSS 选择其般做更加灵活的匹配的话,上面的判断未免就太多了,并且很难做到灵活性,这里可以使用 Element.matches API 来匹配;
Element.matches API 的基本使用方法: Element.matches(selectorString),selectorString 既是 CSS 那样的选择器规则,比如本例中可以使用 target.matches(‘li.class-1’),他会返回一个布尔值,如果 target 元素是标签 li 并且它的类是 class-1 ,那么就会返回 true,否则返回 false;
当然它的兼容性还有一些问题,需要 IE9 及以上的现代化浏览器版本;
我们可以使用 Polyfill 来解决兼容性上的问题:
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (–i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
加上 Element.matches 之后就可以来实现我们的需求了:
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (–i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
document.getElementById(‘list’).addEventListener(‘click’, function (e) {
// 兼容性处理
var event = e || window.event;
var target = event.target || event.srcElement;
if (target.matches(‘li.class-1’)) {
console.log('the content is: ', target.innerHTML);
}
});
函数封装
在应对更多场景上我们可以把事件代理的功能封装成一个公用函数,这样就可以重复利用了。
结合上面的例子来实现一个函数 eventDelegate,它接受四个参数:
- [String] 一个选择器字符串用于过滤需要实现代理的父层元素,既事件需要被真正绑定之上;
- [String] 一个选择器字符串用于过滤触发事件的选择器元素的后代,既我们需要被代理事件的元素;
- [String] 一个或多个用空格分隔的事件类型和可选的命名空间,如 click 或 keydown.click ;
- [Function] 需要代理事件响应的函数;
**这里就有几个关键点:
- 对于父层代理的元素可能有多个,需要一一绑定事件;
- 对于绑定的事件类型可能有多个,需要一一绑定事件;
- 在处理匹配被代理的元素之中需要考虑到兼容性问题;
- 在执行所绑定的函数的时候需要传入正确的参数以及考虑到 this 的问题;
function eventDelegate (parentSelector, targetSelector, events, foo) {
// 触发执行的函数
function triFunction (e) {
// 兼容性处理
var event = e || window.event;
var target = event.target || event.srcElement;
// 处理 matches 的兼容性
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (–i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
// 判断是否匹配到我们所需要的元素上
if (target.matches(targetSelector)) {
// 执行绑定的函数,注意 this
foo.call(target, Array.prototype.slice.call(arguments));
}
}
// 如果有多个事件的话需要全部一一绑定事件
events.split(‘.’).forEach(function (evt) {
// 多个父层元素的话也需要一一绑定
Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
$p.addEventListener(evt, triFunction);
});
});
}
优化
当被代理的元素不是目标元素的时候,既选择器 targetSelector 所指向的元素不是 event.target (事件目标阶段指向的元素)的时候,这时候就需要层层遍历 event.target 的 parentNode 去匹配 targetSelector 了,直到 parentSelector。
比如:
- item 1
- item 2
还是把 li 的事件代理到 #list 之上,但这时候会发现 event.target 指向的是 li span,因此需要层层遍历外层元素去匹配,直到到代理事件的函数,我们可以用 event.currentTarget 来获取到代理事件的函数;
完整函数:
function eventDelegate (parentSelector, targetSelector, events, foo) {
// 触发执行的函数
function triFunction (e) {
// 兼容性处理
var event = e || window.event;
// 获取到目标阶段指向的元素
var target = event.target || event.srcElement;
// 获取到代理事件的函数
var currentTarget = event.currentTarget;
// 处理 matches 的兼容性
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (–i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
// 遍历外层并且匹配
while (target !== currentTarget) {
// 判断是否匹配到我们所需要的元素上
if (target.matches(targetSelector)) {
var sTarget = target;
// 执行绑定的函数,注意 this
foo.call(sTarget, Array.prototype.slice.call(arguments))
}
target = target.parentNode;
}
}
// 如果有多个事件的话需要全部一一绑定事件
events.split(‘.’).forEach(function (evt) {
// 多个父层元素的话也需要一一绑定
Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
$p.addEventListener(evt, triFunction);
});
});
}
使用函数:
eventDelegate(‘#list’, ‘li’, ‘click’, function () { console.log(this); });
点击后可以看到 console 出了 #list li
元素对象;
局限性
当然,事件委托也是有一定局限性的;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。
目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。