前言

最近在一个弹窗插件,看了比较多的图片弹窗预览插件源码,有一个弹窗插件也是网站正在用的 fancybox 插件,看了源码实数写的巧妙,可以说绝大部分插件都是需要 new 一个实例,然后传入参数初始化的,但是这个却可以直接引入就可以点击初始化弹窗,看里面的做法是监听了文档加载事件,再去监听全局点击,判断点击的元素是否含有自定义属性 data-fancybox,然后做一些操作,最后在代码里面实现动态的 new 实例。但是本片不讲插件的处理,说一下点击的获取监听的部分,事件委托。

正文

我们都知道JavaScript 的事件点击,只需要绑定一个元素既可以实现点击操作,如果有一个列表的元素都需要点击处理,一般情况下是把所有列表都去循环一遍,然后在循环的过程中给每一个元素做一个绑定点击事件(如下代码)。这样操作固然简单粗暴,但是也有一个不好的弊端,就是给每个元素都做了绑定,设想一下如果元素过多,一百上千的这样的都绑定,就会有性能问题。

那有没有绑定一次就可以做的点击操作呢? (ˇˍˇ) 想~
答案:肯定是有的,使用事件委托,绑定父元素一次即可。
<ul>
  <li></li>
  <li></li>
  ...
</ul>

<script>
  var lis = document.querySelectorAll('li')
  for (var i = 0; i < lis.length; i++) {
    lis.onclick = function () {
      console.log(i)
    }
  }  
<script>

事件委托

事件委托也是什么很神奇的东西,其实就是通过绑定父元素点击,然后借助事件的捕获阶段来找到子元素,关于事件的冒泡和捕获我这里就不展开说了,等找到子元素之后就可以继续做下面的一些操作了。

优点:就是可以大量节省内存占用,减少事件注册。

可以稍微的修改一下上面的代码:

<ul>
  <li>1</li>
  <li>2</li>
  ...
</ul>

<script>
  // 可以直接监听 ul 元素
  var ul = document.querySelector('ul')
  ul.onclick = function(event) {
    if (event.target.nodeName === 'li') {
      console.log(event.target.innerHTML)
    }
  } 
<script>

改完之后代码就只监听了一个元素,通过事件捕获的方式向下传递,当找到节点为 LI 时就会执行 打印的代码。看来看去都是获取执行 LI 里面东西,为什么要大费周章的搞这些呢?
其实除了能减少节点的绑定事件外,还有一个比较容易忽略的点就是,只有当列表数据全部渲染之后,我们才能在 LI 节点添加绑定事件,数据量一多的话,就会导致延迟点击,或者点击过快数据还没渲染点击无效的情况。

设想一个场景,后端返回的数据有一千条,我们拿到数据渲染到页面上,然后再通过 js 获取所有的 LI 项,通过我上面写的循环绑定节点的方式去操作,是不是会感觉繁琐,但是如果你使用事件委托的方式,就不需要等到数据的获取到,甚至在获取数据前就可以绑定父元素的 ul 点击事件,等所有的子节点 li 都渲染到页面时,也就可以直接做点击操作了,这就是一个区别。

说了这么多的委托,其实这些随便百度一篇都能给你讲的明白,但今天的主角不是这些,而是委托了另外一个场景,看下面例子。

<ul>
  <li>
    <div>
      <div>111</div>
    </div>
    <p>222</p>
  </li>
  ...
</ul>

上面代码中 li 节点下面可以有很多子节点,并不是像我们想象的一样里面就只有一个元素,那样的话使用委托的方式直接获取判断 nodeName 的方式话就会有问题,因为点击的时候不会每次恰好都会点到 li 标签,可能会点击到 p 标签或者 div 标签,这样就会导致,点击的时候一下可以一下不可以,像这样如果多节点下使用委托就略显不合适了,但是真的就不行吗?

closest 方法使用

答案:那肯定是可以继续使用委托的,不过这次的主角需要用到另外一个获取节点的方法 closest()

看一下这个方法的使用介绍

Element.closest() 方法用来获取:匹配特定选择器且离当前元素最近的祖先元素(也可以是当前元素本身)。如果匹配不到,则返回 null。
selectors 是指定的选择器,比如 "p:hover, .toto + q"。

可以向上父元素查找,选择器可以是伪类,自定义值,id,class,标签等,感觉很厉害的样子。

稍微改造一下之前的代码;

<ul>
  <li>
    <div>
      <div>111</div>
    </div>
    <p>222</p>
  </li>
  ...
</ul>

<script>
  // 可以直接监听 ul 元素
  var ul = document.querySelector('ul')
  ul.onclick = function(event) {
    // 直接获取 li 元素
    if (event.target.closest('li')) {
      console.log(event.target.innerHTML)
    }
  } 
</script>

需要注意的是有些浏览器对 closest 的支持并不是很好,但是没关系可以使用 matches() 方法实现 polyfill (IE:你们不要看着我...)

总结,可以使用委托和独立绑定事件,看情况而定。