zoukankan      html  css  js  c++  java
  • JavaScript事件代理入门

    事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。

    顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。

    为什么要这样做呢?

    众所周知,DOM操作是十分消耗性能的。所以重复的事件绑定简直是性能杀手。而事件代理的核心思想,就是通过尽量少的绑定,去监听尽量多的事件。

    下面将会用 Zepto 为大家演示怎么实现事件代理。

    啊?Zepto是什么?

    Zepto is a minimalist JavaScript library for modern browsers with a largely jQuery-compatible API. If you use jQuery, you already know how to use Zepto.

    由于API是兼容 jQuery 的,熟悉jQuery的童鞋使用 Zepto 几乎无需学习成本。演示代码实际上也能在 jQuery 上正常运行。而目前 Zepto 的适用场景更多是在移动端,一个远离旧版IE的世界。

    那为什么不直接用jQuery?

    因为下文会简要分析源码,但jQuery里面为了兼容旧版IE做了很多妥协,源码十分不直观。而 Zepto 是面向现代浏览器设计的,所用到的API绝大多数都符合W3C标准,分析起来更加直观。

    以这个HTML结构为例:

    <ul class="list">
        <li class="list_items">000</li>
        <li class="list_items">111 <a href="javascript:void(0);">link</a></li>
        <li class="list_items">222 <i>italic</i></li>
        <li>333</li>
    </ul>

    一般情况下,会这样绑定事件:

    $('.list_items').on('click', function (e) {
        console.log(e.target.tagName);
        console.log(this.tagName);
    });

    Deom>>

    打开浏览器的 Console 看一下,会发现每一个 .list_items 元素都被绑定了click事件,并且绑定的对象是 li.list_items 。如下图:

    这意味着,Zepto的实际上是遍历了所有 .list_items 元素,并逐个绑定 click 事件。

    实现思路和下面这段原生 JavaScript 代码相同:

    [].forEach.call(document.querySelectorAll('.list_items'), function (elem) {
      elem.addEventListener('click', function (e) {
        console.log(e.target.tagName);
        console.log(this.tagName);
      }, false);
    });

    这样的做法,当遇到数量超长的列表(ul)和表格(table)时性能开销非常大。

    例如:ul 中有1000个 li 时,就需要进行1000次的事件绑定。

    而事件代理,就是应用于这种场景的。

    我们先看一下Zepto官方的API文档:

    on 方法还支持在 回调函数 前传入一个 [selector] 的值,而这个 [selector] 就是实际需要监听事件的的元素。

    看看实际例子:

    $('.list').on('click', '.list_items', function (e) {
        console.log(e.target.tagName);
        console.log(this.tagName);
    });

    Demo>>

    通过 on 方法把 click 事件代理在 ul.list 元素上,监听其所有 .list_items 的子元素的点击操作。

    打开浏览器的 Console 看看:

    虽然提示每个 .list_items 都有事件监听,但它们的绑定对象都是指向 ul.list 。

    事件代理是通过什么机制实现的? 

    这其中的核心思想是 事件冒泡(Event Bubble) 。但在这里我不打算细说事件冒泡,因为这会是一个很大的话题。Google一下,就能找到有很多相关的介绍了。

    下面我们看一下 Zepto 的源码,它是怎么去处理这个事件代理的。

    方法 on 的实现:

      $.fn.on = function(event, selector, data, callback, one){
        var autoRemove, delegator, $this = this
        if (event && !isString(event)) {
          $.each(event, function(type, fn){
            $this.on(type, selector, data, fn, one)
          })
          return $this
        }
    
        if (!isString(selector) && !isFunction(callback) && callback !== false)
          callback = data, data = selector, selector = undefined
        if (isFunction(data) || data === false)
          callback = data, data = undefined
    
        if (callback === false) callback = returnFalse
    
        return $this.each(function(_, element){
          if (one) autoRemove = function(e){
            remove(element, e.type, callback)
            return callback.apply(this, arguments)
          }
    
          if (selector) delegator = function(e){
            var evt, match = $(e.target).closest(selector, element).get(0)
            if (match && match !== element) {
              evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
              return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
            }
          }
    
          add(element, event, callback, data, selector, delegator || autoRemove)
        })
      }

    代码的大致执行流程如下:

     

    由上面的活动图可以看出,大部分逻辑都是用来处理 on 方法传入参数,delegator 函数是事件代理的关键。

    至于 add 方法,主要是对某些特殊事件进行处理,例如:ready、hover等。当成黑盒去看待,它就等同于原生的 addEventListener 方法。

    下面再细分一下 delegator 函数的实现逻辑:

    到这里,大家应该都清楚了。

    调用 on 方法进行事件绑定时,只有传入 [selector] 参数才会实现事件代理。

    实际应用中,如何用 JavaScript 原生代码写出事件绑定呢?如下:

    var listElem = document.querySelector('.list');
    listElem.addEventListener('click', function (e) {
        var delegateTarget = this,
            fireTarget = e.target,
            eventTarget;
    
        if (fireTarget.className !== 'list_items') {
            function findParent(elem) {
                if (elem === delegateTarget) {
                    return null;
                }
                var parent = elem.parentNode;
                if (parent.className === 'list') {
                    return null;
                }
                if (parent.className === 'list_items') {
                    return parent;
                }
                findParent(parent);
            }
            eventTarget = findParent(fireTarget);
        } else {
            eventTarget = fireTarget;
        }
        if (!eventTarget) return false;
        console.log('fireTarget: ' + fireTarget.tagName);
        console.log('eventTarget: ' + eventTarget.tagName);
    }, false);

    Demo>>

    以上。

    参考资料:

    http://zeptojs.com/

    本文作者:Maple Jan

    本文链接:http://www.cnblogs.com/maplejan/p/3761519.html

  • 相关阅读:
    访问系统内容提供器,获取联系人列表
    ubuntu下查看IP Gateway DNS信息
    使用fragment,Pad手机共用一套代码
    动态注册广播接收器,监听网络变化
    启动Activity,传递参数最佳实践
    管理Activity,随时随地控制Activity的销毁工作
    unzip解压中文文件名乱码
    mysql null值转换
    (转)使用scp命令在linux操作系统之间传递文件
    比较两个日期的大小
  • 原文地址:https://www.cnblogs.com/maplejan/p/3761519.html
Copyright © 2011-2022 走看看