js中的事件(event)

什么是事件:



我们可以简单的把事件理解为浏览器的感知系统。比如说:他可以感觉到用户是否点击(click)了页面、鼠标是否进入了页面的某个元素上面(mouseover或mouseenter)、鼠标是否离开了网页(mouseout或mouseleave)、浏览器是都加载完了页面上的资源(window.onload)、文档树是否生成(DOMContentLoaded)、键盘上的某个键是否按下(keydown)、鼠标的滚轮是否滚动了等等。

  其实事件的原理并非是浏览器的感觉系统,它的本质是一个行为发生时,对另一个行为的回调。

事件的实现(事件绑定):


事件的绑定就是:当这个事件发生的时候,运行一个或者多个方法(function),比如说当鼠标点击页面的时候,就弹出一个“事件”,则写成:

document.onclick = function(){alert("事件“);}

事件的绑定相当于做计划,绑定在事件上的方法执行了就相当于计划的事发生了,所以一般情况下,事件属性的前面都有”on“,如:ele.onclick, ele.onmousedown, ele.onmouseup, 这里的on,其实就相当于:当什么时候,做计划要早于计划的事件发生。

当然我们也可以不给事件绑定处理方法,也就是说当此事件发生的时候,什么也不需要做,事件常有,而事件上绑定的方法不一定有,

我们给页面中的元素的某个事件绑定处理方法的时候。经常还会有一个形式参数e,但是运行的事件,却没有办法传递实参给这个形参e,比如:

function fn (e){

//标准浏览器中:定义一个形参e,但当事件触发的时候,并没有给e赋实际的值,则浏览器会把”事件“的对象赋给这个形参e,这时这个e是个系统级的对象:事件;

IE中的事件对象是个全局的属性window.event,而标准浏览器的事件对象就是形参e;

所以事件对象的兼容性写法为:e = e||window.event;

以下是常用的事件对象的属性:

var x =e.clientX,y=e.clientY;所有浏览器都支持,相当于浏览器中鼠标的坐标;

var x=e.pageX,y = e.pageY;ie8或以下不支持,相当于文档的中鼠标的坐标;

target事件源;事件源的概念:事件最终发生在页面的那个元素上;

事件源和事件的传播是息息相关的

事件的传播包括:冒泡和捕获;事件传播是浏览器在处理事件行为的机制,冒泡阶段或者捕获阶段。 注意:ie 678里,事件传播只有冒泡阶段;

var target = e.target||e.srcElement 后者是处理ie兼容‘;

div1.innerHTML="当前的事件类型:”+e.type+“鼠标的X坐标:“+x+”鼠标的Y坐标:“+Y+”事件发生在哪儿:“+e.target;

}

document.onmousemove=fn;把烦恼直接赋值给document的onmousemove这个属性

示例2:

var ele = document.getElememtById('div1');

document.onkeydown = function(e){

e=e||wondow.event;//处理事件对象兼容性

//console。log(e.keyCode):keyCode是是当前的这个键值对应的ASCII码

switch(e.keyCode){

case 37://键盘上的左键

    ele.style.left = ele.offserLeft-5+"px";

break;

case 38://上

ele.style.top = ele.offfsetTop-5+'px';

break;

}

}

以上这样的事件绑定方法(就是直接把fn赋给document.onmousemove的方式)叫DOM0级事件绑定,它是相当于DOM2级事件绑定来说的

DOM元素的默认行为:


  很多的网页元素都会有默认的行为,比如说当你点击一个超链接a0标签的时候,他就会有一个跳转行为;当你在网页上点击鼠标右键的时候会出现一个右键菜单;当你在一个form表单里点击提交按钮时网页会产生一个行为病刷新网页,当你网页上滚动鼠标滚轮的时候,页面的滚动条会滚动等等;这些都叫事件的默认行为,如果想把这些默认行为取消了,相应的js代码如下:

a.onclick = function(){return false}//方法里加个 return false,就是组织超链接点击时的跳转行为了;

document.oncontextmenu = function(){

//在这里可以加一些代码,实现自定义的右键菜单;

return false //系统自带的右键菜单就失效了

}

Form.onsubmit = function(){return false;}//这样表单就不会产生提交行为了;

document.onmousewheel = function(){return false;}//IE和chrome的方式,取消鼠标的滚轮的默认行为,网页的滚动条就不会动了;

document.addEventListener('DOMMouseScoll',function(e){e.preventDefault = true;});火狐的取消滚轮的默认行为;火狐只能用Dom的二级事件绑定方式,并且用e.preventDefault = true;来取消浏览器滚轮的默认行为;

我们要知道常见的事件默认行为有哪些,并且要知道阻止默认行为,只要绑定到这个行为事件的方法最后加一句:return false;就可以了;

但是要强调的是:如果你的事件绑定是用addEventListener来实现的,那阻止默认行为必须用e.preventDefault = true;


事件传播和阻止事件传播:


      当事件发生在子元素中的时候,往往会引起连锁反应,就是在它的祖先元素上也会发生这个事件,比如说你点击了一个div,也相当于点击了一个body,同样相当于点击了HTML,同样相当于点击了document,

在理解事件传播的时候要注意两点:

一、是事件本身在传播,而不是绑定在事件上的方法在传播;

二、是并非所有的事件都会传播,像onfocus,onblur等事件就不会传播,onmouseenter和onmouseleave事件也不会传播。

事件委托:


事件委托是利用事件的传播机制,通过判断事件源来实现的,是一种高性能的事件处理方式。对事件委托的好处和概念详见《高程3》的第402页;

我们通过一个简单的示例来看看事件的好处。

需求:在如下的HTML代码中,当你点击这个页面中的一个元素时,弹出这个元素对应的标签名;

<body>

<div id='outer'>

outer

<div id = 'inner'>

inner

<p>ppppp

<span>span

<a href='###'>张松</a>

</span>

</p>

</div>

</div>

</body>

一般的思路是把所有的元素都获取到,然后循环绑定,这样做的缺点就是不仅性能不好,并且还要处理事件传播的问题,不优化的代码如下;

var eles = document.getElementsByTagNmae('*');

for(var i=0;i<eles.length;i++){

   eles,item(i).onclick = function(e){

alert(this.tagName);

e.stopPropagation();//加上阻止事件传播是可以的,但是性能不是最优的;

return false;//阻止超链接的默认行为;

}

}


以下用事件委托实现

事件委托:事件委托就是利用事件传播的机制,无论哪一个页面元素,他的click事件都会最终传播到document上;这样,只需要在document上处理click事件即可;

document.onclick = function(e){

e = e||window.event;

var target = e.target||e.srcElement;//获得事件源是关键;

alert(target.nodeName);

return false;

};

事件委托的关键是理解号事件的事件源的概念;



DOM二级事件


DOM 是解决文档里元素关系的一套模式,其实那只是dom的第一个版本解决的问题。在dom的第二个版本里,解决的问题就不仅仅是文档里元素之间的关系了,还把dom元素的事件问题也重新给了一套方案,这套方案就叫做’dom二级事件‘;

DOM二级事件解决了原来的同意事件绑定多个处理方法时,后面绑定的会覆盖前面绑定的问题,如:

ele.onclick = fn1;

ele.onlcik=fn2;

这样的处理的结果就是,ele的onclick事件上,fn2方法把fn1方法给覆盖了,这样就不容易实现同一个事件上绑定多个方法。

W3C给出的方法是:ele.addEventListener('click',fn,false);

ie6/7/8给出的方法是:ele.attachEvent('onclick',fn);

解决标准浏览器和低版本的IE方案如下:

事件绑定:

function bind(ele,type,handler){//标准浏览器

     if(ele.addEventListener){

ele.addEventListener('type',handler,false);

}else if(ele.attachEveent){//IE专用

    ele.attachEvent('on'+type,handler)

}

}

移除事件绑定:

function unbind(ele,type,handler){

  if(ele.removeEventListener){

   ele.removeEventListener('type',handler,false);

}else if(ele.detachEvent){

    ele.detachEvent('on'+type,handler);

}

}

注意:虽然IE、谷歌、火狐等浏览器都给出了DOM2级事件的处理方法,但是IE的方法却存在着很多问题,并且非常严重。一、被绑定的方法在事件触发执行时,this关键字竟让是window,二、IE中被绑定到事件上的方法的执行顺序是混乱的。

在W3C的标准是在同一事件上,先绑定的方法先执行,并且不能重复绑定同一个方法在同一个事件上,但是IE6、7、8中,如果绑定的方法少于9个,执行的顺序是相反的,超过9个,执行顺序就是混乱的,这些IE中的问题都是比较严重的,我们必须解决好。

事件的兼容性问题总结:(常见的)


* 事件对象本身:标准浏览器是事件发生时自动给方法传递一个实参,这个实参就是事件对象,IE是全局的window.event;

* 阻止事件传播:标准e.stopPropagation这个方法;IE是e.cancelBubble = true这个属性;

* 阻止默认行为:e.preventDefault()方法,IE是e.returnValue = false;

* 事件源:e.target IE是e.srcElement;

* e.pageX ,e.pageY  IE不支持这两个属性

*DOM二级的事件绑定e.addEventListener ,IE ele.attachEvent;

*IE 的attachEvent 绑定的方法上:1、this不是当前的元素  2、执行顺序是混乱的

DOM二级事件兼容性问题解决之一:解决this关键字


注意:以下代码中的handler是个形参,他可以表示不同的方法,所以不要把handler认为是某一个具体的方法。如果你bind(ele,'click',fn1),则fn1就是handler,如果bind(ele,'click',fn2),那么fn2就是handler。新手一定要理解好把握好;

关键思路:关键是两个问题,一、理解好call的用途和作用,二、理解好在一个程序里写的代码,是为了解决另一个程序中的问题的这种思路。

我们修改一下上面写过的bind方法;

如果只是解决被绑定的方法的this指向,倒是好办,只需要在IE专用的代码里做如下修改就可以,

else if (ele.attachEvent){

   function fnTemp = function(){handler.call(ele)};//使用call方法,强制使handler方法在运行时this指向被绑定的ele这个DOM元素

ele.detachEvent('on'+type,fnTemp);//再绑定时,就不是直接绑定handler这个方法了,而是绑定经过’化装‘的fnTemp这个方法

}

或者直接写:

else if(ele.detachEvent){

   ele.detachEvent('on'+type,function(){handler.call(ele)};

}

这样确实在事件触发时,handler运行了,并且让handler的this指向了被绑定的元素ele,但是由于我已经不是直接绑定的handler方法,而是经过call变形后的fnTemp方法,那在移除绑定的时候,我们就没有办法移除handler方法了。

那我们怎么能找到这个化装之后的fntemp,并将其移除呢?这件事必须要在绑定事件的时候就要考虑好,在bind方法里,把fnTemp方法保存下来,并且还要用某种方式识别出来这个fnTemp方法是由那个handler’变形。而来的。

这是个不好解释的难点,做这件事,我们需要两步;

一、把fnTemp保存下来,不能使用全局变量保存,因为容易被污染,保存在bind的某个变量里,局部变量在unbind这个作用域内也访问不到,在不同的作用域里,还能访问到一个不是全局变量的值,那用什么呢?最好的方式就是把fnTemp保存在ele这个DOM元素的属性上,因为这个ele是两个函数都要操作的引用类型的变量,那么我们在ele上定义一个自定义属性,那这个属性在bind和unbind两个作用域里面都能访问到,具体实现方式如下:

else if(ele.attachEvent){

   if(!ele['aBind'+type){//如果不存在这个属性则创建一个

       ele['aBind'+type] = [];//使用数组来保存被绑定到不同事件上的那些方法(相当的事件上,可能会被绑定很多个handler)

//这个属性是以‘aBind"为前缀,以type为区分符的,Type是事件的类型,这是一个非常重要的技巧

}

var tempFn =function(){handler.call(ele)};让这个方法运行是this关键字指向被绑定的元素;

ele['aBind'+type].push(tempFn);把变形后的tempFn保存在数组里

ele.attachEvent('on'+type,tempFn);

}

补充:理解好ele['aBind'+type]=[]这个属性定义时,’aBind‘这个字符串是区别符的意思。

先看如果没有这个区别符会怎么样?那就定义了ele[type]=[];如果type是’click‘,则会出现ele.click=[];而ele本身就有click这个方法属性,我们就没有办法修改这个原生的属性,这样定义就失效了。所以才给type前面加上'aBind’区别符作为前缀,以避免或减少和原生的属性的冲突,这种加前缀的技巧还是很常见。

二、是给tempFn再添加一个自定义属性,用来标示当前这个tempFn是由handler变形而来的

var tempFn= function(){handler.call(ele);}

tempFn.photo = handler ;//我们通过tempFn的photo这个属性,就可以分辨出tempFn这个方法是由那个handler变形而来的,photo这个属性只是在这定义,而使用它是在unbind函数里。

三、还要强调一个DOM2级事件绑定变成的原则,即:一个函数不能重复绑定在同一个事件上。比如:不能把fn1这个函数重复的绑定给ele的click事件;

ele.addEventListener('click',fn1,false);

ele.addEventListener('click',fn1,false);//绑定两次或者多次,在事件触发时,后面的绑定是无效,

当然,低版本的IE浏览器是没有遵循这个原则的,所以这儿还要解决以下这个问题,加一个判断即可

for(var i=0;i<ele['aBind'+type].length;i++){

if(ele['aBind'+type][i].photo==handler){//如果数组中已经存在了经过化妆的handler方法,则退出循环;

return;//保证一个方法只能被绑定到某个事件上一次;


}

}

Bind方法的完整代买如下:


function bind(ele,type,handler){

   if(ele.addEventListener){

      ele.addEventListener(type,handler,false);

}else if(ele.attachEvent){

    if(!ele['aBind'+type]){ele['aBind'+type]=[];}

var tempFn = function(){handler.call(ele)};

tempFn.photo=handler;

for(var i=0;i<ele['aBind'+type].length;i++){

  if(ele['aBind'+type][i].photo ==handler){return;}

}

ele['aBind'+type].push(tempFn);

ele.attachEvent('on'+type,tempFn);

}

}

我们再来看unbind,该做的准备工作,都已经在bind里完成了。unbind负责把绑定的在事件上的方法移除,但现在已经不是移除handler这个方法了,而是移除经过化装后的这个方法。这个方法保存在ele的['aBind'+type这个属性上。

我们知道ele['aBind'+type]这是个数组,先把它取到。赋值给一个短变量a:var a=ele['aBind'+type]。操作a这个短变量比操作ele['aBind'+type]这个属性更方便。

然后遍历这个数组,逐个比较那个是经过化装的handler方法,当然比较的一句是photo这个属性所以是:

for (var i=0;i<a.length;i++){

     if(a[i].photo==handler){

           ele.detachEvent('on'+type,a[i]);

     a.splice(i,1);//移除时间之后一定要把这个方法从数组里移除了,要不然下一次就不能再绑定了。

return;//以为每一次绑定都是唯一的(bind上讲到过),所以移除后直接结束这个函数的运行就可以了;

}

}

因为用splice会造成“数组塌陷”,所以在正式的代码里,用a[i]=null来解决的,

完整的unbind代码如下:

function unbind(ele,type,handler){

if(ele.removeEventListener){

ele.removeEventListener(type,handler,false);

}else if(ele.detachEvent){

var a=ele['aBind'+type];

if(a){

for(var i=0;i<a.length;i++){

if(a[i].photo==handler){ele.detachEvent('on'+type,a[i]);a[i] =null;return;

}

}

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容