zepto.js.v1.0 源码解读-event.js-03

21 November 2015

前端开发 zepto 源码 event

接着上一篇文章,再来解读zepto的event.js模块。事件在前端看来不是什么新鲜事,从你打开一个页面,到页面的关闭,事件是无处不在的。 但事件可能也是我们最容易忽略和难以琢磨的。日常的鼠标点击,键盘的输入,页面的滚动,都是事件存在的有力证明。 今天,就来看看zepto.event是怎么为dom元素添加事件和删除事件的。

概述

添加事件我们一般都会使用:

    //为div下 为有 calssName == three 添加click事件
    $('div').on('click' , '.three' , function(e){
        e.preventDefault();
        console.log('添加事件' , e.target);
        return false;
    });

以上的方式来添加事件,那么就从on函数入手,解读event是如何添加到元素上面的。

事件添加流程图

$.event $.event

添加流程处理问题分析

event.on() 函数需要处理的问题

    1. 传入数据的个数
    1. 监听事件执行的次数
    1. 是否需要代理事件

$.add() 函数需要处理的问题

    1. 分配唯一id 和 句柄数组
    1. 事件句柄设置
    1. 添加事件

你是不是发现好像也不是很难啊。其实最主要的就是事件句柄的设置,事件的核心就是句柄,句柄里包含了整个事件的信息。 页面所有的事件句柄都是放在handlers对象里面的,所以可想而知,如果事件过多会出现的状况。

下面就开始对事件添加函数的解剖吧。

on函数

on函数主要的工作就是对添加事件的基本数据的处理,将处理好的数据交给真正的add函数去添加事件。这也好比ajax对数据的序列化过程。

上代码: $.on

1.event参数

现在来看看event接收的参数类型,大家所知道的就是传入String类型的字符串,要添加多个事件就以空格隔开。其实zepto还有其他的方式,那就是传人 一个Object对象。Object每个事件名称对应一个处理的方法,这个方法就是所说的回调函数。

    //event参数类型
    //1.普通 字符串类型
    'click'  | 'click mouseover'
    //2.Object 对象
    {
    	click : function(){
            console.log('this is click event callback.');
    	},
    	mouseover:function(){
            console.log('tis is mouseover event callback.');
    	}
    }

这也就是为什么on函数会在最顶部会有一个判断event不是String类型的情况。

    //传人事件 object类型
    if (event && !isString(event)) { 
      $.each(event, function(type, fn){//遍历object对象
        $this.on(type, selector, data, fn, one)//递归调用on函数
      })
      return $this
    }

这样的话,你是不是想说我是不是可以把Object对象写成这样,把click 改写成 ‘click mouseout’:

    //2.Object 对象
    {
    	'click mouseout' : function(){
            console.log('this is click&mouseout event callback.');
    	},
    	mouseover:function(){
            console.log('tis is mouseover event callback.');
    	}
    }

确实,这是没有问题的。因为 ‘click mouseout’ 会被递归调用重新传人on函数,而on函数是接受String类型的。

2.传入的参数个数

为dom元素添加事件,并不是每个参数的需要传人的。在平常的应用中,我们最多就传人3个参数,很少有全部传人的。 但这里就有一个问题了,zepto是怎么知道我们要传人的是什么参数。

   //on函数传人参数判断以及重新赋值
   $.fn.on = function(event, selector, data, callback, one){
    ...
    // selector没有传人  但传人了 data , callback 所以必须重新赋值
    if (!isString(selector) && !isFunction(callback) && callback !== false)
      callback = data, data = selector, selector = undefined
    //data也没传人  只传人了callback
    if (callback === undefined || data === false)
      callback = data, data = undefined
	//callback也没有 默认为空
    if (callback === false) callback = returnFalse //空
    ...
   }

