JavaScript-事件委托详解

使用 Element.matches 精确匹配

如果改变下 HTML 成:

  • item 1
  • item 2
  • item 3
  • ......
  • item n
// ...... 代表中间还有未知数个 li

这里,我们想把 #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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

点击这里领取Web前端开发经典面试题

目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

点击这里领取Web前端开发经典面试题

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值