zepto源码学习-04 event

之前说完$(XXX),然后还有很多零零碎碎的东西需要去分析,结果一看代码,发现zepto的实现都相对简单,没有太多可分析的。直接略过了一些实现,直接研究Event模块,相比JQuery的事件系统,zepto的设计相对简单很多,看起来也就相对轻松,整个event模块也就300行代码。

先看事件的相关接口以及用法

$.Event

$.Event(type, [properties])   ⇒ event

创建并初始化一个指定的DOM事件。如果给定properties对象,使用它来扩展出新的事件对象。默认情况下,事件被设置为冒泡方式;这个可以通过设置bubblesfalse来关闭。

一个事件初始化的函数可以使用trigger来触发。

$.Event(‘mylib:change‘, { bubbles: false })

$.proxy v1.0+

$.proxy(fn, context)   ⇒ function
$.proxy(fn, context, [additionalArguments...])   ⇒ function v1.1.4+
$.proxy(context, property)   ⇒ function
$.proxy(context, property, [additionalArguments...])   ⇒ function v1.1.4+

接受一个函数,然后返回一个新函数,并且这个新函数始终保持了特定的上下文(context)语境,新函数中this指向context参数。另外一种形式,原始的function是从上下文(context)对象的特定属性读取。

如果传递超过2个的额外参数,它们被用于 传递给fn参数的函数 引用。

bind

不推荐, 使用on代替。

delegate

不推荐, 使用on代替。

die

不推荐, 使用on代替。

live

不推荐, 使用on代替。

unbind

不推荐

undelegate

不推荐

off

off(type, [selector], function(e){ ... })   ⇒ self
off({ type: handler, type2: handler2, ... }, [selector])   ⇒ self
off(type, [selector])   ⇒ self
off()   ⇒ self

移除通过on添加的事件.移除一个特定的事件处理程序, 必须通过用on()添加的那个相同的函数。否则,只通过事件类型调用此方法将移除该类型的所有处理程序。如果没有参数,将移出当前元素上全部的注册事件。

on

on(type, [selector], function(e){ ... })   ⇒ self
on(type, [selector], [data], function(e){ ... })   ⇒ self v1.1+
on({ type: handler, type2: handler2, ... }, [selector])   ⇒ self
on({ type: handler, type2: handler2, ... }, [selector], [data])   ⇒ self v1.1+

添加事件处理程序到对象集合中得元素上。多个事件可以通过空格的字符串方式添加,或者以事件类型为键、以函数为值的对象 方式。如果给定css选择器,当事件在匹配该选择器的元素上发起时,事件才会被触发(即事件委派,或者说事件代理)。

如果给定data参数,这个值将在事件处理程序执行期间被作为有用的 event.data 属性

事件处理程序在添加该处理程序的元素、或在给定选择器情况下匹配该选择器的元素的上下文中执行(this指向触发事件的元素)。 当一个事件处理程序返回falsepreventDefault() 和 stopPropagation()被当前事件调用的情况下,  将防止默认浏览器操作,如链接,同时执行阻止事件冒泡。

 one

添加一个处理事件到元素,当第一次执行事件以后,该事件将自动解除绑定,保证处理函数在每个元素上最多执行一次,使用方法和on相同

event.isDefaultPrevented v1.1+

event.isDefaultPrevented()   ⇒ boolean

如果preventDefault()被该事件的实例调用,那么返回true。 这可作为跨平台的替代原生的 defaultPrevented属性,如果 defaultPrevented缺失或在某些浏览器下不可靠的时候。

event.isImmediatePropagationStopped v1.1+

event.isImmediatePropagationStopped()   ⇒ boolean

如果stopImmediatePropagation()被该事件的实例调用,那么返回true。Zepto在不支持该原生方法的浏览器中实现它,  (例如老版本的Android)。

event.isPropagationStopped v1.1+

event.isPropagationStopped()   ⇒ boolean

如果stopPropagation()被该事件的实例调用,那么返回true。

trigger

trigger(event, [args])   ⇒ self

在对象集合的元素上触发指定的事件。事件可以是一个字符串类型,也可以是一个 通过$.Event 定义的事件对象。如果给定args参数,它会作为参数传递给事件函数

