zeptojs庫解讀2之事件模塊


第一,通過obj.addEventListener("click",fn)綁定的事件,你不能通過obj.onclick = null;來移除綁定點擊事件的所有回調函數。
所以引入第三方對象空間handler,來對用最終add函數綁定的事件,進行事件管理。
具體如何管理,
首先是以html element為key,它的value是一個數組,數組元素handler是zepto封裝的事件對象。其對象為
  1. delundefined
  2. e"click"
  3. fnfunction (){
  4. i0
  5. ns"namespace"
  6. proxyfunction (e) {
  7. selundefined
  8.  
其中del是事件委托,e是事件名稱,fn是事件回調函數,i是事件在數組里的索引,ns是命名空間,proxy是事件代理(為后面的trigger函數做准備),sel是選擇器(這個是用來查找指定元素的zepto事件對象數組,findHandlers會用到)
這樣就可以通過$(obj).off()遍歷已綁定的事件,進行事件移除。
第二,事件委托
事件委托是通過父元素綁定事件,判斷其target屬性,獲得子元素信息,間接實現子元素事件綁定效果。
zepto里的實現就是,目標元素target的closet也就是最近父元素了綁定事件。
第三,事件觸發
通過事件模擬的方法,來觸發事件。這樣連自定義事件名稱的事件,都能觸發了。
 
/* 
 事件處理部份
  */
;
(function($) {
  var $$ = $.zepto.qsa,
  // _zid是每個事件綁定函數的標識符
    handlers = {}, _zid = 1,
    specialEvents = {},
    hover = {
      mouseenter: 'mouseover',
      mouseleave: 'mouseout'
    }

  specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'

  //取element的唯一標示符,如果沒有,則設置一個並返回

  function zid(element) {
    return element._zid || (element._zid = _zid++)
  }
  //查找綁定在元素上的指定類型的事件處理函數集合

  function findHandlers(element, event, fn, selector) {
    event = parse(event)
    if (event.ns) var matcher = matcherFor(event.ns)
    return (handlers[zid(element)] || []).filter(function(handler) {
      return handler && (!event.e || handler.e == event.e) //判斷事件類型是否相同
      && (!event.ns || matcher.test(handler.ns)) //判斷事件命名空間是否相同
      //注意函數是引用類型的數據zid(handler.fn)的作用是返回handler.fn的標示符,如果沒有,則給它添加一個,
      //這樣如果fn和handler.fn引用的是同一個函數,那么fn上應該也可相同的標示符,
      //這里就是通過這一點來判斷兩個變量是否引用的同一個函數
      && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
    })
  }
  //解析事件類型,返回一個包含事件名稱和事件命名空間的對象

  function parse(event) {
    var parts = ('' + event).split('.')
    return {
      e: parts[0],
      ns: parts.slice(1).sort().join(' ')
    }
  }
  //生成命名空間的正則

  function matcherFor(ns) {
    return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
  }
  //遍歷events
  // events可能是1.["click","keyup"]  2."click keyup"
  // fn就是事件處理函數
  // iterator就是包裝函數,外面再套一層的第三方函數
  function eachEvent(events, fn, iterator) {
    // 不是字符串,就當做數組處理
    if ($.type(events) != "string") $.each(events, iterator)
    // 字符串,則以空格為標識符,轉數組,再進行枚舉處理
    else events.split(/\s/).forEach(function(type) {
      iterator(type, fn)
    })
  }
  //通過給focus和blur事件設置為捕獲來達到事件冒泡的目的

  function eventCapture(handler, captureSetting) {
    return handler.del &&
      (handler.e == 'focus' || handler.e == 'blur') || !! captureSetting
  }

  //修復不支持mouseenter和mouseleave的情況

  function realEvent(type) {
    return hover[type] || type
  }

  //給元素綁定監聽事件,可同時綁定多個事件類型,如['click','mouseover','mouseout'],也可以是'click mouseover mouseout'
  // add函數做了三件事
  // 1.給添加的事件設置唯一id,以此來索引
  // 2.對每個事件進行重新封裝,有特殊事件需要修復的修復比如mouseover和mouseout,有委托的觸發委托,有代理的觸發代理
  // 3.進行addEventListener綁定
  function add(element, events, fn, selector, getDelegate, capture) {
    var id = zid(element),
      set = (handlers[id] || (handlers[id] = [])) //元素上已經綁定的所有事件處理函數
      // 根據events的內容,進行遍歷處理
      // event是單個事件名稱
      eachEvent(events, fn, function(event, fn) {
        var handler = parse(event)
        //保存fn,下面為了處理mouseenter, mouseleave時,對fn進行了修改
        handler.fn = fn
        handler.sel = selector
        //如果是 mouseenter, mouseleave,則轉換成mouseover和mouseout處理
        // 這里的參數handler.e是指 事件名稱比如click
        if (handler.e in hover) fn = function(e) {
          /* 
             relatedTarget為事件相關對象,只有在mouseover和mouseout事件時才有值
             mouseover時表示的是鼠標移出的那個對象,mouseout時表示的是鼠標移入的那個對象
             當related不存在,表示事件不是mouseover或者mouseout,mouseover時!$.contains(this, related)當相關對象不在事件對象內
             且related !== this相關對象不是事件對象時,表示鼠標已經從事件對象外部移入到了對象本身,這個時間是要執行處理函數的
             當鼠標從事件對象上移入到子節點的時候related就等於this了,且!$.contains(this, related)也不成立,這個時間是不需要執行處理函數的
         */
          var related = e.relatedTarget
          if (!related || (related !== this && !$.contains(this, related)))
            return handler.fn.apply(this, arguments)
        }
        //事件委托
        handler.del = getDelegate && getDelegate(fn, event)
        var callback = handler.del || fn

        handler.proxy = function(e) {
          // 給下面的trigger觸發函數,進行e.data自定義增加數據
          var result = callback.apply(element, [e].concat(e.data))
          //當事件處理函數返回false時,阻止默認操作和冒泡
          if (result === false) e.preventDefault(), e.stopPropagation()
          return result
        }
        //設置處理函數的在函數集中的位置
        handler.i = set.length
        //將函數存入函數集中
        set.push(handler)
        element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
      })
  }
  //刪除綁定在元素上的指定類型的事件監聽函數,可同時刪除多種事件類型指定的函數,用數組或者還空格的字符串即可,同add
  // 通過事件唯一標識符,來移除事件
  function remove(element, events, fn, selector, capture) {
    var id = zid(element)
    eachEvent(events || '', fn, function(event, fn) {
      findHandlers(element, event, fn, selector).forEach(function(handler) {
        // 首先是移除事件管理對象的引用
        delete handlers[id][handler.i]
        // 其次是移除事件綁定
        element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
      })
    })
  }

  $.event = {
    add: add,
    remove: remove
  }

  //設置代理
  $.proxy = function(fn, context) {
    if ($.isFunction(fn)) {
      //如果fn是函數,則申明一個新的函數並用context作為上下文調用fn
      var proxyFn = function() {
        return fn.apply(context, arguments)
      }
      //引用fn標示符
      proxyFn._zid = zid(fn)
      return proxyFn
    } else if (typeof context == 'string') {
      return $.proxy(fn[context], fn)
    } else {
      throw new TypeError("expected function")
    }
  }

  $.fn.bind = function(event, callback) {
    return this.each(function() {
      add(this, event, callback)
    })
  }
  $.fn.unbind = function(event, callback) {
    return this.each(function() {
      remove(this, event, callback)
    })
  }
  //綁定一次性事件監聽函數
  $.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
        }
      })
    })
  }

  var returnTrue = function() {
    return true
  },
    returnFalse = function() {
      return false
    },
    ignoreProperties = /^([A-Z]|layer[XY]$)/,
    eventMethods = {
      preventDefault: 'isDefaultPrevented', //是否調用過preventDefault方法
      //取消執行其他的事件處理函數並取消事件冒泡.如果同一個事件綁定了多個事件處理函數, 在其中一個事件處理函數中調用此方法后將不會繼續調用其他的事件處理函數.
      stopImmediatePropagation: 'isImmediatePropagationStopped', //是否調用過stopImmediatePropagation方法,
      stopPropagation: 'isPropagationStopped' //是否調用過stopPropagation方法
    }
    //創建事件代理

    function createProxy(event) {
      var key, proxy = {
          originalEvent: event
        } //保存原始event
      for (key in event)
        if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] //復制event屬性至proxy

        //將preventDefault,stopImmediatePropagatio,stopPropagation方法定義到proxy上
      $.each(eventMethods, function(name, predicate) {
        proxy[name] = function() {
          this[predicate] = returnTrue
          return event[name].apply(event, arguments)
        }
        proxy[predicate] = returnFalse
      })
      return proxy
    }

    // emulates the 'defaultPrevented' property for browsers that have none
    //event.defaultPrevented返回一個布爾值,表明當前事件的默認動作是否被取消,也就是是否執行了 event.preventDefault()方法.

    function fix(event) {
      if (!('defaultPrevented' in event)) {
        event.defaultPrevented = false //初始值false
        var prevent = event.preventDefault // 引用默認preventDefault
        event.preventDefault = function() { //重寫preventDefault
          this.defaultPrevented = true
          prevent.call(this)
        }
      }
    }
    //事件委托
  $.fn.delegate = function(selector, event, callback) {
    return this.each(function(i, element) {
      add(element, event, callback, selector, function(fn) {
        return function(e) {
          //如果事件對象是element里的元素,取與selector相匹配的最近的父元素
          var evt, match = $(e.target).closest(selector, element).get(0)
            if (match) {
              //evt成了一個擁有preventDefault,stopImmediatePropagatio,stopPropagation方法,currentTarge,liveFiredn屬性的對象,另也有e的默認屬性
              evt = $.extend(createProxy(e), {
                currentTarget: match,
                liveFired: element
              })
              return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
            }
        }
      })
    })
  }
  //取消事件委托
  $.fn.undelegate = function(selector, event, callback) {
    return this.each(function() {
      remove(this, event, callback, selector)
    })
  }

  $.fn.live = function(event, callback) {
    $(document.body).delegate(this.selector, event, callback)
    return this
  }
  $.fn.die = function(event, callback) {
    $(document.body).undelegate(this.selector, event, callback)
    return this
  }

  //on也有live和事件委托的效果,所以可以只用on來綁定事件
  $.fn.on = function(event, selector, callback) {
    return !selector || $.isFunction(selector) ?
      this.bind(event, selector || callback) : this.delegate(selector, event, callback)
  }
  $.fn.off = function(event, selector, callback) {
    return !selector || $.isFunction(selector) ?
      this.unbind(event, selector || callback) : this.undelegate(selector, event, callback)
  }
  //主動觸發事件
  // 依據
  $.fn.trigger = function(event, data) {
    if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event)
    fix(event)
    event.data = data
    return this.each(function() {
      // items in the collection might not be DOM elements
      // (todo: possibly support events on plain old objects)
      if ('dispatchEvent' in this) this.dispatchEvent(event)
    })
  }

  // triggers event handlers on current element just as if an event occurred,
  // doesn't trigger an actual event, doesn't bubble
  //觸發元素上綁定的指定類型的事件,但是不冒泡
  $.fn.triggerHandler = function(event, data) {
    var e, result
      this.each(function(i, element) {
        e = createProxy(typeof event == 'string' ? $.Event(event) : event)
        e.data = data
        e.target = element
        //遍歷元素上綁定的指定類型的事件處理函數集,按順序執行,如果執行過stopImmediatePropagation,
        //那么e.isImmediatePropagationStopped()就會返回true,再外層函數返回false
        //注意each里的回調函數指定返回false時,會跳出循環,這樣就達到的停止執行回面函數的目的
        $.each(findHandlers(element, event.type || event), function(i, handler) {
          result = handler.proxy(e)
          if (e.isImmediatePropagationStopped()) return false
        })
      })
      return result
  }

  // shortcut methods for `.bind(event, fn)` for each event type
  ;
  ('focusin focusout 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 callback ?
      //如果有callback回調,則認為它是綁定
      this.bind(event, callback) :
      //如果沒有callback回調,則讓它主動觸發
      this.trigger(event)
    }
  })

  ;
  ['focus', 'blur'].forEach(function(name) {
    $.fn[name] = function(callback) {
      if (callback) this.bind(name, callback)
      else this.each(function() {
        try {
          this[name]()
        } catch (e) {}
      })
      return this
    }
  })

  //根據參數創建一個event對象,通過事件模擬的方法
  $.Event = function(type, props) {
    //當type是個對象時
    if (typeof type != 'string') props = type, type = props.type
    //創建一個event對象,如果是click,mouseover,mouseout時,創建的是MouseEvent,bubbles為是否冒泡
    var event = document.createEvent(specialEvents[type] || 'Events'),
      bubbles = true
      //確保bubbles的值為true或false,並將props參數的屬性擴展到新創建的event對象上
    if (props)
      for (var name in props)(name == 'bubbles') ? (bubbles = !! props[name]) : (event[name] = props[name])
      //初始化event對象,type為事件類型,如click,bubbles為是否冒泡,第三個參數表示是否可以用preventDefault方法來取消默認操作
    event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null)
    //添加isDefaultPrevented方法,event.defaultPrevented返回一個布爾值,表明當前事件的默認動作是否被取消,也就是是否執行了 event.preventDefault()方法.
    event.isDefaultPrevented = function() {
      return this.defaultPrevented
    }
    return event
  }

})(Zepto)

 

//-----------------------20140103

handlers是一個對象,作用是管理zepto的事件。

handlers的key是一個唯一數字,這個唯一數字又對應一個html元素。

表現為{1:[{del: undefined, e: "click",fn: function (){},i: 0,ns: "namespace",proxy: function (e) {},sel: undefined},{del: undefined, e: "click",fn: function (){},i: 1,ns: "namespace",proxy: function (e) {},sel: undefined}], 2:[], 3:[]}。

通過key,可以很容易把一個html元素綁定的所有事件,都解除綁定。

通過ns命名空間,我們可以觸發跨類型的事件,比如click和mousedown同時觸發。

通過i可以快速找到事件所在數組的位置,可以快速定位。

fn就是事件處理函數。

del是委托封裝了fn,用於事件委托。

$.fn.delegate = function(selector, event, callback) {
    return this.each(function(i, element) {
      add(element, event, callback, selector, function(fn) {
        return function(e) {
          //如果事件對象是element里的元素,取與selector相匹配的
          var evt, match = $(e.target).closest(selector, element).get(0);
            if (match) {
              //evt成了一個擁有preventDefault,stopImmediatePropagatio,stopPropagation方法,currentTarge,liveFiredn屬性的對象,另也有e的默認屬性
              evt = $.extend(createProxy(e), {
                currentTarget: match,
                liveFired: element
              })
              return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
            }
        }
      })
    })
  }

到時候執行的回調函數被$.fn.delegate封裝過了。當你下次觸發事件時,只冒泡到你給定的html元素為止,再判斷觸發事件來源,然后調用相關事件處理函數。

proxy是封裝了fn(或者被委托封裝過的fn),可以知道該事件要不要冒泡,阻止默認操作。

sel是zepto對象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM