为了客姥爷的用户体验,我们必须做兼容处理,我们可以
1.通过提供统一的api(不兼容就是api不统一,有没有一种越走越远的感觉...),通过鸭辨式找到浏览器提供的相似功能进行兼容
2.使用浏览器提供的最基本的功能进行模拟
兼容模型
在事件模型兼容性中,我们需要兼容以上浏览器,其中ie6,7,8使用的是ie事件模型,其他的则为标准dom模型,可以简单的理解为非ie即dom,大致有2中思路
1.鸭辨式,有dom事件接口即使用,否则就是用ie事件接口
2.通过传统事件模拟事件高阶性质
鸭辨式
Element.prototype.addEvent = function(type, fn, useCapture) { if (this.addEventListener) { this.addEventListener(type, fn, !!useCapture); //dom模型 } else { this.attachEvent("on" + type, fn); //ie模型 } }
标准的鸭辨式,有点代理的意思,javascript可以进行方法的传递/赋值,所以,也可以这样
Element.prototype.addEvent = this.addEventListener?this.addEventListener:function(type, fn){ this.attachEvent("on" + type, fn); }
利用了方法赋值的特性,使的检测只进行一次(当然,这里面有坑,不管他),或者干脆将addEvent取名为addEventListener
无论如何,事件兼容,我们做到了,但这里面有几个坑
1.鸭辨式总是不准的(尤其是国内,尼玛,提供个空接口是几个意思)
2.ie模型和dom模型也是一点点在规范,每个版本浏览器都有一点点的不一样(一群一点点比一样会要人命的)
所以,通过传统方式自制事件模型也是必须的
传统式/自制事件模型
先按照dom模型画个图图压压惊,在模拟事件中,只是用传统方式,即onxxx系列
这里我认为传播不需要模拟,毕竟目前的浏览器都会有传播的概念(我模拟过一次,很容易就执行2遍),这里可以说是只模拟多投事件,可以看到就是一个事件数组的封装
模拟多投事件,第一件事就是解决数组,我可以将他放在闭包中,也可以将它"序列化",将其与dom进行连接
1.使用dom与fns直接进行关联
非常简单,dom1.fns就可以实现的结构
Element.prototype.addEvent = function(type, fn) { var fns= this.fns; if(!fns){ this.fns=[]; fns=this.fns; } var fs = this.fns[type]; if (fs) { fs.push(fn); } else { fns[type] = [fn]; } this["on" + type] = this.proxy; } Element.prototype.proxy = function(event) { var fns=this.fns,type = event.type; if(!fns||!fns[type]){ return; } var fs = fns[type]; if (fs) { for (var index in fs) { fs[index](); } } }
多投事件就已经被模拟出来了
2.使用闭包
闭包是函数中的函数,是函数调用过程中,对作用域链的"回调",
var fns={}; Element.prototype.addEvent =function(type, fn){ var fs=fns[type]; if(fs){ fs.push(fn); }else{ fns[type]=[fn]; } this["on"+type]=this.proxy; } Element.prototype.proxy=function(event){ var type=event.type; var fs=fns[type]; if(fs){ for(var index in fs ){ fs[index](); } } }
将上面这段话丢到自执行中,就不会直接找到fns的句柄了,以此产生闭包,当然因为通过原形进行赋值,可以发现,此时的fns是公共的也就是将原本的fns改成下面的方式
(function(){ var allfns={}; Element.prototype.addEvent =function(type, fn){ var fns=allfns[this]; if(!fns){ allfns[this]={}; fns=allfns[this]; } var fs=fns[type]; if(fs){ fs.push(fn); }else{ fns[type]=[fn]; } this["on"+type]=this.proxy; } Element.prototype.proxy=function(event){ var type=event.type; var fns=allfns[this]; var fs=fns[type]; if(fs){ for(var index in fs ){ fs[index](); } } }})()
这样就无敌了,对吧,我也是这样认为的,但尝试一下就会发现还是有可能重复,尤其是相似的Element之间(比如2个input),原因就在于json中的key(他必须是字符串,并非引用), allfns[this] 会将this的toString/valueOf(详细可看转换规则),转换为key值,所以
allfns[this]有时就等价于allfns["[object HTMLInputElement]"]
json的key必须是字符串,记得当年有道面试题就是用js写一个map,原因就是这个,我们可以修改toString,将他改成hash,或者直接提供一个hash接口,当然通过自增的方式(id),也是不错的选择
(function(){ var allfns={}; var sequence=1; Element.prototype.addEvent =function(type, fn){ var id=this.sequence; if(!id){ id=this.sequence=sequence++; } var fns=allfns[id]; if(!fns){ fns=allfns[id]={}; } var fs=fns[type]; if(fs){ fs.push(fn); }else{ fns[type]=[fn]; } this["on"+type]=this.proxy; } Element.prototype.proxy=function(event){ var id=this.sequence; if(!id){ return; } var type=event.type; var fns=allfns[id]; var fs=fns[type]; if(fs){ for(var index in fs ){ fs[index](); } } }})()
一个可以添加的模型产生了,现在再考虑一下事件的删除,我们可以通过delete删除,但数组并不是链式的,删了以后坑依然留着,如果这不算bug的话,一个简单的兼容事件模型已经产生了
我们还可以参考jquery的事件模型(反正当我写完一个事件模型以后,在看jquery,我都想告jquery侵权- -!)