Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zepto源码分析-event模块 #12

Open
mominger opened this issue Apr 3, 2015 · 2 comments
Open

Zepto源码分析-event模块 #12

mominger opened this issue Apr 3, 2015 · 2 comments

Comments

@mominger
Copy link
Owner

mominger commented Apr 3, 2015

源码注释

//     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功能一样且支持冒泡

    //此处标准浏览器clickmousedownmouseupmousemove抛出的就是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

        //不存在表明不是mouseovermouseout事件//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 //缓存数据
         //执行回调函数contextelementargumentsevent,e._args(默认是undefindtrigger()时传递的参数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属性至proxyignoreProperties里包含的属性除外
    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个应该用arguments5个是惯例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回调是绑定事件否则触发事件// 不用onon才通用啊 ,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    浏览器只识别truefalse, !!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为事件类型如clickbubbles为是否冒泡第三个参数表示是否可以用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流程解析

enter image description here
enter image description here
enter image description here

浏览器原生支持自定义事件举例(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);

方法图

enter image description here

@tso-si
Copy link

tso-si commented Oct 24, 2016

请问一下parse那个函数里,事件的命名空间指的是什么啊。我有点不懂。。

@qianlongo
Copy link

@imnotnerd

你在给元素添加事件的时候,

$('.list').on('click.myEvent', () => {})

这里的myEvent就是命名空间

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants