javascript事件委托详解

事件委托:就是将子类的事件交给父类去执行。事件委托利用的是事件冒泡的机制。

一、事件委托的好处

1、减少内存消耗
<ul>
	<li>1</li>
	<li>2</li>
	<li>3</li>
	<li>4</li>
</ul>
正如上面的例子,如果我们需要给每一个li标签都设置点击事件,那么需要设置很多
个点击事件,导致内存消耗就很大。如果我们使用事件委托,将全部的事件都委托给
最外层的Ul上,那么只需要在ul上监听一个事件即可。
2、动态绑定事件
我们在开发中很多时候,都需要去异步请求数据,并且将数据展示到前端页面上,如
果我们给每一个标签元素都设置点击事件,那么就必须在每一次新增或者删除标签元
素时,都要将相关的事件进行解绑或者新增。如果使用事件委托,那么就不用在标签
删除的时候将相关事件解绑或者赋值,因为所有的事件都设置在外层的标签元素上。

二、基本实现

<body>

  <ul>
    <li>标签1</li>
    <li>标签2</li>
    <li>标签3</li>
    <li>标签4</li>
    <li>标签5</li>
  </ul>

  <script>
    document.getElementsByTagName("ul")[0].addEventListener("click", function(e) {
      var event = e || window.event //兼容性问题
      var target = event.target || event.srcElement  //兼容性问题
      if(target.nodeName.toLowerCase() === "li") {
        console.log("this  is tag of li:" + target.innerHTML)
      }
    })
  </script>
</body>
试想一种情况,如果我们只是需要所有标签中的class为liStyle的元素,我们可以使用target.matches()方法。如下所示。
<body>

  <ul>
    <li class="liStyle">标签1</li>
    <li>标签2</li>
    <li class="liStyle">标签3</li>
    <li>标签4</li>
    <li class="liStyle">标签5</li>
  </ul>

  <script>
    document.getElementsByTagName("ul")[0].addEventListener("click", function (e) {
      var event = e || window.event
      var target = event.target || event.srcElement
      if (target.matches("li.liStyle")) {
        console.log("大家好:" + target.innerHTML)
      }
    })
  </script>

</body>
target.matches()函数存在一些兼容性问题(ie8以及以下的版本不支持),所以可以使用polyfill来解决。
代码为:
if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}
加上matches兼容性设置,总体代码为

<body>

  <ul>
    <li class="liStyle">标签1</li>
    <li>标签2</li>
    <li class="liStyle">标签3</li>
    <li>标签4</li>
    <li class="liStyle">标签5</li>
  </ul>

  <script>
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function (s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) { }
          return i > -1;
        };
    }
    document.getElementsByTagName("ul")[0].addEventListener("click", function (e) {
      var event = e || window.event
      var target = event.target || event.srcElement
      if (target.matches("li.liStyle")) {
        console.log("大家好:" + target.innerHTML)
      }
    })
  </script>

</body>

三、代码封装

function eventDelegate (parentSelector, targetSelector, events, foo) {
  // 触发执行的函数
  function triFunction (e) {
    // 兼容性处理
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {}
          return i > -1;            
        };
    }
    // 判断是否匹配到我们所需要的元素上
    if (target.matches(targetSelector)) {
      // 执行绑定的函数,注意 this
      foo.call(target, Array.prototype.slice.call(arguments));
    }
  }
  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
      $p.addEventListener(evt, triFunction);
    });
  });
}


这里一共需要四个参数,第一个参数是,需要实现代理的父级元素,第二个参数是:
用于过滤触发事件的选择器元素的后代,第三个参数是:一个或多个用空格分隔的事
件类型和可选的命名空间,如 click 或 keydown.click,第四个参数是:需要代
理事件响应的函数

四、局限性

像focus和blur不存在事件冒泡,不能使用事件委托。
mousemove,mouseout存在事件冒泡,但是只能通过定位去计算,性能消耗大,所以也不试用。

你可能感兴趣的:(js面试题,事件委派)