DOM 事件模型

一、引入:

对于以下代码

<div class="爷爷">
  <div class="爸爸">
    <div class="儿子">文字</div>
  </div>
</div>

给这三个盒子都添加事件监听

  • 点击文字,算不算点击儿子?
  • 点击文字,算不算点击爸爸?
  • 点击文字,算不算点击爷爷?
    都算
    那么他们的顺序是什么呢。
    在捕获阶段,爷爷->爸爸->儿子
    在冒泡阶段,儿子->爸爸->爷爷

二、DOM 事件机制

捕获与冒泡

上述过程发生了什么,为什么点击文字,就算把三个div全部点击了呢。

DOM 事件机制主要有 2 个阶段,分别是:捕获阶段和冒泡阶段
先执行捕获阶段,再执行冒泡阶段
比如刚才说的点击事件,执行的顺序就是

  • 先是捕获阶段:爷爷->爸爸->儿子
  • 接着是冒泡阶段:儿子->爸爸->爷爷

只要添加了时间监听,就会按照上面的顺序执行。
DOM 事件模型_第1张图片
事件监听函数是addEventListener('click', fn, bool)
如果不穿bool的值或者bool值为false,fn函数就只在冒泡阶段执行
如果bool的值是true,那么fn函数就在捕获阶段执行。
DOM 事件模型_第2张图片

取消冒泡

捕获不可以取消,但是冒泡可以取消,e.propagation()就可
但是有一些事件不可以取消冒泡,比如 scroll 事件,具体可以在 MDN 上查询

target 和 currentTarget 的区别

target:监听用户操作的元素
currentTarget:程序员监听的元素

<div><span>内容</span></div>

加入程序员监听的是div,用户能看见的就是内容,用户点击内容,
事件e.target就是span,而e.currentTarget就是div

三、事件委托

先来一个应用场景:我们要给 100 个按钮添加点击事件,怎么办?最笨的办法:直接给 100 个按钮都 addEventListener有了事件委托后:监听这 100 个按钮的爸爸,等冒泡的时候,判断 target 是不是这 100 个按钮中的一个

场景二:我们要监听目前不存在的元素的点击事件,咋办?有了事件委托:监听祖先,等到冒泡时,判断点击的元素是不是我想要监听的元素

所以使用事件委托的好处就是

  • 可以省掉监听数,从而节省内存(用少量的监听器,监听多个元素)
  • 可以监听动态元素

例子
需求:监听所有的 li 标签,如果用户点击 li 标签,就 console.log(‘用户点击了 li 标签’)

// 监听父元素 ul#test
test.addEventListener('click', (e)=> {
     
  //通过浏览器传进来的e参数,找到当前点击元素
  const t = e.target
  // 判断当前元素是不是Li标签
  if(t.matches('li') {
     
    console.log('用户点击了li')
  }
})

思路很简单:
利用事件委托

  1. 监听父元素
  2. 然后根据冒泡和捕获,看点击的元素是否是li

就实现了事件委托。

封装成函数

on("click", "#test", "li", () => {
     
  console.log("用户点击了li");
});

function on(eventType, element, selector, fn) {
     
  // 先判断是不是element,
  //如果传进来的是选择器,不是element本身,就先变成element,
  // 因为只有element才能监听事件`在这里插入代码片`
  if (!(element instanceof Element)) {
     
    element = element.querySelectorAll(element);
  }
  element.addEventListener(eventType, (e) => {
     
    let target = e.target;
    if (target.matches(selector)) {
     
      fn(e);
    }
  });
}

但是以上这种实现有一个小问题,那就是如果被点击元素有多个父元素怎么办?

<ul id="test">
  <li>
    <p>
      <span>1</span>
    </p>
  </li>
  <li>
    <p>
      <span>2</span>
    </p>
  </li>
  <li>
    <p>
      <span>3</span>
    </p>
  </li>
  <li>
    <p>
      <span>4</span>
    </p>
  </li>
</ul>

我们需要做的就是:
递归地向上多找几层父节点,直到找到 li 标签,
同时还必须限定,寻找的范围不能超过 element,
拿上面的例子来说,不可以越过 ul 标签,去找 body 标签

on("click", "#test", "li", () => {
     
  console.log("用户点击了li");
});

function on(eventType, element, selector, fn) {
     
  if (!(element instanceof Element)) {
     
    element = document.querySelectorAll(element);
  }

  element.addEventListener(eventType, (e) => {
     
    let target = e.target;
    // 如果匹配到了selector就跳出循环
    while (!target.matches(selector)) {
     
      if (target === element) {
     
        //已经找到了父元素,说明还没找到,就设置为null
        target = null;
        break;
      }
      target = target.parentNode;
    }

    // 找到了target, 就调用函数
    target && fn.call(target, e);
  });
}

事件委托和事件冒泡,捕获有关系吗

我觉得这是一个很好的问题,很少有人思考这种问题。
我的观点是时间为委托和事件冒泡,捕获没有关系,从上面的代码可以看出,我们是获取到这个事件元素,然后再手动遍历。没有用到事件冒泡和事件捕获的特性。

你可能感兴趣的:(dom,javascript)