`
LiZn
  • 浏览: 9804 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
最近访客 更多访客>>
社区版块
存档分类
最新评论

JS面试题 跨浏览器的事件处理函数的绑定和解绑定

阅读更多

 

原文地址: JS面试题 跨浏览器的事件处理函数的绑定与解绑定

题目如题,还有一个条件,要保证事件处理函数的this正确指向绑定的元素。

说来惭愧,第一次做这道题的时候,根本不知道IE的attachEvent方法没有让事件处理函数的this正确指向所绑定的元素,就简单处理了一下跨浏览器的绑定与解绑定。

 

1 var LIZ = {
2     util : {
3         addEventListener : function ( element, type, handler ){
4             if( element.addEventListener ){
5                 element.addEventListener(type, handler, false);
6             else if ( element.attachEvent ){
7                 element.attachEvent('on'+type, handler);
8             else {
9                 element['on'+type] = handler;
10             }
11         },
12         removeEventListener : function ( element, type, handler ){
13               if( element.removeEventListener ){
14                   element.removeEventListener(type, handler, false);
15              else if ( element.dettachEvent ){
16                  element.dettachEvent('on'+type, handler);
17              else {
18                  element['on'+type] = null;
19              }
20        }
21    }
22 };


那次面试就这样悲剧了-。-回来之后开始研究怎么做。以上代码倒是可以跨浏览器绑定和解绑事件处理函数,但是this指向在IE下是不正确的。让this指向正确很简单,使用handler.call(element)或者handler.apply(element)就可以了么……突然发现这个handler不是由我来调用的,傻X了…… 记得一般会有类似于jQuery.proxy函数的bind方法,可以返回指定context调用的方法,简单一点就是

1 LIZ.patterns = {
2     bind : function ( fn, context ){
3         return function (){
4             return fn.apply( context, arguments );
5         };
6     }
7 };

这样给IE的attachEvent方法传入通过bind方法处理过的函数就可以保证事件处理函数this的正确指向了。然而…

1 var LIZ = {
2     util : {
3         addEventListener : function ( element, type, handler ){
4             if( element.addEventListener ){
5                 element.addEventListener(type, handler, false);
6             else if ( element.attachEvent ){
7                 element.attachEvent('on'+type, LIZ.patterns.bind( handler, element ) );
8             else {
9                 element['on'+type] = handler;
10             }
11         },
12         removeEventListener : function ( element, type, handler ){
13             if( element.removeEventListener ){
14                 element.removeEventListener(type, handler, false);
15             else if ( element.dettachEvent ){
16                 element.dettachEvent('on'+type, handler);
17             else {
18                 element['on'+type] = null;
19             }
20         }
21     }
22 };

这样给IE的attachEvent传入的handler和用户传入LIZ.util.addEventListener的handler已经不是同一个函数,会造成IE下面无法将绑定的handler移除。如果既要保证this指向正确,还要保证能够跨浏览器绑定和解绑定,也就是既要用bind方法来改变IE中事件处理函数的context,又要能够通过handler引用的比较来解绑定事件处理函数,那么必须在传入的handler 和 bind方法返回的函数之间建立映射关系。但是用handler引用做key来写map的话是不行的,比如

1 var handler = function ( arg ){ return arg; };
2 var anotherHandler = function ( arg ){ return arg; };
3 var map = {};
4 map[handler] = 1;
5 alert( map[anotherHandler] ); //会alert 1

因为key值只能是string类型吧,handler大概是按照String(handler)被自动转换了,这样做key值的就是handler的内容而非其引用了。

map不能用来做映射,但还有array。可以用2个数组来记录映射关系。

1 var key = [], val = [], proxyHandler = LIZ.patterns.bind( handler, element );
2  
3 //attachEvent时向数组中添加记录
4 key.push( handler );
5 val.push( proxyHandler );
6  
7 //dettachEvent时通过循环比较key中的值来找到
8 //proxyHandler 然后解绑定 再删除key val中的记录
9 for var i = 0; i < key.length ; i++ ){
10     if ( key[i] === handler ){
11         element.detachEvent( type, val[i] );
12         key.splice( i, 1 );
13         val.splice( i, 1 );
14         break;
15     }
16 }

只要把这2个数组记在element上就行了,做法是丑陋了点,但这样是行的通的……其实这个方法是写此文章想引出下面的做法的时候才想到的,根据逻辑关系走就想到这样做了。但其实一开始就想到是因为这些事件处理函数是基于浏览器自身的api来处理的,所以才有不兼容的现象,如果将这些事件都纳入自己代码的管理,那要兼容事件处理就要比上面的方法优雅的多了。那么,Observer 观察者模式登场……

1 LIZ.patterns.Observer = function (target){
2     this.target = target || this;
3     this.listeners = {};
4 };
5  
6 LIZ.patterns.Observer.prototype = {
7     constructor : LIZ.patterns.Observer,
8     addListener : function (type, handler){
9         iftypeof this.listeners[type] == 'undefined' ){
10             this.listeners[type] = [];
11         }
12            this.listeners[type].push(handler);
13     },
14     fire : function (event){
15         var handlers = this.listeners[event.type],
16             i = 0;
17         if( handlers instanceof Array ){
18             for ( ; i < handlers.length ; i++ ){
19                 handlers[i].call(this.target, event);
20             }
21         }
22     },
23     removeListener : function (type, handler){
24         var handlers = this.listeners[type],
25             i = 0;
26         if( handlers instanceof Array ){
27             for ( ; i < handlers.length ; i++ ){
28                 if( handlers[i] === handler ){
29                     break;
30                 }
31             }
32             handlers.splice(i,1);
33         }
34     }
35 };

关于观察者模式,个人认为《head first 设计模式》是讲的非常形象,容易理解的。如果有这样一个观察者类,我们就能够将事件处理函数的绑定和解绑定纳入自己的管理,轻松的实现跨浏览器的绑定和解绑定。只要新建一个Observer的实例,将所有向目标element注册的事件处理函数都注册到该观察者实例上,然后让该观察者向目标element注册一批事件处理函数来观察目标element,这样在目标element上发生的任何事件都可以通过该观察者向事件处理函数传递了。这样也就可以规避之前handler与proxyHandler的映射问题以及this指向的问题。

1 var LIZ = {
2     dom : {
3         getData : function (element, name) {
4             if ( element ){
5                 iftypeof element.liz === 'object' ){
6                     return element.liz[name];
7                     }
8                 }
9             return undefined;
10         },
11         setData : function (element, name, data) {
12             if ( element ){
13                 iftypeof element.liz !== 'object' ){
14                     element.liz = {};
15                 }
16                 element.liz[name] = data;
17             }
18         },
19         removeData : function (element, name) {
20             this.setData(element, name, null);
21         },
22         trigger : function ( element, event ) {
23             if this.getData(element, 'observer'instanceof LIZ.patterns.Observer ){
24                 this.getData(element, 'observer').fire(event);
25             }
26         },
27         addEventListener : function (element, type, handler){
28             var observer = this.getData(element, 'observer'),
29             proxyHandler = function (event){
30                 observer.fire(event);
31             };
32             if( !observer || !(observer instanceof LIZ.patterns.Observer) ){
33                 observer = new LIZ.patterns.Observer(element);
34                 this.setData(element, 'observer', observer);
35             }
36             iftypeof observer[type] == 'undefined' ){
37                 if( element.addEventListener ){
38                     element.addEventListener(type, proxyHandler, false);
39                 else if ( element.attachEvent ){
40                     element.attachEvent('on'+type, proxyHandler);
41                 else {
42                     element['on'+type] = proxyHandler;
43                 }
44             }
45             observer.addListener(type, handler);
46         },
47         removeEventListener : function (element, type, handler){
48             var observer = this.getData(element, 'observer');
49  
50             if( observer instanceof LIZ.patterns.Observer ){
51                 observer.removeListener(type, handler);
52             else {
53                 if( element.removeEventListener ){
54                     element.removeEventListener(type, handler, false);
55                 else if ( element.detachEvent ){
56                     element.detachEvent('on'+type, handler);
57                 else {
58                     element['on'+type] = null;
59             }
60         }
61     }
62 };

 

在dom节点上存取数据不知道有什么要注意的地方,有空了还要读读jQuery的源码。

本博客文章由LiZn创作或分享,以创作公用CC 姓名标示-非商业性-相同方式分享 3.0 Unported 授权条款共享。 
希望本文能够对你有所帮助,欢迎留言讨论,如果你喜欢本站文章,可以使用该 RSS订阅地址来订阅本站。

 

1
3
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics