前端面试题——事件代理 delegate 的实现(一)

当我们需要监听某个元素被点击的时候,我们会给这个元素添加事件监听器

事件监听器只会绑定到当前 DOM 中已有的元素上,而实际需求中,往往会有临时渲染出来的元素也需要监听事件?那这时候改怎么办呢?难道在每次页面渲染结束后,都再绑定一次事件监听吗?

什么是 事件代理

从字面上来理解,“代理”即将自己要做的事交给别人来做。那么这边的“事件代理”又是什么呢?同样的,如果原本有某个事件 A 是元素 a 的事件,但是 A 事件并不直接由 a 来完成,而是转交给元素 b 来监听并完成

要想彻底理解事件代理的原理,我们需要先了解两个概念:事件捕获事件冒泡

当有下面一段页面结构存在,并且点击了a标签时:

<html>
    <body>
        <div>
            <a></a>
        </div>
    </body>
</html>

事件捕获

事件的触发顺序为 html => body => div => a,即一个事件将会从最不精确的对象 (html) 开始触发,一直到最精确的一个对象 (a),四个字概括就是:由外而内

事件冒泡

与事件捕获刚好相反,触发顺序为 a => div => body => html,由最精确的对象开始触发,一直到最不精确的对象,是 由内而外 式的

绑定事件到 dom 元素

想要绑定事件到元素上,需要先来看下事件监听器的 api :

el.addEventListener(event, action, useCapture)

在这个 api 中,第一个参数是触发事件,如点击事件 ‘click’;第二个参数是事件触发后需要执行的方法;第三个参数的含义是是否使用事件捕获,将该值设为true表示在事件捕获阶段触发

一个对象绑定一个事件

当需要向上文页面结构中的a标签添加一个点击事件时,可以用如下代码:

const aTag = document.querySelector('a')
aTag.addEventListener('click', e => {
    console.log(e)
}) // 默认冒泡事件机制

当该标签被点击时将会执行我们定义好的回调函数,这个回调函数有一个参数 event(此处为了方便将参数名定义为了 e),打印出来会发现这个参数包括了很多很多信息,比如当我们在做拖动效果的时候可能会用到的位置信息的参数等。但是其中的绝大多数参数在这次的事件代理中不会使用到

仔细查看 event 中的属性,会发现有一个属性名为target,我们知道不管是冒泡还是捕获,都会有一个最精确的对象和一个最不精确 的对象,而这个target就是这个最精确的对象,也即我们实际点击到的元素——a

(好奇的小伙伴也可以试着加上最后一个参数true,将事件机制改为捕获,回调中的参数 e 依然会有target,且指向最精确的元素)

那么如果我们将点击事件绑定在a的父级div上再点击a,此时的e.target又会是什么呢?依然是a,因为我们实际点击的对象并没有改变,a依然是最精确的对象。除非点击在a以外的div区域,才会使e.target变成div

多个对象绑定一个事件

<ul>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
</ul>

但是实际情况是,我们往往需要向多个相同的节点中添加事件,而addEventListener这个 api 只能向一个元素添加事件。所以当出现上面这种结构,并且需要向每一个li添加事件时,我们可能会这样做:

// 获取到所有的元素,返回一个包含所有对应元素的类数组
const els = document.querySelectorAll('li')
// 遍历每个元素并未其添加点击事件
Array.prototype.forEach.call(els, el => {
    el.addEventListener('click', e => {
        console.log(e)
    })
})

会发现上面这种方法是通过遍历的方式一个一个地向元素添加所需要的事件,这种方式不得不说是我们非常不愿意见到的

同时,我们只能够向已经存在的元素添加事件,如果通过异步请求数据后重新渲染了页面,新增的节点该如何处理呢?难道再次执行一遍相同的操作吗?显然,这很愚蠢

几遍我们已经确定不会有更多的新元素进入页面,我们也不该使用这种方式达到我们的目的

实现一个事件代理

用过 jQuery 的童鞋都知道,通过$(bigEl).on('click', el, () => {...})的方式添加事件绑定,并且在页面重新渲染后也不需要再次绑定,这就是事件代理的好处:一次绑定,处处通用

那么这是如何实现的呢?这就要用到我们上面说到的target属性了。原理如下:

我们先将需要执行的事件回调绑定在一个必然存在的元素上,比如body,又比如文档节点document,当我们指定的事件,如click发生时,我们就能够获得target属性

由于target指向的永远是我们实际点击到的元素,那么我们就可以通过这个元素来判断是不是我们所需要被点击的元素,从而判断是否执行回调或执行哪一个回调

这样,即使页面上重新增加了元素,我们也不需要对这些元素进行再次绑定

以上面的多li结构为例,代码如下:

document.addEventListener('click', e => {
    if (e.target && e.target.nodeName.toUpperCase == 'LI') {
        // do something u want
        alert('u clicked li element')
    }
})

当然,target不仅能获取到标签名,也能够获取到 class、id 和众多属性,方便我们进行更加精确的判断

通过这种方式,我们还能实现:一个元素绑定多个事件,多个元素绑定一个事件,多个事件绑定多个元素

而所有的事件事实上并不是直接与其对应的元素关联的,而是统一挂载在一个元素上,如 document / body,通过它们间接的触发了事件,实现了事件代理

事件的卸载

jQuery 中提供了一个off方法将已经绑定的事件卸载,通过上面的学习,我们也可以实现这样的事件卸载功能,只是需要绕点弯,至于如何实现,我们下周再说~~~

【结束语:这是最近几周以来最长的一篇,希望你们喜欢!动动你们的食指,不要吝啬你们的喜欢哦!】

扫码关注前端周记公众号,感谢您的支持!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 总结: 鼠标事件 1.click与dbclick事件$ele.click()$ele.click(handler(...
    阿r阿r阅读 1,598评论 2 10
  • 第1章 鼠标事件 1-1 jQuery鼠标事件之click与dbclick事件 用交互操作中,最简单直接的操作就是...
    mo默22阅读 1,263评论 0 6
  • 今天早上是补昨天的笔记,昨天太累,加上晚上宿舍的灯又坏了,回到家后看了25分钟书,想躺床上写笔记,没写一会儿就...
    傲娇小猫咪阅读 220评论 0 0
  • 秋日,一枝残艳的月季花 /深山老林(千年桃妖) 蝉绝声 蛙缄口 夏日已落幕 黄昏 谁在叹息 一枝残艳的月季花 ...
    深山老林千年桃妖阅读 220评论 3 2