原生事件代理实现

发表于 JS 分类,标签:

版本信息

作者时间主要变更内容链接
吴惟刚2022/02/25事件代理http://wuweigang.com/?id=365

概述

事件代理对前端来说非常常见,尤其是jquery时代,我们为了提升性能,我们会尽量减少给每个元素绑定事件,而是给需要绑定事件的元素的父级绑定事件。

原理

将事件绑定在父级元素上, 通过判断event.target对象,判断当前点击的元素是否是目标元素或者是目标元素的后代元素,决定是否触发事件回调函数。

示例

分三段,html,css,js, 大家可以复制代码进行浏览器中测试。
operateProxyEvent() 函数,大家可以参考

  • html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>原生事件代理的实现</title>
   <link rel="stylesheet" href="./index.css">
</head>
<body>
   <h1>原生事件代理的实现</h1>
   <div class="fd-proxy-contain"  id="jsProxyContain">
       <ul>
           <li class="jsItem"  data-number="1">
               <h3>条目一标题</h3>
               <p><span>条目一内容</span></p>
           </li>
           <li class="jsItem"  data-number="2">
               <h3>条目二标题</h3>
               <p><span>条目二内容</span></p>
           </li>
           <li class="jsItem" data-number="3">
               <h3>条目三标题</h3>
               <p><span>条目三内容</span></p>
           </li>
       </ul>

       <div>
           <p>这里不是目标节点</p>
           <p>这里不是目标节点</p>
           <p>这里不是目标节点</p>
           <p>这里不是目标节点</p>
           <p>这里不是目标节点</p>
       </div>
   </div>
   <script src="./index.js"></script>
</body>
</html>

  • css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
* {
   margin: 0;
   padding: 0;
   box-sizing: border-box;
}

ul, li {
   list-style: none;
}

li, .fd-proxy-contain > div > p {
   padding: 20px;
   font: 16px/1.5em "Microsoft YaHei";
   border-bottom: 1px solid #ccc;
}

.fd-proxy-contain > div > p:last-child{
   border: none;
}

h1 {
   line-height: 3em;
   text-align: center;
}

.fd-proxy-contain {
   display: flex;
   flex-flow: row nowrap;
   margin: 0 auto;
   width: 800px;
   border: 1px solid #ccc;
}

.fd-proxy-contain > ul {
   flex: 200px 0 0;
   border-right: 1px solid #ccc;
}

.fd-proxy-contain > div {
   flex: auto 1 1;
}

  • js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* @author  wuwg
* @createTime  20220225
* @param event {Object}  event 事件
* @param proxyContain {Object}  代理容器元素
* @param callback {function}  找到目标元素后的回调函数
*/
// 处理代理事件
function operateProxyEvent(event, proxyContain, callback) {
   // 代理容器
   const _proxyContain = proxyContain;
   // 是否继续查找
   let _loopFlag = false;
   // 【当前节点】
   let _currentNode = event.target;
   // 【目标节点】
   let _targetNode = null;
   // 循环查找目标元素
   do {
       // 判断 【当前节点】是不是【目标节点】
       const _isTargetFlag = _currentNode &&
           // 这里判断的是class 为 item
           _currentNode.classList.contains('jsItem');
       if (_isTargetFlag) {
           // 打断循环
           _loopFlag = false;
           //  【目标节点】= 【当前节点】
           _targetNode = _currentNode;
       }
       // 是否继续循环, 找到目标,那么不再循环, 否则判断点击元素是否在代理容器里,在的话继续循环查找。
       _loopFlag = _isTargetFlag ? false : _proxyContain.contains(_currentNode);
       // 【当前节点】往上推一级,等于当前节点的【父节点】
       _currentNode = _loopFlag ? _currentNode.parentNode : _currentNode;
   } while (_loopFlag);
   // 如果是目标节点
   if (_targetNode) {
       typeof callback === 'function' && callback(_targetNode);
   } else {
       console.warn('非代理的目标节点: 不触发点击事件');
   }
}

// 代理容器
const _proxyContain = document.querySelector('#jsProxyContain');
// 给代理容器绑定事件
_proxyContain.addEventListener('click', (event) => {
   // 处理代理事件
   operateProxyEvent(event, _proxyContain, (target) => {
       const _dataItemNumber = target.dataset.number;
       alert('是代理的目标节点,当前节点的number值是:' + _dataItemNumber);
   });
});

  • 视图
    左侧列表会触发事件,右侧列表不会触发事件!




image.png

0 篇评论

发表我的评论