triggerHandler

triggerHandler(event, [args])   ⇒ self像trigger它只在当前元素上触发事件,但不冒泡。 在代码注释里面有详细解释

以上均是说明事件api的使用,接下来分析一些具体的实现。先从最常用的on开始说起,先上代码,看看zepto里面on的具体实现。

    $.fn.on = function(event, selector, data, callback, one) {
        var autoRemove, delegator, $this = this
            //如果是{‘click‘:function(){},‘touchmove‘:function(){}}
            //此时event是Object
        if (event && !isString(event)) {
            $.each(event, function(type, fn) {
                $this.on(type, selector, data, fn, one)
            })
            return $this
        }

        if (!isString(selector) && !isFunction(callback) && callback !== false)
            callback = data, data = selector, selector = undefined

        if (isFunction(data) || data === false)
            callback = data, data = undefined

        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)
                }
                //事件委托,这里是事件冒泡到element元素上
            if (selector) delegator = function(e) {
                //事件触发元素e.target的祖先级元素
                var evt, match = $(e.target).closest(selector, element).get(0)
                    //找到了 并且不是element本身
                if (match && match !== element) {
                    //创建一个event对象
                    evt = $.extend(createProxy(e), {
                            currentTarget: match,//匹配到的元素
                            liveFired: element//委托的元素
                        })
                        //(autoRemove || callback)不是一次性事件,就调用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)
        })
    }

看上面代码就知道,在on里面对一次性事件,委托事件都做了相关处理,也对$(XXX).on({‘click‘:function(){},‘touchmove‘:function(){}})这种传递Object的情况做了相应处理。

一次性事件,及时绑定函数只值执行一次,zepto里面简单处理,执行一次就移除该事件绑定,所以最后把我们传入的回调包装了一下

//如果是一次性事件
 if (one) autoRemove = function(e) {
      //移除该事件
      remove(element, e.type, callback)
      //执行回调
      return callback.apply(this, arguments)
 }

如果是委托事件,也是把我们传入的callback进行了包装

if (selector) delegator = function(e) {
    //事件触发元素e.target的祖先级元素
    var evt, match = $(e.target).closest(selector, element).get(0)
        //找到了 并且不是element本身
    if (match && match !== element) {
        //创建一个event对象
        evt = $.extend(createProxy(e), {
                currentTarget: match,//匹配到的元素
                liveFired: element//委托的元素
            })
            //(autoRemove || callback)不是一次性事件,就调用callback,
            // [evt].concat(slice.call(arguments, 1))拼接参数数组。
        return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
    }
}

不管是一次性事件,还是事件委托,在on里面只是对我们的callback做了一些包装处理,然后再调用add方法执行最后的绑定,看看add的实现

function add(element, events, fn, data, selector, delegator, capture) {
    //取到元素的zid
    var id = zid(element),
        set = (handlers[id] || (handlers[id] = [])) //元素上已经绑定的所有事件处理函数,如果没有赋值一个新数组

    events.split(/\s/).forEach(function(event) {
        //如果是绑定dom ready事件
        if (event == ‘ready‘) return $(document).ready(fn)
            //解析事件类型,返回一个包含事件名称和事件命名空间的对象
        var handler = parse(event)
            // //保存fn,下面为了处理mouseenter, mouseleave时,对fn进行了修改
        handler.fn = fn
        handler.sel = selector
            // emulate mouseenter, mouseleave
            // 模仿 mouseenter, mouseleave
        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 = delegator //事件委托
        var callback = delegator || fn
        handler.proxy = function(e) {
                e = compatible(e)
                    //这个event对象执行过阻止冒泡方法stopImmediatePropagation,这里直接返回。
                if (e.isImmediatePropagationStopped()) return
                e.data = data
                    //调用之前传入的回调函数
                var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
                    //当事件处理函数返回false时,阻止默认操作和冒泡
                if (result === false) e.preventDefault(), e.stopPropagation()
                return result
            }
            //设置处理函数的在函数集中的位置,remove的时候要用到
        handler.i = set.length
            //将函数存入函数集中,引用类型,你懂的,handlers里面也有了
        set.push(handler)
        if (‘addEventListener‘ in element)
        //realEvent(handler.e) 处理事件类型,eventCapture绑定事件类型,是捕获还是冒泡
            element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
    })
}

