zoukankan      html  css  js  c++  java
  • Event in Zepto

    你有想过没,当你监听某个DOM元素的一个事件时,其事件处理函数是如何和该DOM元素关联起来的呢:

    1 var wp=document.getElementById(‘wrapper’);
    2 wp.addEventListener(‘click’,function(){
    3       // event handler
    4 });

    你又想过没,当你监听某个对象上的自定义事件时,其事件处理函数是如何和该对象关联起来的, 事件是如何被触发的,这背后的库,又做了什么呢:

    1 var  obj={}
    2 $(obj).on(‘fire’,function(){
    3     // event handler
    4 })

    带着这些问题,我们以zepto库为原型,从代码实现的角度来一窥究竟:

    首先,我们构造一个mini库,以$记之吧.为简单起见,它只做两件事:id选择器,each方法:

     1 <script>
     2     $ = (function () {
     3         function typestr(o) {
     4             var s = Object.prototype.toString.call(o);
     5             if (s == "[object Object]") return “object”;
     6             else if (s == "[object String]") return “string”;
     7             else;
     8         }
     9         var _$ = function (node) {
    10             if (typestr(node) == “string”) node = document.getElementById(node.slice(1));
    11             var rev = [node];
    12             rev.__proto__ = _$.fn;
    13 
    14             return rev;
    15         }
    16         _$.fn = {
    17             each: function (callback) {
    18                 for (var i = 0; i < this.length; i++) {
    19                     callback(this[i], i);
    20                 }
    21             }
    22         };
    23         return _$;
    24     })();</script>
    View Code

    下面是核心部分,我们先完成准备工作,声明一个计数器和一个对象来维护事件,然后给出基本骨架:

     1 <script>
     2  (function($){
     3     var handlers = {};
     4     var _zid = 1;
     5     function zid(element) {
     6         return element._zid || (element._zid = _zid++);
     7 }
     8 function add(element,event,callback){
     9     // 内部添加事件
    10 }
    11 function remove(element,event,callback){
    12    //内部删除事件
    13 }
    14 $.fn.on=function(event,callback,one){
    15    //对外公开监听事件方法
    16    //add
    17 }
    18 $.fn.off=function(event,callback){
    19    //对外公开移除事件的方法
    20    //remove
    21 }
    22 $.fn.one=function(event,callback){
    23 }
    24 $.fn.trigger=function(event){
    25      //对外公开触发事件的方法
    26 }
    27 })($);
    28 </script>
    View Code

    add方法:

     1 function add(element, event, callback) {
     2         var id = zid(element), set = (handlers[id] || (handlers[id] = [])), handler = {};
     3         handler.en = event;
     4         handler.ev = callback;
     5         handler.i=set.length;
     6         set.push(handler);
     7         if ('addEventListener' in element) {
     8             element.addEventListener(handler.en,callback, false);
     9         }
    10 }

    如果某元素注册过事件,就通过它的_zid属性值去handlers中找到事件队列,将新的事件对象添加进队列;如果该元素没注册过事件,则在handlers中开辟一个以_zid关联的新队列,再将事件对象添加进队列. 事件队列的长度正好是新添加事件对象在事件队列中的位置,记录该位置,可方便后面从事件队列中删除该事件对象.

    这里的’元素’指的是对象,因为DOM元素上的事件是用addEventListener方法来通知浏览器,让浏览器来为我们来作类似的事情.

    remove方法:

     1 function remove(element,event,callback){
     2       var id=zid(element);
     3       var set=handlers[id]||[];
     4       set.forEach(function(handler){
     5         if(handler.en==event){
     6            delete set[handler.i];
     7            if("removeEventListener" in element){
     8              element.removeEventListener(event,callback,false);
     9             }  
    10        }
    11       });
    12 }

    和add方法作相反的事情,对象和DOM元素也是分别对待:

    两个核心方法讲完,看看对外公开的几个方法:

    on/one/off方法:

     1     $.fn.on = function (event, callback,one) {
     2         var cbx=callback;
     3         this.each(function (elem, index) { 
     4             if(one){
     5                 cbx=function(){
     6                     callback();
     7                     remove(elem,event,cbx);
     8                   
     9                 };
    10             }
    11                 add(elem, event,cbx);           
    12         });
    13 
    14     };
    15     $.fn.one=function(event,callback){
    16        this.on(event,callback,1);
    17     };
    18 
    19     $.fn.off=function(event,callback){
    20         this.each(function (elem, index) {
    21             remove(elem, event, callback);
    22         });         
    23     };

    在on方法里给one方法预留了一个判断 ,在执行callback一次后,就remove掉该事件,该事件就不会再次被触发;

    trigger方法:

     1  $.fn.trigger = function (event, args) {
     2         var elem = this[0],set = handlers[zid(element)],len=set.length,handler;
     3         for (var i = 0; i <len; i++) { //forEach
     4             handler=set[i];
     5             if(handler.en==event){
     6                 if('dispatchEvent' in elem) elem.dispatchEvent(event)
     7                 handler.ev.call(this,args);
     8             }
     9         }
    10  };

    通过计数器去查看元素的_zid属性值,然后去handlers中查找事件队列,循环事件队列,执行相应处理函数,如果是DOM元素,则用dispatchEvent方法来告知浏览器触发事件.

    就上面来看,大部分代码是用来解决如何通过维护事件队列来监听,移除,触发一个对象上的事件的.对于DOM元素上的事件来说,我们只是通过addEventListener方法告知浏览器,要注册事件,通过removeEventListener方法告知浏览器,要移除事件了,但浏览器是如何维护它的事件队列的,对于我们来讲,是透明的.

    事件的触发也靠浏览器的自身的机制去完成的.例如,浏览器如果检测到一个DOM元素被单击了,它会去触发click事件以执行相应的处理函数.也就是说,浏览器形为和事件之间是有对应或契约关系的.我们常见的DOM元素上面一些默认的事件,都是以这种方式来处理的.

    上面的代码为了做到尽可能的简单,很多地方做了简化,这里要提一点的是,自定义事件的用法:

     1 // Create the event.
     2 var event = document.createEvent('Event');
     3 // Define that the event name is 'build'.
     4 event.initEvent('build', true, true);
     5 // Listen for the event.
     6 document.addEventListener('build', function (e) {
     7   // e.target matches document from above
     8 }, false);
     9 // target can be any Element or other EventTarget.
    10 document.dispatchEvent(event);

    相关内容参考: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events

    移动端click事件问题

    因为移动端的单击事件会延时,zepto的tap事件据说又很坑爹,所以,果断决定来模拟一个自己的tap事件:

     1 (function (root, $) {
     2            var x, y, target, startTime;
     3            root = $(root);
     4            root.bind('touchstart', function (e) {
     5                target = $(event.target);
     6                var touch = event.changedTouches[0];
     7                x = touch.pageX;
     8                y = touch.pageY;
     9                startTime = new Date().getTime();
    10            }).bind('touchend', function (e) {
    11                var touch = event.changedTouches[0];
    12                var tx = (new Date().getTime() - startTime);
    13                var cx = touch.pageX;
    14                var cy = touch.pageY;
    15                if (Math.abs(cx - x) <= 10 && Math.abs(cy - y) <= 10 && tx <= 500) {
    16                    var ev = $.Event('tap');
    17                    target.triggerHandler(ev);
    18                }
    19            });
    20 })(document,zepto)
    View Code

    这里$.Event的内部实现其实就用到了上面提到的自定义事件.我们这里已经定义好了tap事件的触发时机,只待事件注册了.

    改用tap注册事件,事件执行确实是快了,但是,它却带来了新的问题:

    场景:  当我们在一个弹出层的关闭按钮上面用tap注册了一个事件,功能是单击后,弹出层消失.

    效果:  确实能让对弹出层消失,但是如果关闭按钮下方刚好有个文本框,或是有一个上面已经注册了其他事件的DOM元素,你会发现不期望的事情发生了:

       1:  系统键盘弹出来了;

       2:  触发了DOM元素上的事件,页面跳转了;

       3:  导致页面跳转,触发了下个页面元素上的事件;

    执行得太快,也是个错么?

    这个问题一度的解决方案是定义一个白色的透明层,执行tap事件时,立马把整个屏幕罩起来,0.8s后,移除遮罩:

    1 /*#ng{position:fixed;top:0;left:0;100%;height:100%;background:#fff;opacity:0.0;z-index:1999;}*/
    2 var ng=$(‘#ng’);
    3 ng.show();
    4 setTimeout(function(){ng.hide();},800)

    后来受叶小钗同学一文的启发,还是用click事件:

    1 if (Math.abs(cx - x) <= 10 && Math.abs(cy - y) <= 10 && tx <= 500) {
    2    var ev = $.Event('click.me');
    3    target.triggerHandler(ev);
    4 }

    为了避免click执行两次,在自定义的click事件里,我给加了个.me的别名,用intel XDK找了几款机型测试了下,暂时没发现什么问题,有兴趣的同学可以试试!

  • 相关阅读:
    python json文件
    Abp(net core)+easyui+efcore实现仓储管理系统——出库管理之七(五十六)
    abp(net core)+easyui+efcore实现仓储管理系统——出库管理之六(五十五)
    abp(net core)+easyui+efcore实现仓储管理系统——出库管理之五(五十四)
    abp(net core)+easyui+efcore实现仓储管理系统——出库管理之四(五十三)
    abp(net core)+easyui+efcore实现仓储管理系统——出库管理之三(五十二)
    一个屌丝程序猿的人生(一百二十)
    C++中SORT函数使用方法
    大话西游手游
    Ubuntu查看并修改主机名的方法
  • 原文地址:https://www.cnblogs.com/stenson/p/3924265.html
Copyright © 2011-2022 走看看