zepto源码研究 - event.js(高层api)

守給你的承諾、 提交于 2020-02-29 18:30:54

简要:本文主要介绍event.js中暴露出来的api,包括各种注册,移除事件方法和手动触发事件的方法,这些api都是调用了上一篇所讲到的底层api。

/**
   * 绑定事件,应直接采用on
   * 源自1.9版本前jquery的绑定事件的区分:
   bind()是直接绑定在元素上

   .live()则是通过冒泡的方式来绑定到元素上的。更适合列表类型的,绑定到document DOM节点上。和.bind()的优势是支持动态数据。

   .delegate()则是更精确的小范围使用事件代理,性能优于.live()

   .on()则是1.9版本整合了之前的三种方式的新事件绑定机制
   * @param event
   * @param data
   * @param callback
   * @returns {*}
   */
  $.fn.bind = function(event, data, callback){
    return this.on(event, data, callback)
  }

  /**
   *  解绑事件,应直接用off
   * @param event
   * @param callback
   * @returns {*}
   */
  $.fn.unbind = function(event, callback){
    return this.off(event, callback)
  }
  /**
   * 绑定一次性事件
   * @param event
   * @param selector
   * @param data
   * @param callback
   * @returns {*}
   */
  $.fn.one = function(event, selector, data, callback){
    return this.on(event, selector, data, callback, 1)
  }
/**
   * 小范围冒泡绑定事件,应直接采用on
   */
  $.fn.delegate = function(selector, event, callback){
    return this.on(event, selector, callback)
  }
  /**
   *  解绑事件,应直接用off
   */
  $.fn.undelegate = function(selector, event, callback){
    return this.off(event, selector, callback)
  }

  /**
   *  冒泡到document.body绑定事件,应直接采用on
   * @param event
   * @param callback
   * @returns {*}
   */
  $.fn.live = function(event, callback){
    $(document.body).delegate(this.selector, event, callback)
    return this
  }
  /**
   *  在doument.body解绑事件,应直接用off
   */
  $.fn.die = function(event, callback){
    $(document.body).undelegate(this.selector, event, callback)
    return this
  }

  /**
   * 扩展Zepto on监听事件方法
   * 元素上绑定一个或多个事件的事件处理函数
   * 注意: 方法参数不应超过5个,超过5个,应该用arguments。5个是惯例。if或for或闭包嵌套层也不应超过5层
   * @param event 事件集 字符串/
   * @param selector 子选择器
   * @param data  event.data
   * @param callback          事件响应函数
   * @param one        内部用, $.fn.one用。标记一次性事件
   * @returns {*}
   */
  //this.on(event, selector, data, callback, 1)
  $.fn.on = function(event, selector, data, callback, one){
    var autoRemove, delegator, $this = this

    //event是对象{click:fn},支持这种方式我觉得没多大用
    if (event && !isString(event)) {
      $.each(event, function(type, fn){
        $this.on(type, selector, data, fn, one)
      })
      return $this
    }

    //选择器非字符串  callback非方法
    //未传data    on('click','.ss',function(){})
    // on('click',{ss:'ss'},function(){})
    if (!isString(selector) && !isFunction(callback) && callback !== false)
      callback = data, data = selector, selector = undefined
    //data传了function    或未传
    // on('click','.ss',function(){})  或   on('click','.ss',false)
    if (callback === undefined || data === false)
      callback = data, data = undefined

    //callback传了false,转换成false函数
    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){
        //以element元素为容器,以事件源为起点,往上冒泡找到匹配selector的元素
        // match  响应函数对应的事件源
        var evt, match = $(e.target).closest(selector, element).get(0)
        //    selector能找到,且不是容器,即不是绑定事件的上下文,即$('.parent').on('click','.son',fn)形式。开始处理委托。
        if (match && match !== element) {

          //createProxy(e) 创建event代理对象 currentTarget指向selector元素,liveFired指向绑定事件的容器element
          evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})

          // 执行事件响应函数
          // autoRemove触发一次事件响应函数后自动销毁。 callback触发事件响应函数
          // [evt].concat(slice.call(arguments, 1))响应函数的参数数组
          return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
        }
      }

      add(element, event, callback, data, selector, delegator || autoRemove)
    })
  }

  /**
   * 移除事件响应函数
   * @param event
   * @param selector
   * @param callback
   * @returns {*}
   */
  $.fn.off = function(event, selector, callback){
    var $this = this

    //是对象,遍历移除
    if (event && !isString(event)) {
      $.each(event, function(type, fn){
        $this.off(type, selector, fn)
      })
      return $this
    }

    // 是函数
    // this.off("click",function(){})
    if (!isString(selector) && !isFunction(callback) && callback !== false)
      callback = selector, selector = undefined

    if (callback === false) callback = returnFalse

    return $this.each(function(){
      //元素遍历移除
      remove(this, event, callback, selector)
    })
  }

  /**
   * 触发事件
   * @param event 事件类型
   * @param args
   * @returns {*}
   */
  $.fn.trigger = function(event, args){
    //修正event为事件对象
    event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)

    //传参
    event._args = args

    return this.each(function(){
      // handle focus(), blur() by calling them directly
      //如果事件是focus blur
      if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
      // items in the collection might not be DOM elements
      // 支持浏览器原生触发事件API
      //DOM源码
//        /**
//         @param {Event} event
//         @return {boolean}
//         */
//        EventTarget.prototype.dispatchEvent = function(event) {};
      else if ('dispatchEvent' in this) this.dispatchEvent(event)

      //模拟触发事件
      else $(this).triggerHandler(event, args)
    })
  }

  // triggers event handlers on current element just as if an event occurred,
  // doesn't trigger an actual event, doesn't bubble
  /**
   * 触发事件,不能冒泡
   * @param event  event对象
   * @param args 传参
   * @returns {*}
   */
  $.fn.triggerHandler = function(event, args){
    var e, result
    this.each(function(i, element){
      //修正事件对象
      e = createProxy(isString(event) ? $.Event(event) : event)
      e._args = args
      e.target = element


      //找到此元素上此事件类型上的事件响应函数集,遍历,触发
      $.each(findHandlers(element, event.type || event), function(i, handler){
        //调用 handler.proxy执行事件
        result = handler.proxy(e)

        //如果event调用了immediatePropagationStopped(),终止后续事件的响应                                                                                                                                                                                                                                                                                                                                                         
        if (e.isImmediatePropagationStopped()) return false
      })
    })
    return result
  }

  // shortcut methods for `.bind(event, fn)` for each event type
  /**
   * 给常用事件生成便捷方法
   * @param event
   * @param args
   * @returns {*}
   */
  ;('focusin focusout focus blur load resize scroll unload click dblclick '+
  'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+
  'change select keydown keypress keyup error').split(' ').forEach(function(event) {
    $.fn[event] = function(callback) {
      return (0 in arguments) ?
          //有callback回调,是绑定事件,否则,触发事件  ,
          // 不用on?on才通用啊 ,bind也是调用on
          //$.fn.bind = function(event, data, callback){
//             return this.on(event, data, callback)
//           }
          this.bind(event, callback) :
          this.trigger(event)
    }
  })

 

