面试官:谈谈你对JS点击事件的理解和感悟?

简介: 前言这是一道比较经典的面试题,主要考察的是面试者原生 JS 的基础怎么样,以及对 JS 的一些理解。很多小伙伴可能大概会想到事件捕获和冒泡等,确实没有题。但是很多小伙伴都是模模糊糊,理解得不够透彻,今天我们就来梳理一下。

1.JS 有哪些事件?


虽然我们这里主要讲的是 JS 点击事件,但是我们还是很有必要了解一下 JS 里面有哪些事件。以便于让我们更好的理解事件在 JS 中扮演的角色以及过程。


JS 事件是用户与网页进行交互时发生的事情,比如用户点击网页,这就是一个点击事件,在网页上按下键盘,这就是键盘事件,还有很多其它的事件...


事件分类:


1.1 鼠标事件98.png

1.2 键盘事件99.png

1.3 窗口事件100.png

1.4 表单事件101.png


2.点击事件执行过程


用户点击按钮或者点击其它元素时,触发点击事件,这个过程并不是立即执行的,它是需要走一遍既定的流程的。


一个点击事件被触发,主要会走以下流程:

  1. 用户点击某个按钮,即触发点击事件。
  2. 浏览器从顶层 document 元素发出一个事件流。
  3. 事件流顺着 DOM 逐层向下查找触发事件的目标元素,这就是常说的事件捕获。
  4. 如果在查找过程中遇到了相同的事件,比如其它元素也绑定点击事件,那么默认不执行,继续往下找。
  5. 查找到目标元素后,就会执行目标元素所绑定的事件函数,这也就是常说的事件目标阶段
  6. 到这儿整个点击事件还没有完,浏览器会逆向执行该操作,也就是我们所说的事件冒泡
  7. 事件冒泡阶段,默认会触发相同的事件,也就是我们刚刚在事件捕获阶段,遇到相同的事件未执行,因为默认在这个冒泡阶段执行。


上面的流程大致就是一个点击事件的执行过程,这中过程也被称作 DOM 的事件模型。其实总结下来主要就三步:事件捕获阶段->事件目标阶段->事件冒泡阶段


看图理解:


102.png

看图就很好理解了,整个就是一个环形,分别对应了三个阶段。


3.代码演示

3.1 正常事件流


上一节我们说目标元素的点击事件函数是在浏览器找到它的时候执行的,在冒泡阶段会执行相同的事件函数,我们来验证一下。

示例代码:

<head>
  <style>
    #box1 {
      width: 200px;
      height: 200px;
      background-color: #ccc;
    }
    #box2 {
      width: 100px;
      height: 100px;
      background-color: yellow;
    }
    #box3 {
      width: 50px;
      height: 50px;
      background-color: blue;
    }
  </style>
</head>
<body>
  <div id="box1" onclick="box1Click">
    <div id="box2" onclick="box2Click">
      <div id="box3" onclick="box3Click"></div>
    </div>
  </div>
</body>
<script>
  // 获取 3 个元素节点
  let box1 = document.getElementById("box1");
  let box2 = document.getElementById("box2");
  let box3 = document.getElementById("box3");
  // 绑定点击事件
  box1.addEventListener("click", box1Click);
  box2.addEventListener("click", box2Click);
  box3.addEventListener("click", box3Click);
  // 事件处理函数
  function box1Click(){
    console.info("box1 被点击了!")
  }
  function box2Click(){
    console.info("box2 被点击了!")
  }
  function box3Click(){
    console.info("box3 被点击了!")
  }
</script>


上段代码中我们简单写了 3 个 div,它们是层级关系。然后分别给每个 div 都加上了一个点击事件,且定义了相应的处理事件函数。


页面展示:103.png

点击 box1,输出结果:104.png

这里只执行了 box1 的点击事件函数,因为事件流操作向下查找到目标元素后,便会向上开始冒泡,box2 和 box3 都不在整个过程中。

点击 box2,输出结果:105.png


点击 box2 时,box1 和 box2 都在事件流这整个过成功,超找到目标元素后,执行了 box2 的事件处理函数,然后向上冒泡,执行了 box1 的事件函数。

点击 box3,输出结果:

106.png




点击 box1,这个时候三个元素都处于事件流当中,都会在冒泡阶段执行对应的事件处理函数。


3.2 阻止冒泡


上一节中的正常事件流中,点击 box3,box1 和 box2 的点击事件函数也被执行了,在很多时候这可能不是我们想要的。我们只想点击 box1 的时候只执行 box1 的事件函数,这个时候我们就可以通过阻止冒泡来解决这个问题。


因为我们最开始说:整个事件流过程中,在冒泡阶段会执行在捕获阶段发现的相同的事件函数。所以我们阻止冒泡就可以解决问题。


示例代码:


function box3Click(e){
  console.info("e",e);
  e.stopPropagation();
  console.info("box3 被点击了!")
}

我们更改 box3de 事件处理函数,函数会默认接收一个事件参数,可以调用 stopPropagation()阻止冒泡。

点击 box3,输出结果:


107.png

这时候点击 box3,只有 box3 的点击事件函数被执行了。


3.3 捕获阶段执行


在未阻止冒泡的前提下,我们点击 box3,执行顺序依次是:box3->box2->box1。 这是因为所有事件都是在冒泡阶段执行的,那么如果我们想要执行的顺序改为:box1->box2->box3。这种情况我们该怎么做呢?


想要实现这种情况,我们得先了解 addEventListener 这个函数。


使用方法:


element.addEventListener(event, function, useCapture)

可以看到 addEventListener 函数还可以接收第三个参数,前两个参数大家都懂,这里就不用提了。


第三个参数 useCapture 接收一个 Boolean 值,它得作用主要如下:

  • true:事件在捕获阶段执行
  • false:默认值,事件在冒泡阶段执行

知道了第三个参数的含义以后,要实现我们的需求就简单了,我们只需要事件函数在捕获阶段执行即可。


示例代码:

box1.addEventListener("click", box1Click,true);
box2.addEventListener("click", box2Click,true);
box3.addEventListener("click", box3Click,true);


点击 box3,输出结果:108.png

这个时候执行顺序就是我们想要的了。


4.事件委托

事件委托是事件模型中的一个经典应用。


假如有这样一个需求:


有 100 个 li 标签,我们要给每个 li 标签都添加一个点击事件。


初学者常见的一个错误做法就是循环列表,然后分别添加点击事件。这种做法非常消耗内存的,而且也非常繁琐,假如有 100000 个列表呢?


这个时候我们思考一下事件模型,我们是不是可以事件模型中的冒泡来实现这个需求呢?我们将点击事件绑定在 li 标签的父级,当我们点击 li 标签是,在冒泡阶段就会触发 li 标签上一层的点击事件,这也算是变相给 li 标签添加了点击事件。


示例代码:

<body>
  <ul id="ul" onclick="ulClick">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
  </ul>
</body>
<script>
  // 事件委托
  let ul = document.getElementById("ul");
  ul.addEventListener("click", ulClick);
  function ulClick(e) {
    // 兼容性处理
    let event = e || window.event;
    let target = event.target || event.srcElement;
    // 判断是否匹配目标元素
    if (target.nodeName.toLocaleLowerCase === 'li') {
      console.log('the content is: ', target.innerHTML);
    }
    console.log("li 标签被点击了:", e.target.innerHTML);
  }
</script>


我们指在 ul 上添加了点击事件,点击 li 标签时,在冒泡阶段便会执行 ul 上的事件函数。

输出结果:109.png


5.一些兼容性问题


event 兼容性:

  • IE 下:event 对象有 srcElement 属性,但是没有 target 属性。
  • 火狐或谷歌下:event 对象有 target 属性,但是没有 srcElement 属性。


event || window.event:

  • 也是为了兼容 IE


阻止冒泡:

  • e.stopPropagation()
  • window.event.cancelBubble = true(兼容 IE)


万恶之源 IE。


总结


JS 的事件模型其实不复杂,只要理解三个阶段,一切便可迎刃而解,至于 EventLoop 事件循环,这不是我们这节要讨论的。


三个阶段:事件捕获、事件目标、事件冒泡。


想要视频学习,可以移步B站:小猪课堂

相关文章
|
3月前
|
前端开发 JavaScript 网络协议
前端最常见的JS面试题大全
【4月更文挑战第3天】前端最常见的JS面试题大全
77 5
|
15天前
|
JavaScript 前端开发
常见的JS面试题
【8月更文挑战第5天】 常见的JS面试题
40 3
|
17天前
|
JavaScript 前端开发 程序员
JS小白请看!一招让你的面试成功率大大提高——规范代码
JS小白请看!一招让你的面试成功率大大提高——规范代码
|
17天前
|
JavaScript 前端开发 UED
小白请看! 大厂面试题 :如何用JS实现瀑布流
小白请看! 大厂面试题 :如何用JS实现瀑布流
|
17天前
|
存储 JavaScript 前端开发
JS浅拷贝及面试时手写源码
JS浅拷贝及面试时手写源码
|
17天前
|
JavaScript 前端开发
JS:类型转换(四)从底层逻辑让你搞懂经典面试问题 [ ] == ![ ] ?
JS:类型转换(四)从底层逻辑让你搞懂经典面试问题 [ ] == ![ ] ?
|
28天前
|
缓存 JavaScript 前端开发
js高频面试题,整理好咯
中级前端面试题,不低于12k,整理的是js较高频知识点,可能不够完善,大家有兴趣可以留言补充,我会逐步完善,若发现哪里有错,还请多多斧正。
|
1月前
|
JavaScript
JS【详解】setTimeout 延时(含清除 setTimeout,计时开始时间,0 秒延时解析,多 setTimeout 执行顺序,setTimeout 应用场景,网红面试题)
JS【详解】setTimeout 延时(含清除 setTimeout,计时开始时间,0 秒延时解析,多 setTimeout 执行顺序,setTimeout 应用场景,网红面试题)
74 0
|
1月前
|
存储 JavaScript 前端开发
面试官:JS中变量定义时内存有什么变化?
面试官:JS中变量定义时内存有什么变化?
23 0
|
1月前
|
JavaScript 前端开发
JS进阶篇(前端面试题整合)(三)
JS进阶篇(前端面试题整合)(三)
23 0

热门文章

最新文章