zepto.js的事件处理

能够深入理解zepto对事件的处理,那么整个JS的事件处理就应该差不多合格了,事件处理是JS语言的一个难点。

1. 首先来看$.event函数

JS中有很多事件,都是已经定义好了,我们直接调用就可以,例如熟悉的click事件,直接对dom绑定一个事件,点击该dom就能触发这个事件,但是有这样的场景:我点击一个dom,重新打开一个页面。按照常规,可以通过window.open来执行,也可以模拟一个连接,在这个链接上绑定click,之后触发这个click事件。代码如下:

var a = document.createElement("a");
a.setAttribute("href", url);
a.setAttribute("target", "_blank");
a.setAttribute("id", "openwin");
document.body.appendChild(a);
//模拟点击事件
var m = document.createEvent("MouseEvents"); //FF的处理
m.initEvent("click", true, true);
a.dispatchEvent(m);

在zepto.js的ajax源码中,有很多注册事件,这些事件都是通过下来代码来完成的。

function triggerAndReturn(context, eventName, data) {
    //步骤1:注册事件
    var event = $.Event(eventName) 
    //步骤2:分发事件 
    $(context).trigger(event, data)
    return !event.defaultPrevented
  }
在注册和分发事件中,有三个步骤,对于与三个函数:
document.createEvent()  //新建
event.initEvent()       //初始化
this.dispatchEvent()    //元素触发事件

来看看$.event源码:

$.Event = function(type, props) {
    if (!isString(type)) props = type, type = props.type
    //click,mousedown,mouseup,mousemove的事件是MouseEvents,其他的是Events。
    var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
    //event.initEvent(eventType,canBubble,cancelable) 
    //eventType  字符串值。事件的类型。
    //canBubble 事件是否起泡。
    //cancelable 是否可以用 preventDefault() 方法取消事件。把bubbles从props中过滤出来,单独处理。
    if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
    event.initEvent(type, bubbles, true)
    return compatible(event)
  }

/*
  stopImmediatePropagation方法作用在当前节点以及事件链上的所有后续节点上。
  目的是在执行完当前事件处理程序之后,停止当前节点以及所有后续节点的事件处理程序的运行。
  stopPropagation方法作用在后续节点上,目的在执行完绑定到当前元素上的所有事件处理程序之后,停止执行所有后续节点的事件处理程序
*/
eventMethods = {
  preventDefault: 'isDefaultPrevented',
  stopImmediatePropagation: 'isImmediatePropagationStopped',
  stopPropagation: 'isPropagationStopped'
}
function compatible(event, source) {
    if (source || !event.isDefaultPrevented) {
      source || (source = event)
      /*
        给event注册6个函数,默认[isDefaultPrevented,isImmediatePropagationStopped,isPropagationStopped] = false,
        执行preventDefault之后,对应的preventDefault = {return true;}
      */
      $.each(eventMethods, function(name, predicate) {
        var sourceMethod = source[name]
        event[name] = function(){
          this[predicate] = returnTrue
          return sourceMethod && sourceMethod.apply(source, arguments)
        }
        event[predicate] = returnFalse
      })
      //给isDefaultPrevented赋值
      if (source.defaultPrevented !== undefined ? source.defaultPrevented :
          'returnValue' in source ? source.returnValue === false :
          source.getPreventDefault && source.getPreventDefault())
        event.isDefaultPrevented = returnTrue
    }
    return event
  }

再来看看$.trigger事件:

function fix(event) {
    if (!('defaultPrevented' in event)) {
      event.defaultPrevented = false
      var prevent = event.preventDefault
      //通过preventDefault取消事件的触发。
      event.preventDefault = function() {
        this.defaultPrevented = true
        prevent.call(this)
      }
    }
  }
$.fn.trigger = function(event, data){
    if (typeof event == 'string') event = $.Event(event)
    //添加preventDefaulted成员和重载preventDefault事件。
    fix(event)
    event.data = data
    return this.each(function(){
      //是dom节点都会有dispatchEvent事件,不是dom,如果有dispatchEvent事件也会执行。
      if('dispatchEvent' in this) this.dispatchEvent(event)
    })
  }

还有一个triggerHandler事件,它与trigger有四个不同点:

  1. 它不会引起事件(比如表单提交)的默认行为
  2. trigger() 会操作 jQuery 对象匹配的所有元素,而 .triggerHandler() 只影响第一个匹配元素。
  3. 由 .triggerHandler() 创建的事件不会在 DOM 树中冒泡;如果目标元素不直接处理它们,则不会发生任何事情。
  4. 该方法的返回的是事件处理函数的返回值,而不是具有可链性的 jQuery 对象。此外,如果没有处理程序被触发,则这个方法返回 undefined。

这个后面我们再讨论。

2.事件对象

  用JS原生态的绑定事件很easy,而zepto对绑定事件进行重重封装,最明显的莫过于event对象,常规绑定事件。

    //每个element都有一个_zid来判断该element上已经绑定了几个事件。
    var id = zid(element), 
    set = (handlers[id] || (handlers[id] = []))
    eachEvent(events, fn, function(event, fn){
      //如果没有事件委托,add只有element, events, fn三个参数
      //element: 元素节点 events=["click","mouseup"...]
      //fn:函数名或者匿名函数。
      var delegate = getDelegate && getDelegate(fn, event),
        callback = delegate || fn
      var proxyfn = function (event) {
        var result = callback.apply(element, [event].concat(event.data))
        //callback返回为false,阻止默认事件。
        if (result === false) event.preventDefault()
        return result
      }
      /*
        event = {
            e:  事件名称
            ns: 事件命名空间的对象
            data: 参数
        }
       */
      var handler = $.extend(parse(event), 
            {
                fn: fn, 
                proxy: proxyfn, 
                sel: selector, 
                del: delegate, 
                i: set.length
            })
      set.push(handler)
      element.addEventListener(handler.e, proxyfn, capture)
    })
  }

委托绑定事件:

$.fn.delegate = function(selector, event, callback){
    //委托事件不需要冒泡到父节点,只针对特定元素。
    var capture = false
    if(event == 'blur' || event == 'focus'){
      if($.iswebkit)
        event = event == 'blur' ? 'focusout' : event == 'focus' ? 'focusin' : event
      else
        capture = true
    }
    return this.each(function(i, element){
      add(element, event, callback, selector, function(fn){
        return function(e){
          var evt, 
          match = $(e.target).closest(selector, element).get(0)
          //匹配到特定的元素
          if (match) {
            evt = $.extend(createProxy(e), 
                {   currentTarget: match,    
                    liveFired: element
                })
            return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
          }
        }
      }, capture)
    })
  }

还有一种绑定事件,只绑定一次,

$.fn.one = function(event, callback){
    return this.each(function(i, element){
     //没有子元素选择参数
      add(this, event, callback, null, function(fn, type){
        return function(){
          var result = fn.apply(element, arguments)
          //触发之后,删除该事件。
          remove(element, type, fn)
          return result
        }
      })
    })

 

posted @ 2016-04-07 17:33  anthonyliu  阅读(1865)  评论(0编辑  收藏  举报