• 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对象。

  • 相关阅读:
    如何使用SQL语句 查看存储过程的内容
    sl第一篇
    winForm连接数据库(sqlserver2005)
    Format
    dual使用
    ThreadLocal与事务
    oracle中的常用函数
    Oracle中merge into的使用
    API设计中token的思路
    SVN常用功能
  • 原文地址:https://www.cnblogs.com/samwu/p/3281035.html
Copyright © 2020-2023  润新知