里面zid,负责到元素上的一个属性,_zid的值初始从1开始。

//取element的唯一标示符,如果没有,则设置一个并返回 ,保证-zid的唯一性
function zid(element) {
    return element._zid || (element._zid = _zid++)
}

如果是同时绑定多个事件$(XXX).on(‘tap click‘,XXX); 通过events.split(/\s/).forEach进行处理

var handler = parse(event),主要是解析出时间的命名空间,以及绑定事件的类型

function parse(event) {
    var parts = (‘‘ + event).split(‘.‘)
    return {
        e: parts[0],
        //name space
        ns: parts.slice(1).sort().join(‘ ‘)
    }
}

最终指向绑定

if (‘addEventListener‘ in element)
//realEvent(handler.e) 处理事件类型,eventCapture绑定事件类型,是捕获还是冒泡
    element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))

可以看到绑定的时间处理函数是handler.proxy,这个其实也是把传入的callback进行了再次包装。不难想象,如果我们绑定的是一次性事件或者是委托事件、先是在on里面进行了一次包装,然后到了add里面再做了一次,每次的包装都是有特殊意义的。要完全明白这些东西,还是脱离不了基础!! 闭包、作用域、函数、this等等。on和add的相关处理代码上我进行了详细注释,这里不说了。

和on对应的就是off,off的用法上面已经说了,看具体实现

$.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)
    })
}

一看off的代码基本上处理处理相关参数,剩下没啥了,最后都聚焦到remove的实现上,所以重点关注remove的实现,关注remove的同时需要知道findHandlers

//删除绑定在元素上的指定类型的事件监听函数,可同时删除多种事件类型指定的函数,用数组或者还空格的字符串即可,同add
function remove(element, events, fn, selector, capture) {
    //取到元素的zid
    var id = zid(element);
    (events || ‘‘).split(/\s/).forEach(function(event) {
        findHandlers(element, event, fn, selector).forEach(function(handler) {
            //删除handlers 对应这个元素(通过zid关联的),对应的索引的callback。
            //var a=[1,2,3,4,5]  delete a[0],delete a[3]====>[2,3,5]
            delete handlers[id][handler.i]
                //移除元素上绑定的事件
            if (‘removeEventListener‘ in element)
                element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
        })
    })
}
//查找绑定在元素上的指定类型的事件处理函数集合
function findHandlers(element, event, fn, selector) {
    event = parse(event)
    if (event.ns) var matcher = matcherFor(event.ns)
    return (handlers[zid(element)] || []).filter(function(handler) {
        //判断事件命名空间是否相同
        //注意函数是引用类型的数据zid(handler.fn)的作用是返回handler.fn的标示符,如果没有,则给它添加一个,
        //这样如果fn和handler.fn引用的是同一个函数,那么fn上应该也可相同的标示符,
        //这里就是通过这一点来判断两个变量是否引用的同一个函数
        return handler && (!event.e || handler.e == event.e) &&
            (!event.ns || matcher.test(handler.ns)) && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
    })
}

最后看看trigger和triggerHandler的实现,用法上面已经说过了看实现。两者的差异代码注释里面说了,一个是dispatchEvent(event),一个是直接调用handler.proxy(e)。相当于一个经过了浏览器的相关行为,另一个根本没走浏览器,直接调用了元素上绑定事件的对应回调函数。

$.fn.trigger = function(event, args) {
    event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
    event._args = args
    return this.each(function() {
        // handle focus(), blur() by calling them directly
        if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
            // items in the collection might not be DOM elements
        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
//触发元素上绑定的指定类型的事件,但是不冒泡
$.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
            //遍历元素上绑定的指定类型的事件处理函数集,按顺序执行,如果执行过stopImmediatePropagation方法,
            //那么e.isImmediatePropagationStopped()就会返回true,再外层函数返回false
            //each里的回调函数指定返回false时,会跳出循环,这样就达到的停止执行回面函数的目的
        $.each(findHandlers(element, event.type || event), function(i, handler) {
            //直接调用handler.proxy发方法,没有经过浏览器,所以很多浏览器的行为不会发生。
            // $("input").triggerHandler(‘focus‘);
            // 此时input上的focus事件触发,但是input不会获取焦点。因为这里直接取到绑定到该元素对应的focus事件,然后调用
            //$("input").trigger(‘focus‘);
            // 此时input上的focus事件触发,input获取焦点。这里最后会dispatchEvent,会触发浏览器相关行为
            result = handler.proxy(e)
                //如果这个对象调用了ImmediatePropagationStopped方法
            if (e.isImmediatePropagationStopped()) return false
        })
    })
    return result
}

代码具体实现还得自己去折腾,没什么特别复杂的,花点事件肯定能够看懂,看不懂多google就行。zepto 的时间设计比起jquery简单很多很多,所以看懂这个再去啃jqeury吧,jqeury看得头大。最后附上事件处理的全部代码,都是自己加上的注释。

  1 ;
  2 (function($) {
  3     var _zid = 1,
  4         undefined,
  5         slice = Array.prototype.slice,
  6         isFunction = $.isFunction,
  7         isString = function(obj) {
  8             return typeof obj == ‘string‘
  9         },
 10         handlers = {},
 11         specialEvents = {},
 12         focusinSupported = ‘onfocusin‘ in window,
 13         focus = {
 14             focus: ‘focusin‘,
 15             blur: ‘focusout‘
 16         },
 17         hover = {
 18             mouseenter: ‘mouseover‘,
 19             mouseleave: ‘mouseout‘
 20         }
 21         //特殊事件
 22     specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = ‘MouseEvents‘
 23
 24     //取element的唯一标示符,如果没有,则设置一个并返回 ,保证-zid的唯一性
 25     function zid(element) {
 26             return element._zid || (element._zid = _zid++)
 27         }
 28         //查找绑定在元素上的指定类型的事件处理函数集合
 29     function findHandlers(element, event, fn, selector) {
 30             event = parse(event)
 31             if (event.ns) var matcher = matcherFor(event.ns)
 32             return (handlers[zid(element)] || []).filter(function(handler) {
 33                 //判断事件命名空间是否相同
 34                 //注意函数是引用类型的数据zid(handler.fn)的作用是返回handler.fn的标示符,如果没有,则给它添加一个,
 35                 //这样如果fn和handler.fn引用的是同一个函数,那么fn上应该也可相同的标示符,
 36                 //这里就是通过这一点来判断两个变量是否引用的同一个函数
 37                 return handler && (!event.e || handler.e == event.e) &&
 38                     (!event.ns || matcher.test(handler.ns)) && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
 39             })
 40         }
 41         //解析事件类型,返回一个包含事件名称和事件命名空间的对象
 42     function parse(event) {
 43             var parts = (‘‘ + event).split(‘.‘)
 44             return {
 45                 e: parts[0],
 46                 //name space
 47                 ns: parts.slice(1).sort().join(‘ ‘)
 48             }
 49         }
 50         //生成命名空间的正则
 51     function matcherFor(ns) {
 52             return new RegExp(‘(?:^| )‘ + ns.replace(‘ ‘, ‘ .* ?‘) + ‘(?: |$)‘)
 53         }
 54         //通过给focus和blur事件设置为捕获来达到事件冒泡的目的
 55     function eventCapture(handler, captureSetting) {
 56             return handler.del &&
 57                 (!focusinSupported && (handler.e in focus)) ||
 58                 !!captureSetting
 59         }
 60         //修复不支持mouseenter和mouseleave的情况
 61     function realEvent(type) {
 62         return hover[type] || (focusinSupported && focus[type]) || type
 63     }
 64
 65     function add(element, events, fn, data, selector, delegator, capture) {
 66         //取到元素的zid
 67         var id = zid(element),
 68             set = (handlers[id] || (handlers[id] = [])) //元素上已经绑定的所有事件处理函数,如果没有赋值一个新数组
 69
 70         events.split(/\s/).forEach(function(event) {
 71             //如果是绑定dom ready事件
 72             if (event == ‘ready‘) return $(document).ready(fn)
 73                 //解析事件类型,返回一个包含事件名称和事件命名空间的对象
 74             var handler = parse(event)
 75                 // //保存fn,下面为了处理mouseenter, mouseleave时,对fn进行了修改
 76             handler.fn = fn
 77             handler.sel = selector
 78                 // emulate mouseenter, mouseleave
 79                 // 模仿 mouseenter, mouseleave
 80             if (handler.e in hover) fn = function(e) {
 81                 /*
 82                           relatedTarget为事件相关对象,只有在mouseover和mouseout事件时才有值
 83                           mouseover时表示的是鼠标移出的那个对象,mouseout时表示的是鼠标移入的那个对象
 84                           当related不存在,表示事件不是mouseover或者mouseout,mouseover时!$.contains(this, related)当相关对象不在事件对象内
 85                           且related !== this相关对象不是事件对象时,表示鼠标已经从事件对象外部移入到了对象本身,这个时间是要执行处理函数的
 86                           当鼠标从事件对象上移入到子节点的时候related就等于this了,且!$.contains(this, related)也不成立,这个时间是不需要执行处理函数的
 87                       */
 88                 var related = e.relatedTarget
 89                 if (!related || (related !== this && !$.contains(this, related)))
 90                     return handler.fn.apply(this, arguments)
 91             }
 92             handler.del = delegator //事件委托
 93             var callback = delegator || fn
 94             handler.proxy = function(e) {
 95                     e = compatible(e)
 96                         //这个event对象执行过阻止冒泡方法stopImmediatePropagation,这里直接返回。
 97                     if (e.isImmediatePropagationStopped()) return
 98                     e.data = data
 99                         //调用之前传入的回调函数
100                     var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
101                         //当事件处理函数返回false时,阻止默认操作和冒泡
102                     if (result === false) e.preventDefault(), e.stopPropagation()
103                     return result
104                 }
105                 //设置处理函数的在函数集中的位置,remove的时候要用到
106             handler.i = set.length
107                 //将函数存入函数集中,引用类型,你懂的,handlers里面也有了
108             set.push(handler)
109             if (‘addEventListener‘ in element)
110             //realEvent(handler.e) 处理事件类型,eventCapture绑定事件类型,是捕获还是冒泡
111                 element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
112         })
113     }
114
115     //删除绑定在元素上的指定类型的事件监听函数,可同时删除多种事件类型指定的函数,用数组或者还空格的字符串即可,同add
116     function remove(element, events, fn, selector, capture) {
117         //取到元素的zid
118         var id = zid(element);
119         (events || ‘‘).split(/\s/).forEach(function(event) {
120             findHandlers(element, event, fn, selector).forEach(function(handler) {
121                 //删除handlers 对应这个元素(通过zid关联的),对应的索引的callback。
122                 //var a=[1,2,3,4,5]  delete a[0],delete a[3]====>[2,3,5]
123                 delete handlers[id][handler.i]
124                     //移除元素上绑定的事件
125                 if (‘removeEventListener‘ in element)
126                     element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
127             })
128         })
129     }
130
131     $.event = {
132             add: add,
133             remove: remove
134         }
135         //看到他 就想起了bind
136     $.proxy = function(fn, context) {
137         var args = (2 in arguments) && slice.call(arguments, 2)
138         if (isFunction(fn)) {
139             var proxyFn = function() {
140                 return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments)
141             }
142             proxyFn._zid = zid(fn)
143             return proxyFn
144         } else if (isString(context)) {
145             if (args) {
146                 args.unshift(fn[context], fn)
147                 return $.proxy.apply(null, args)
148             } else {
149                 return $.proxy(fn[context], fn)
150             }
151         } else {
152             throw new TypeError("expected function")
153         }
154     }
155
156     $.fn.bind = function(event, data, callback) {
157         return this.on(event, data, callback)
158     }
159     $.fn.unbind = function(event, callback) {
160         return this.off(event, callback)
161     }
162
163     $.fn.one = function(event, selector, data, callback) {
164         return this.on(event, selector, data, callback, 1)
165     }
166
167     var returnTrue = function() {
168             return true
169         },
170         returnFalse = function() {
171             return false
172         },
173         ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$)/,
174         eventMethods = {
175             //是否调用过preventDefault方法
176             preventDefault: ‘isDefaultPrevented‘,
177             //取消执行其他的事件处理函数并取消事件冒泡.如果同一个事件绑定了多个事件处理函数, 在其中一个事件处理函数中调用此方法后将不会继续调用其他的事件处理函数.
178             stopImmediatePropagation: ‘isImmediatePropagationStopped‘, //是否调用过stopImmediatePropagation方法,
179             stopPropagation: ‘isPropagationStopped‘ //是否调用过stopPropagation方法
180         }
181
182         //主要是在event和source做相关的处理
183     function compatible(event, source) {
184         //存在source 或者 event的isDefaultPrevented不存在
185         if (source || !event.isDefaultPrevented) {
186             source || (source = event)
187             $.each(eventMethods, function(name, predicate) {
188                 //source[‘preventDefault‘]、source[‘stopImmediatePropagation‘]、source[‘stopPropagation‘]
189                 var sourceMethod = source[name]
190                     //event[‘preventDefault‘]、event[‘stopImmediatePropagation‘]、event[‘stopPropagation‘]
191                 event[name] = function() {
192                         //this[‘isDefaultPrevented‘]this[‘isImmediatePropagationStopped‘]this[‘isPropagationStopped‘]
193                         //一旦调用过,event对象相应的值就会发生变化, 之前是returnFalse,现在是returnTrue
194                         this[predicate] = returnTrue
195                         return sourceMethod && sourceMethod.apply(source, arguments)
196                     }
197                     //event[‘isDefaultPrevented‘]、event[‘isImmediatePropagationStopped‘]、event[‘isPropagationStopped‘]
198                 event[predicate] = returnFalse
199             })
200
201             if (source.defaultPrevented !== undefined ? source.defaultPrevented :
202                 ‘returnValue‘ in source ? source.returnValue === false :
203                 source.getPreventDefault && source.getPreventDefault())
204
205                 event.isDefaultPrevented = returnTrue
206         }
207         return event
208     }
209
210     function createProxy(event) {
211         var key, proxy = {
212             originalEvent: event //保存原始event
213         }
214         for (key in event)
215             //不是需要忽略的
216             if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] //复制event属性至proxy
217
218         return compatible(proxy, event)
219     }
220
221     $.fn.delegate = function(selector, event, callback) {
222         return this.on(event, selector, callback)
223     }
224     $.fn.undelegate = function(selector, event, callback) {
225         return this.off(event, selector, callback)
226     }
227     $.fn.live = function(event, callback) {
228         //委托到body上
229         $(document.body).delegate(this.selector, event, callback)
230         return this
231     }
232     $.fn.die = function(event, callback) {
233         $(document.body).undelegate(this.selector, event, callback)
234         return this
235     }
236     $.fn.on = function(event, selector, data, callback, one) {
237         var autoRemove, delegator, $this = this
238             //如果是{‘click‘:function(){},‘touchmove‘:function(){}}
239             //此时event是Object
240         if (event && !isString(event)) {
241             $.each(event, function(type, fn) {
242                 $this.on(type, selector, data, fn, one)
243             })
244             return $this
245         }
246
247         if (!isString(selector) && !isFunction(callback) && callback !== false)
248             callback = data, data = selector, selector = undefined
249
250         if (isFunction(data) || data === false)
251             callback = data, data = undefined
252
253         if (callback === false) callback = returnFalse
254
255         return $this.each(function(_, element) {
256             //如果是一次性事件
257             if (one) autoRemove = function(e) {
258                     //移除该事件
259                     remove(element, e.type, callback)
260                         //执行回调
261                     return callback.apply(this, arguments)
262                 }
263                 //事件委托,这里是事件冒泡到element元素上
264             if (selector) delegator = function(e) {
265                 //事件触发元素e.target的祖先级元素
266                 var evt, match = $(e.target).closest(selector, element).get(0)
267                     //找到了 并且不是element本身
268                 if (match && match !== element) {
269                     //创建一个event对象
270                     evt = $.extend(createProxy(e), {
271                             currentTarget: match,//匹配到的元素
272                             liveFired: element//委托的元素
273                         })
274                         //(autoRemove || callback)不是一次性事件,就调用callback,
275                         // [evt].concat(slice.call(arguments, 1))拼接参数数组。
276                     return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
277                 }
278             }
279             add(element, event, callback, data, selector, delegator || autoRemove)
280         })
281     }
282     $.fn.off = function(event, selector, callback) {
283         var $this = this
284         if (event && !isString(event)) {
285             $.each(event, function(type, fn) {
286                 $this.off(type, selector, fn)
287             })
288             return $this
289         }
290
291         if (!isString(selector) && !isFunction(callback) && callback !== false)
292             callback = selector, selector = undefined
293
294         if (callback === false) callback = returnFalse
295
296         return $this.each(function() {
297             remove(this, event, callback, selector)
298         })
299     }
300     $.fn.trigger = function(event, args) {
301         event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
302         event._args = args
303         return this.each(function() {
304             // handle focus(), blur() by calling them directly
305             if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
306                 // items in the collection might not be DOM elements
307             else if (‘dispatchEvent‘ in this) this.dispatchEvent(event)
308             else $(this).triggerHandler(event, args)
309         })
310     }
311
312     // triggers event handlers on current element just as if an event occurred,
313     // doesn‘t trigger an actual event, doesn‘t bubble
314     //触发元素上绑定的指定类型的事件,但是不冒泡
315     $.fn.triggerHandler = function(event, args) {
316         var e, result
317         this.each(function(i, element) {
318             e = createProxy(isString(event) ? $.Event(event) : event)
319             e._args = args
320             e.target = element
321                 //遍历元素上绑定的指定类型的事件处理函数集,按顺序执行,如果执行过stopImmediatePropagation方法,
322                 //那么e.isImmediatePropagationStopped()就会返回true,再外层函数返回false
323                 //each里的回调函数指定返回false时,会跳出循环,这样就达到的停止执行回面函数的目的
324             $.each(findHandlers(element, event.type || event), function(i, handler) {
325                 //直接调用handler.proxy发方法,没有经过浏览器,所以很多浏览器的行为不会发生。
326                 // $("input").triggerHandler(‘focus‘);
327                 // 此时input上的focus事件触发,但是input不会获取焦点。因为这里直接取到绑定到该元素对应的focus事件,然后调用
328                 //$("input").trigger(‘focus‘);
329                 // 此时input上的focus事件触发,input获取焦点。这里最后会dispatchEvent,会触发浏览器相关行为
330                 result = handler.proxy(e)
331                     //如果这个对象调用了ImmediatePropagationStopped方法
332                 if (e.isImmediatePropagationStopped()) return false
333             })
334         })
335         return result
336     }
337
338     // shortcut methods for `.bind(event, fn)` for each event type
339     ;
340     (‘focusin focusout focus blur load resize scroll unload click dblclick ‘ +
341         ‘mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave ‘ +
342         ‘change select keydown keypress keyup error‘).split(‘ ‘).forEach(function(event) {
343             $.fn[event] = function(callback) {
344                 return (0 in arguments) ?
345                     //多个参数
346                     this.bind(event, callback) :
347                     //没有参数 直接调用
348                     this.trigger(event)
349             }
350         })
351         //根据参数创建一个event对象
352     $.Event = function(type, props) {
353         //当type是个对象时
354         if (!isString(type)) props = type, type = props.type
355             //创建一个event对象,如果是click,mouseover,mouseout时,创建的是MouseEvent,bubbles为是否冒泡
356         var event = document.createEvent(specialEvents[type] || ‘Events‘),
357             bubbles = true
358             //确保bubbles的值为true或false,并将props参数的属性扩展到新创建的event对象上
359         if (props)
360             for (var name in props)(name == ‘bubbles‘) ? (bubbles = !!props[name]) : (event[name] = props[name])
361                 //初始化event对象,type为事件类型,如click,bubbles为是否冒泡,第三个参数表示是否可以用preventDefault方法来取消默认操作
362         event.initEvent(type, bubbles, true)
363         return compatible(event)
364     }
365
366 })(Zepto); 

