1 请简述DOM事件模型(DOM Event)

  • 每个事件都先经历从上到下(根元素=>最底层子节点)的捕获阶段,再经历从下到上(最底层子节点=>根元素)的冒泡阶段,这决定着元素接收事件的顺序,从而决定函数执行的顺序
  • 事件模型的核心API是addEventListener(type, fn, true/false),第三个参数可以选择执行阶段(是否在捕获阶段执行)
    • 当给同一个元素绑定listener时,仅仅是队列模型,即按照函数定义顺序来执行
    • 绑定在不同元素时,当第三个参数为true时,就会在捕获阶段来执行函数(爷爷-爸爸-儿子),
  • 可以使用event.stopPropagation()来阻止传播(捕获或冒泡)

2 DOM事件委托

  1. 是什么:
    • 事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown……)的函数委托到另一个元素
    • 一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
    • 举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地 一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学;在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。
    • React和Vue框架会自动帮我们实现事件委托(把事件绑定在根元素上)
  2. 有什么优点:
    1. 节省内存,节省监听器(不用每个子元素都加事件监听器)
    2. 实现动态监听(里面子元素随意换)
  3. 有什么缺点:
    1. 调试复杂,不容易知道当前元素的监听器
  4. 缺点如何解决:
    1. chrome和edge浏览器中调试窗口有一个选项 EventListener
  5. 简单用法:
1
2
3
4
5
6
7
ul.addEventListener('click', function(e){
  // e.target是用户点击的元素,不固定
  // e.currentTarget是监听器绑定的元素(ul)
	if(e.target.tagName.toLowerCase() === 'li'){
		fn() // 执行某个函数
	}
})
  • 如果用户点击的是li里面的span,则无法触发此事件监听器
    • 解决思路:点击子元素(li)内部的子元素(span)时,对其父元素进行递归遍历,直到找到监听器绑定的元素(ul)为止,如果没找到,说明点击的不是绑定元素的子元素(ul外面的span)

3 手写可拖拽div

 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
53
54
55
56
57
58
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #root {
        // 一定要是absolute,否则无法移动
        position: absolute;
        left: 0;
        top: 0;
        width: 200px;
        height: 200px;
        border: 1px solid red;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    // 标识是否进行拖拽
    let dragging = false;
    // 元素的位置
    let position = null;
    // 给root元素添加mousedown的事件监听器
    root.addEventListener("mousedown", function (e) {
      dragging = true;
      // 记录鼠标的初始位置,也可以用pageX/pageY,只要统一就行
      position = [e.clientX, e.clientY];
    });
    // 给document添加mousemove的事件监听器
    document.addEventListener("mousemove", function (e) {
      if (dragging === false) return;
      // 记录鼠标移动后的位置
      const x = e.clientX;
      const y = e.clientY;
      // 计算鼠标移动的距离(移动后-单击时的位置)
      const deltaX = x - position[0];
      const deltaY = y - position[1];
      // 获取元素当前位置
      const left = parseInt(root.style.left || 0);
      const top = parseInt(root.style.top || 0);
      // 将移动距离赋给元素,别忘加'px'
      root.style.left = left + deltaX + "px";
      root.style.top = top + deltaY + "px";
      // 记录移动后的当前位置,用于下一次计算
      position = [x, y];
    });
    // 给document添加mouseup的事件监听器
    document.addEventListener("mouseup", function (e) {
      // 鼠标松开,停止拖拽
      dragging = false;
    });
  </script>
</html>

4 target和currentTarget什么区别

  • 当事件处理程序直接绑定在目标元素上,此时e.target===e.currentTarget===this
  • 当事件处理程序绑定在目标元素的父节点上时,currentTarget会指向绑定的父元素,而target依旧指向目标元素