JavaScript棘手代码第三部分

JavaScript代码系列(5部分)
JavaScript棘手代码第一部分
在JavaScript中处理设备方向
JavaScript棘手代码第二部分
JavaScript棘手代码第三部分
数组变异和非变异方法
问题:嵌套盒子的事件处理
我们有3个嵌套的盒子(box1、box2、box3)。当点击每个盒子时,将其ID记录到控制台。点击内部盒子时,不应该触发外部盒子的点击事件。
这个问题是关于什么?
这个问题是关于JavaScript中的事件处理和事件传播,特别是如何控制嵌套HTML元素的点击事件流。
核心问题是如何防止点击内部元素时也触发其父(外部)元素的点击事件。这是Web开发中的一个常见场景,你希望特定操作只在点击特定元素时发生,而不是其容器。
解决方案:有很多方法可以实现,但更优化的方式是将所有div包装在一个id为container父div中。
然后在父元素上添加事件监听器。
完整代码实现
HTML文件
  1. <div id="container">
  2. <div id="box1" class="box">
  3. box1
  4. <div id="box2" class="box">
  5. box2
  6. <div id="box3" class="box">box3div>
  7. div>
  8. div>
  9. div>
html
CSS文件
  1. .box {
  2. min-width: 200px;
  3. width: fit-content; /* 匹配div内容宽度 */
  4. border: 1px solid #000;
  5. padding: 15px;
  6. }

  7. #box1 {
  8. background-color: yellow;
  9. padding: 10px;
  10. }
  11. #box2 {
  12. background-color: cyan;
  13. padding: 10px;
  14. }
  15. #box3 {
  16. background-color: red;
  17. }
css
JavaScript文件
  1. document.getElementById('container').addEventListener('click', event => {
  2. // 当点击容器元素或其任何子元素时执行
  3. if (event.target.classList.contains('box')) {
  4. /* 检查点击是否只发生在具有box类的元素上,因为我们不需要处理容器的其他子元素的点击,除了box。*/

  5. console.log('id is: ', event.target.id);
  6. event.stopPropagation(); // 防止事件在点击子元素时传播到父元素
  7. }
  8. })
javascript
关键概念解释
1. 事件委托(Event Delegation)
在父元素上监听事件,而不是在每个子元素上
利用事件冒泡机制
减少事件监听器数量,提高性能
2. event.target
指向实际被点击的元素
不是事件监听器绑定的元素
用于确定具体是哪个子元素被点击
3. event.stopPropagation()
阻止事件继续冒泡到父元素
防止触发父元素的事件处理器
确保只有被点击的元素响应事件
4. classList.contains()
检查元素是否包含特定CSS类
用于过滤需要处理的元素
提供灵活的事件处理条件
性能优化考虑
重要提示:正如我之前所说,有很多方法可以解决这个问题,我们也可以为每个单独的div添加事件监听器,因为现在只有3个div,但如果数量更多,我们就需要循环等。
所以为了避免循环,我们选择了父元素。我们也可以选择<body>作为父元素,但那样脚本会搜索更大范围的代码,会耗时。因此我们将事件监听器的范围缩小到一个小的区域,即id为container的div
替代解决方案
方案1:为每个盒子单独添加事件监听器
  1. document.getElementById('box1').addEventListener('click', (event) => {
  2. console.log('id is: box1');
  3. event.stopPropagation();
  4. });

  5. document.getElementById('box2').addEventListener('click', (event) => {
  6. console.log('id is: box2');
  7. event.stopPropagation();
  8. });

  9. document.getElementById('box3').addEventListener('click', (event) => {
  10. console.log('id is: box3');
  11. event.stopPropagation();
  12. });
javascript
方案2:使用事件捕获
  1. document.getElementById('container').addEventListener('click', event => {
  2. if (event.target.classList.contains('box')) {
  3. console.log('id is: ', event.target.id);
  4. }
  5. }, true); // 第三个参数true启用捕获模式
javascript
方案3:使用事件委托的现代方法
  1. document.getElementById('container').addEventListener('click', event => {
  2. const box = event.target.closest('.box');
  3. if (box) {
  4. console.log('id is: ', box.id);
  5. event.stopPropagation();
  6. }
  7. });
javascript
实际应用场景
导航菜单:处理嵌套菜单项的点击
表单组件:处理复杂表单中的交互
卡片布局:处理卡片内部的按钮点击
模态框:处理模态框内容的交互
拖拽组件:处理拖拽区域内的元素
最佳实践
使用事件委托:减少事件监听器数量
合理的作用域:选择合适的事件监听器绑定元素
性能考虑:避免在大量元素上绑定事件
代码可维护性:使用类名或数据属性进行过滤
浏览器兼容性:考虑不同浏览器的事件处理差异
调试技巧
  1. // 添加调试信息
  2. document.getElementById('container').addEventListener('click', event => {
  3. console.log('Event target:', event.target);
  4. console.log('Current target:', event.currentTarget);
  5. console.log('Event phase:', event.eventPhase);
  6. if (event.target.classList.contains('box')) {
  7. console.log('Box clicked:', event.target.id);
  8. event.stopPropagation();
  9. }
  10. });
javascript
📌📌 更多JavaScript面试相关代码请访问:
关键概念总结
事件委托:在父元素上监听子元素事件
事件冒泡:事件从子元素向上传播到父元素
事件捕获:事件从父元素向下传播到子元素
stopPropagation():阻止事件传播
target vs currentTarget:实际触发元素 vs 事件监听器绑定元素
扩展练习
练习1:动态添加元素的事件处理
  1. // 动态添加新盒子
  2. function addNewBox() {
  3. const container = document.getElementById('container');
  4. const newBox = document.createElement('div');
  5. newBox.className = 'box';
  6. newBox.id = 'box' + (document.querySelectorAll('.box').length + 1);
  7. newBox.textContent = newBox.id;
  8. container.appendChild(newBox);
  9. }

  10. // 事件委托自动处理新添加的元素
  11. document.getElementById('container').addEventListener('click', event => {
  12. if (event.target.classList.contains('box')) {
  13. console.log('Clicked:', event.target.id);
  14. }
  15. });
javascript
练习2:条件事件处理
  1. document.getElementById('container').addEventListener('click', event => {
  2. if (event.target.classList.contains('box')) {
  3. const boxId = event.target.id;
  4. // 根据不同的盒子执行不同的操作
  5. switch(boxId) {
  6. case 'box1':
  7. console.log('外层盒子被点击');
  8. break;
  9. case 'box2':
  10. console.log('中层盒子被点击');
  11. break;
  12. case 'box3':
  13. console.log('内层盒子被点击');
  14. break;
  15. }
  16. event.stopPropagation();
  17. }
  18. });
javascript