可能光看代码有点不好理解,那么现在我对应的写几个例子对应一下,就应该明白了。这里有3个if语句,所以会产生 8中结果 ——2^3 = 8。

    //现在我用  1代表进入if  0 代表 不进入
    //8中情况大概就是下面这样:
    /**
        1.   0 0 0
        2.   1 0 0
        3.   0 1 0
        4.   0 0 1
        5.   1 1 0
        6.   1 0 1
        7.   0 1 1
        8.   1 1 1
     */
    //1.全部不进入
    $.fn.on('click' , '.class' , {data:'success'} , function(){
        console.log('done');
    } [, true|false] );
    //2. selector没有传人
    $.fn.on('click' , {data:'success'} , function(){
        console.log('done');
    } [, true] );
    //3. 结合实际环境  callback === undefined 才会只进入第2个if
    $.fn.on('click' , '.class' , {data:'success'} , undefined [, true|false] );
    //4.callback 没有传人 one = false
    $.fn.on('click' , '.class' , {data:'success'} , false);
    //5.selector 和 data 都没有传人
    $.fn.on('click' , function(){
        console.log('done');
    });
    //6. selector没有传人 callback = false
    $.fn.on('click' , {data:'success'} , false);
    //7. 这个就好比没有添加 return false
    $.fn.on('click' , '.class' , false);
    //8.就传了event
    $.fn.on('click');

我们使用最多的就是上面的 1,2,5这3中情况。其实也不用特意的去记。只要明白根据类型判断传人的参数来识别用户到底要传人的真正参数就可以了。

3.是否需要代理事件

事件代理并不是什么新鲜的事了 ,可是有时候使用事件代理可以处理单页监听事件过多的问题。比如有一个ul里面有10个li, 每个li他有一个事件就是当用户点击li的时候展开li。 你可能会觉得这个时候把事件添加到li上是没什么问题的,但假如有200个li呢。那么这时候你的事件句柄对象就会有点大了,所以可以考虑将li的事件全部代理到ul上面,那么 你的事件句柄就会从200个缩减到了1个,这就是代理事件的好处。

判断是否要代理:

  if (selector){
    delegator = function(e){ 
      var evt, match = $(e.target).closest(selector, element).get(0)//查找匹配
      if (match && match !== element) { //捕获事件元素 != 事件触发元素 才进行代理
        evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) //代理
        return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
      }
    }
  }

这里引入了2个新的函数,第一个的功能是判断element下面是否有$(e.target) ,第二个就是创建代理对象了。

  //获取代理对象
  closest: function(selector, context){
  	//node : 事件源 , selector:需要代理的目标对象  , context : 代理事件对象
	var node = this[0], collection = false
	if (typeof selector == 'object') 
		collection = $(selector)//获取需要代理元素的集合
	while (node && 
	//集合里面匹配node
	!(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
	   //向上查找元素,直到找到context 或者根元素为止
	   node = node !== context && !isDocument(node) && node.parentNode
	return $(node)
  }

extend函数都不会陌生,它的作用就是将{currentTarget: match, liveFired: element}代理对象添加到原来的 event事件对象上 createProxy函数应该就是返回event事件对象。对与否,我们拭目以待。 createproxy

createProxy函数返回的是compatible函数 , 最后返回的正是所说的event对象,然后通过extend函数把{currentTarget: match, liveFired: element} 这 两个对象往event对象上面添加就形成了最后的代理事件对象。

add 函数

add函数才是真正添加事件的核心,主要的工作就是将事件句柄添加到handlers全局对象中,并为元素注册事件。 $.add

1.分配id

/**
   * [zid 获取唯一id]
   * @param  {[type]} element [description]
   * @return {[type]}         [description]
   */
  function zid(element) {
    return element._zid || (element._zid = _zid++)
  }

2.事件句柄设置 这个就是赋值,将事件的状态都保存到handler里面.

3.添加事件 这个就是用 addEventListener监听事件。

add函数就相当于一个初始化函数,on就相当于序列化数据函数。他们两个分工明确相互合作。

事件触发流程

当一个事件真正触发的时候,流程大概就是这样的。

  1. handler.proxy(e) //这个函数是真正在注册事件的时候注册的。
  2. e = compatible(e) //事件状态检测
  3. 判断 e 是不是被阻止冒泡   ---Y ---> return ;  //不继续执行,直接返回
  4. 获取传人的数据data
  5. var result = callback() //执行回调[delegator|autoRemove|fn]这3中情况的其中一种 
  6. result === false ? //查看回调状态   -----Y----> 则阻止默认行为,停止捕获
  7. return result

最后附上一张删除事件的代码 remove