zepto event

本文地址http://www.cnblogs.com/Bond/p/4206105.html

时间: 2024-10-07 19:05:49

zepto源码学习-04 event的相关文章

zepto源码学习-05 ajax

学习zeptoajax之前需要先脑补下,强烈推荐此文http://www.cnblogs.com/heyuquan/archive/2013/05/13/js-jquery-ajax.html 还有Aaron 的jquery源码分析系列,讲jquery的ajax的部分,当然其他也可以看,很值得学习. zepto ajax部分的设计相对简单,毕竟zepto就是轻量级的库,没有jqeury那样复杂,jquery ajax是依赖于Deferred模块的,整个代码一千多行.而zepto只有几百行,整体设

zepto源码学习-01-整体感知

在公司一直做移动端的项目,偶尔会做点PC端的东西,但基本上都是和移动端打交道,移动端嘛必须上zepto. 简单介绍下Zepto,它是一个面向高级浏览器的JavaScript框架的,实现JQuery的大部分API,尤其针对手机上web开发(流量金贵,JQ又太重了,体积太大,考虑太多性能不好),因此选择Zepto.js一个非常不错的选择!纵观各大网站选用zepto的特别多. 做移动端为了让页面更轻巧,大多都是自己写原生代码,遇到难题,我一般都是去看zepto的实现,把其中的一些搬到自己代码中,久而久

zepto源码学习-03 $()

在第一篇的时候提到过关于$()的用法,一个接口有很多重载,用法有很多种,总结了下,大概有一以下几种 1.$(selector,context?) 传入一个选择器返回一个zepto对象 2.$(function(){}) 传入一个函数,dom ready时执行 3.$(html,attrs?) 传入一个html字符串,构建元素,返回一个或zepto对象 4.$(dom obj)传入dom对象返回zepto对象 $()最终调用的zepto.init方法,对以上四种情况做相应处理,该方法有6个retu

zepto源码学习-06 touch

先上菜,看这个模块的最后一段代码,一看就明白. ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap' ].forEach(function(eventName) { $.fn[eventName] = function(callback) { return this.on(eventName, callback) } }) tap —元素tap

zepto源码--几个判断函数--学习笔记

几个需要经常用到的类型判断: 自定义一个类似于typeof的函数,提供更多的类型判断. 如果传入的参数为null,则类型返回'null',基本上可以返回各种常用对象类型,如'function', 'array','regexp'--而不是统一返回object. 判断是否为函数类型: 判断是不是window对象: 判断是不是document对象: 判断是否为object对象: 判断是否为{}对象: 判断是否为类数组:arguments就属于类数组,或者$('div')这种,可以用下标读取,看起来像

【iScroll源码学习01】准备阶段 - 叶小钗

[iScroll源码学习01]准备阶段 - 叶小钗 时间 2013-12-29 18:41:00 博客园-原创精华区 原文  http://www.cnblogs.com/yexiaochai/p/3496369.html 主题 iScroll HTML JavaScript ① viewport相关知识点(device-width等) ②  CSS3硬件加速 ③ 如何暂停CSS动画 ④ e.preventDefault导致文本不能获取焦点解决方案 ...... 当然,我们写的demo自然不能和

tomcat源码学习(2)  关于apache digest

好久不写博文,罪过罪过.因为最近公司比较忙加上琐事有点多,所以隔了好久才来更新博文. apache digest本来是struts2框架中来加载xml文件并实例化对象的一个jar包,后来使用的越来越多. 我们都知道tomcat的conf文件夹下有一个server.xml配置文件,我们经常会其中的来进行配置以来运行一个java web项目,也经常修改中的port属性以来实现修改tomcat监听的端口.其实每个标签基本上都对应着一个对象,那tomcat是如何将这些对象实例化到java 虚拟机的运行内

Netty源码学习——Included transports(传输方式)

Transport API的核心: Channel接口 类图表示Channel含有Pipeline和Config接口,pipeline上一节有所介绍. Channel是线程安全的,这表示在多线环境下操作同一个Channel,不会有数据问题. final Channel channel = null; final ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8); // #1 Runnable writ

Redis源码学习:字符串

Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串,还用来当做缓冲区,例如AOF缓冲区或输入缓冲区等.如下所示,整数len和free分别表示buf数组中已使用的长度和剩余可用的长度,buf是一个原生C字符串,以\0结尾. sds就是sdshdr中char buf[]的别名,后面能看到,各种操作函数的入参和返回值都是sds而非sdshdr.那sdshd