Zepto源碼分析-event模塊

源碼註釋jquery

//     Zepto.js
//     (c) 2010-2015 Thomas Fuchs
//     Zepto.js may be freely distributed under the MIT license.

;(function($){
  var _zid = 1, undefined,
      slice = Array.prototype.slice,
      isFunction = $.isFunction,
      isString = function(obj){ return typeof obj == 'string' },
      handlers = {},//_zid: events    事件緩存池
      specialEvents={},
      focusinSupported = 'onfocusin' in window,      //是否支持即將獲取焦點時觸發函數   onfocusin focus不支持冒泡
      focus = { focus: 'focusin', blur: 'focusout' },    //焦點修正
      hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }    // mouseenter  mouseleave不冒泡的修正 ,mouseover mouseout功能同樣且支持冒泡

    //此處標準瀏覽器,click、mousedown、mouseup、mousemove拋出的就是MouseEvents,應該也是對低版本IE等某些瀏覽器的修正
  specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'


    /**
     * 取元素標識符,沒有設置一個返回
     * @param element
     * @returns {*|number}
     */
  function zid(element) {
    return element._zid || (element._zid = _zid++)
  }

    /**
     *  查找元素上事件響應函數集合
     * @param element
     * @param event
     * @param fn
     * @param selector
     * @returns {Array}
     */
  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)) //判斷事件命名空間是否相同 RegExp.prototype.test = function(String) {};
        && (!fn       || zid(handler.fn) === zid(fn))  // zid(handler.fn)返回handler.fn的標識,沒有加一個,判斷fn標識符是否相同
        && (!selector || handler.sel == selector)  //返回 handler,  判斷selector是否相同
    })
  }

    /**
     * 解析事件類型
     * @param event  'click'
     * @returns {{e: * 事件類型 , ns: string 命名空間}}
     */
  function parse(event) {
        //若是有.分隔,證實有命名空間
    var parts = ('' + event).split('.')
    return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
  }

    /**
     * 生成命名空間的正則對象
     * @param ns
     * @returns {RegExp}
     */
  function matcherFor(ns) {
    return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
  }

    /**
     * 事件捕獲
     * 對focus和blur事件且瀏覽器不支持focusin focusout,經過設置捕獲來模擬冒泡
     * @param handler
     * @param captureSetting
     * @returns {*|boolean|boolean}
     */
  function eventCapture(handler, captureSetting) {
        //若是是focus和blur事件且瀏覽器不支持focusin focusout時,
        //設置爲可捕獲,間接達到冒泡的目的
    return handler.del &&
      (!focusinSupported && (handler.e in focus)) ||
      !!captureSetting
  }

    /**
     *  修正事件類型 focus->focusIn blur->focusOut mouseenter->mouseover  mouseleave->mouseout
     * @param type   事件類型
     * @returns {*|boolean|*|*}
     */
  function realEvent(type) {
        //hover[type] mouseenter和mouseleave 轉換成   mouseover和mouseout
        // focus[type]  focus blur  修正爲  focusin  focusout
    return hover[type] || (focusinSupported && focus[type]) || type
  }


    /**
     * 增長事件底層方法
     * @param element
     * @param events  字符串 如‘click'
     * @param fn
     * @param data
     * @param selector
     * @param delegator
     * @param capture
     */
  function add(element, events, fn, data, selector, delegator, capture){
        //zid Zepto會在elemnt上擴展一個標識屬性_zid
        // 讀取元素上已綁定的事件處理函數
    var id = zid(element), set = (handlers[id] || (handlers[id] = []))

        // \s 匹配空格
    events.split(/\s/).forEach(function(event){
        //若是是ready事件
      if (event == 'ready') return $(document).ready(fn)

        //解析事件   {e: * 事件類型 , ns: string 命名空間}
      var handler   = parse(event)
        //保存fn,下面爲了處理mouseenter, mouseleave時,對fn進行了修改

      //存儲fn響應函數
      //存儲selector
      handler.fn    = fn
      handler.sel   = selector

      // emulate mouseenter, mouseleave
        // 模仿 mouseenter, mouseleave

        //若是事件是mouseenter, mouseleave,模擬mouseover mouseout事件處理
      if (handler.e in hover) fn = function(e){
//          relatedTarget 事件屬性返回與事件的目標節點相關的節點。
//            對於 mouseover 事件來講,該屬性是鼠標指針移到目標節點上時所離開的那個節點。
//            對於 mouseout 事件來講,該屬性是離開目標時,鼠標指針進入的節點。
//            對於其餘類型的事件來講,這個屬性沒有用。
        var related = e.relatedTarget

        //不存在,代表不是mouseover、mouseout事件,
          //related !== this && !$.contains(this, related))  當related不在事件對象event內   表示事件已觸發完成,不是在move過程當中,須要執行響應函數
        if (!related || (related !== this && !$.contains(this, related)))
            //執行響應函數
          return handler.fn.apply(this, arguments)
      }

      //事件委託
      handler.del   = delegator
      var callback  = delegator || fn
      handler.proxy = function(e){
        //修正event
        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
      }

      //設置事件響應函數的索引,刪除事件時,根據它來刪除  delete handlers[id][handler.i]
      handler.i = set.length

        //緩存到handlers[id]裏    set = handlers[id]
      set.push(handler)

        //元素支持DOM2級事件綁定
      if ('addEventListener' in element)
      //綁定事件
      //DOM源碼
//         @param {string} type
//        @param {EventListener|Function} listener
//        @param {boolean} [useCapture]     是否使用捕捉,默認 false
//        EventTarget.prototype.addEventListener = function(type,listener,useCapture) {};
        //realEvent(handler.e)  修正後的事件類型
        //handler.proxy 修正爲代理上下文的事件響應函數
        // eventCapture(handler, capture)
        element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
    })
  }

    /**
     *  刪除事件,   對應add
     * @param element
     * @param events
     * @param fn
     * @param selector
     * @param capture  是否捕獲
     */
  function remove(element, events, fn, selector, capture){
    var id = zid(element)  //找到元素標識
    ;(events || '').split(/\s/).forEach(function(event){ //events多個以空格分隔
            //遍歷事件響應函數集合
      findHandlers(element, event, fn, selector).forEach(function(handler){
        delete handlers[id][handler.i]      //刪除緩存在handlers的響應函數
      if ('removeEventListener' in element)
         //調用DOM原生方法刪除事件
         //DOM源代碼
//          /**
//           @param {string} type
//           @param {EventListener|Function} listener
//           @param {boolean} [useCapture]
//           */
//          EventTarget.prototype.removeEventListener = function(type,listener,useCapture) {};
          //realEvent(handler.e) 修正事件類型     handler.proxy  代理的事件響應函數     eventCapture(handler, capture)修正的是否捕獲
          //與增長事件底層函數 add最後一行    element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))  呼應
        element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
      })
    })
  }

  //此處不清楚要幹嗎,將事件兩個核心底層方法封裝到event對象裏,方便作Zepto插件事件擴展?
  $.event = { add: add, remove: remove }

    /**
     * 代理
     * (function,context),(context,name)
     * @param fn
     * @param context
     * @returns {*}
     */
  $.proxy = function(fn, context) {
    var args = (2 in arguments) && slice.call(arguments, 2)   //若是傳了第3個參數,取到第3個參數之後(包含第3個參數)全部的參數數組,挺好的判斷技巧
    if (isFunction(fn)) {   //fn是函數
        //採用閉包,以context調用函數。
        // args.concat(slice.call(arguments)) 將傳參挪到前面  如傳遞給$.proxy(fn,context,3,4);  轉變成  fn.apply(context,[3,4,fn,context,3,4])
      var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }
        // 標記函數
      proxyFn._zid = zid(fn)
      return proxyFn
    } else if (isString(context)) {  //context是字符串, 實際傳參(context,name)
      if (args) {                           //修正傳參,再以$.proxy調用
        args.unshift(fn[context], fn)   // unshift  往數組開頭添加新的項
        return $.proxy.apply(null, args)
      } else {
        return $.proxy(fn[context], fn)
      }
    } else {
      throw new TypeError("expected function")   //拋出異常:要求的函數類型錯誤
    }
  }

    /**
     * 綁定事件,應直接採用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)
  }

  var returnTrue = function(){return true},
      returnFalse = function(){return false},
      ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,//匹配 大寫字母A-Z開頭/returnValue/layerX/layerY用於createProxy(),過濾event對象的屬性
      eventMethods = {
        preventDefault: 'isDefaultPrevented',//是否已調用preventDefault()    preventDefault      阻止瀏覽器的默認動做
        stopImmediatePropagation: 'isImmediatePropagationStopped', //是否已調用stopImmediatePropagation(),stopImmediatePropagation DOM3提出的阻止任何事件觸發
        stopPropagation: 'isPropagationStopped' //是否已調用stopPropagation()  stopPropagation阻止冒泡
      }

    /**
     * 修正event對象
     * @param event   代理的event對象 原生event對象
     * @param source  原生event對象
     * @returns {*}
     */
  function compatible(event, source) {

        //event.isDefaultPrevented   是否已調用了preventDefault方法
        //

     //event是代理事件對象時,賦值給source
    if (source || !event.isDefaultPrevented) {
      source || (source = event)

        //遍歷,代理preventDefault  stopImmediatePropagation   stopPropagation等方法
      $.each(eventMethods, function(name, predicate) {
        var sourceMethod = source[name]
        event[name] = function(){          //擴展event對象,代理preventDefault  stopImmediatePropagation   stopPropagation方法 ,兼容瀏覽器不支持,同時作其餘事情
        this[predicate] = returnTrue     //若是執行了3方法,原生事件對象isDefaultPrevented  isImmediatePropagationStopped  isPropagationStopped 三方法標記true
          return sourceMethod && sourceMethod.apply(source, arguments)  //且調用原生方法
        }
        event[predicate] = returnFalse   //擴展原生事件對象  isDefaultPrevented  isImmediatePropagationStopped  isPropagationStopped三方法,默認返回false。
      })

        //若是瀏覽器支持  defaultPrevented DOM3 EVENT提出的可否取消默認行爲
      if (source.defaultPrevented !== undefined ? source.defaultPrevented :
          'returnValue' in source ? source.returnValue === false :
          source.getPreventDefault && source.getPreventDefault())
        event.isDefaultPrevented = returnTrue    //默承認以取消
    }

        //返回修正對象
    return event
  }

    /**
     * 建立事件代理
     * @param event Event對象
     * @returns {*}
     */
  function createProxy(event) {
    var key, proxy = { originalEvent: event } //存儲原始event
    for (key in event)
     //複製event屬性至proxy,ignoreProperties裏包含的屬性除外
    if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]

    //
    return compatible(proxy, event)
  }

    /**
     * 小範圍冒泡綁定事件,應直接採用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 {*}
     */
  $.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(){})
    if (!isString(selector) && !isFunction(callback) && callback !== false)
      callback = data, data = selector, selector = undefined
      //data傳了function    或未傳
      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
    }

      // 是函數
    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)
    }
  })

    /**
     *  建立Event對象
     * @param type
     * @param props 擴展到Event對象上的屬性
     * @returns {*}
     * @constructor
     */
  $.Event = function(type, props) {
      //當type是個對象時
    if (!isString(type)) props = type, type = props.type


      //對應 specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
      //建立event對象,若是是click,mousedown,mouseup mousemove,建立爲MouseEvent對象,bubbles設爲冒泡
      //TODO: 爲何要把這些事件單獨拎出來?
    var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true

      // (name == 'bubbles') ? (bubbles = !!props[name])若是是冒泡,確保是true/false    瀏覽器只識別true、false, !!props[name]明確進行類型轉換
       // event[name] = props[name] props屬性擴展到event對象上
    if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])

      //初始化event對象,type爲事件類型,如click,bubbles爲是否冒泡,第三個參數表示是否能夠用preventDefault方法來取消默認操做
      //初始化event對象,type:事件類型   如click  bubbles可否  true:   可否使用preventDefault取消瀏覽器默認操做
      //附上DOM源碼
      /*
      @browser Gecko
      @param {string} eventTypeArg
      @param {boolean} canBubbleArg
      @param {boolean} cancelableArg
      */
    //Event.prototype.initEvent = function(eventTypeArg,canBubbleArg,cancelableArg) {};
      event.initEvent(type, bubbles, true)

      //添加isDefaultPrevented方法,event.defaultPrevented返回一個布爾值,代表當前事件的默認動做是否被取消,也就是是否執行了 event.preventDefault()方法.
    return compatible(event)
  }

})(Zepto)

 

三大核心方法on/off/trigger流程解析數組

 

 

瀏覽器原生支持自定義事件舉例(Zepto自定義事件/瀏覽器事件原理)瀏覽器

 
 
        var element =  document.body;   //綁定事件元素
        var handler = function(e,a,b){     //事件響應函數
            alert('監聽成功 a:' +a+'  b:'+b +' e.data:'+ JSON.stringify(e.data));
        }

        //監聽事件
        element.addEventListener('add',function(e){
            e.data = {wo:1};  //對監聽事件進行傳參
            handler.apply(element, e._args == undefined ? [e] : [e].concat(e._args));
        },false);


        //拋出自定義事件
        var addEvent = document.createEvent('Events');
        addEvent.initEvent('add',true,true);
        addEvent._args = [2,3];  //傳參
        element.dispatchEvent(addEvent);

 

方法圖 緩存

相關文章
相關標籤/搜索