on:

首先是针对不同的参数输入情况做出对应的重新赋值,这里是一个小的技巧:一般对于不同的参数做出判断是这样的:

input(arg1,null,arg3,null,arg5)

function input(arg1,arg2,arg3,arg4,arg5){

  if(arg1 == null){......}

       if(arg2 == null){......}

}

而event.js里面使用的是这种方式:input(arg1,arg3,arg5),这里我暂不知道这两种方式有什么区别,请知道的人不吝告知。

其次:是针对one,和selector的两种情况做出相应处理(其实是对程序员输入的响应函数做出相应的封装)

 

这里对含有shelector的情况做一下分析:

 var callback  = delegator || fn

handler.proxy = function(e){
        //修正event
        //这里对e的属性做出扩展,
        e = compatible(e)

        //如果是阻止所有事件触发
        if (e.isImmediatePropagationStopped()) return
        e.data = data //缓存数据
        //执行回调函数,context:element,arguments:event,e._args(默认是undefind,trigger()时传递的参数)
        var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))

        //当事件响应函数返回false时,阻止浏览器默认操作和冒泡
        if (result === false) e.preventDefault(), e.stopPropagation()
        return result
      }


 //传递了选择器
      if (selector) delegator = function(e){
        //以element元素为容器,以事件源为起点,往上冒泡找到匹配selector的元素
        // match  响应函数对应的事件源
        var evt, match = $(e.target).closest(selector, element).get(0)
        //    selector能找到,且不是容器,即不是绑定事件的上下文,即$('.parent').on('click','.son',fn)形式。开始处理委托。
        if (match && match !== element) {

          //createProxy(e) 创建event代理对象 currentTarget指向selector元素,liveFired指向绑定事件的容器element
          evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})

          // 执行事件响应函数
          // autoRemove触发一次事件响应函数后自动销毁。 callback触发事件响应函数
          // [evt].concat(slice.call(arguments, 1))响应函数的参数数组
          return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
        }
      }

1:首先当事件触发的时候,原生事件对象e会被扩展3个属性(冒泡,阻止默认行为等),并且将事件注册时传入的数据data赋值给

2:调用delegator方法,并传入原生事件对象e(若事件由trigger来触发,则将trigger的形参_arg挂到e中)。

3:在delegator方法中,首先会获取事件源满足selector条件的父节点match,然后创建原生事件对象e的一个副本evt,并将evt作为参数传递给程序员自定义的事件响应函数去执行

4:所以,在我们自定义方法中的形参e并非原生事件对象,e.preventDefault(), e.stopPropagation()在这里是没有用的,可以通过return false来达到效果。

 

bind:

bind主要用于针对具体的dom绑定事件,响应函数中的this就是绑定的dom本身,这里的e就是原生的事件对象,如图:

 

live:

它先将事件绑定到document上,当某一时刻事件被触发,事件冒泡到document上,此时live将事件的响应权交到满足selector条件的节点上去执行。而满足selector条件的节点在事件被触发前可不必存在dom中,所以live相当于为将来的指定节点做出事件响应。此功能用在动态删除与插入的集合上效果会非常好。

delegate:

$('#container').delegate('a', 'click', function() { alert("That tickles!") });

此方法 是live的一般形式,live是绑定到document,而delegate绑定到指定的container,能够缩小事件捕获的范围,其效率要比live大一点。

 

关于bind,live,delegate有关的更详细的信息可参考:http://kb.cnblogs.com/page/94469/

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!