Javascript事件模型系列(一)事件及事件的三种模型

一、开篇

在学习javascript之初,就在网上看过不少介绍javascript事件的文章,毕竟是js基础中的基础,文章零零散散有不少,但遗憾的是没有看到比较全面的系列文章。犹记得去年这个时候,参加百度的实习生面试,被问到事件模型,当时被问的一头雾水,平时敲onclick敲的挺爽,却没有关注到事件模型的整体概念。这个周末难得清闲,决定就javascript中的事件模型写个系列,算是对知识点的一个总结,也是对自己的一个交代。

初步计划分为以下几个部分:

    ①   javascript事件的基本概念及基于原始、IE、DOM2的三种模型的异同点

    ②   javascript事件流介绍,捕获-冒泡机制及事件委托机制

    ③   jquery中的事件监听方式(bind、live、attachEvent、on)及异同点

    ④   javascript自定义事件

二、event简介

什么是事件呢?直观的说就是网页上发生的事情,大部分是指用户的鼠标动作和键盘动作,如点击、移动鼠标、按下某个键。为什么说大部分呢,因为事件不单单只有这两部分,还有其他的例如document的load和unloaded。只不过我们更加关注的是用户的操作。事件被封装成一个event对象,包含了该事件发生时的所有相关信息(event的属性)以及可以对事件进行的操作(event的方法)。

event长啥样呢,来直观的看一下,比如我点击页面上一个按钮,产生的event对象如下:

Javascript事件模型系列(一)事件及事件的三种模型

可以看到是一个MouseEvent对象,包含了一系列属性,如鼠标点击的位置等。那么敲击键盘时产生的event对象和它一样吗?看看就知道:

Javascript事件模型系列(一)事件及事件的三种模型

    可以看到是一个KeyboardEvent对象,并且属性跟上面的也不太一样,如没有clientX/Y,那是理所当然的啦,敲键盘怎么能获取到鼠标的位置呢。

若你有一点面向对象编程的基础,看到这两个类名应该会有所思考,MouseEvent、KeyboardEvent会不会是继承自一个叫Event的类呢?恭喜你猜对了,确实如此。来看一下,我在window.onload监听函数中打印出event对象如下:

Javascript事件模型系列(一)事件及事件的三种模型

    属性少了很多,毕竟是父类嘛。若你想了解更多关于事件类型的内容,可以参考这里,本文就不做更深的介绍。

三、event对象常用属性、方法

1. 事件定位相关属性

    这部分属性平时用的还是挺多的,所以得着重介绍。如果你细细看了MouseEvent对象里的属性,一定发现了有很多带X/Y的属性,它们都和事件的位置相关。具体包括:x/y、clientX/clientY、pageX/pageY、screenX/screenY、layerX/layerY、offsetX/offset 六对。有点乱了吧,一个点击事件能有多少位置啊?不要着急,其实并不复杂,之所以能有这么多是因为各浏览器厂商在版本更迭的时候产生了很多不一致。看下面的例子就明白各自的含义了:

在这里移动鼠标
x:   y:
clientX:  
clientY:
screenX:  
screenY:
offsetX:  
offsetY:
pageX:  
pageY:
layerX:  
layerY:

    得出的结论如下:

    x/y与clientX/clientY值一样,表示距浏览器可视区域(工具栏除外区域)左/上的距离;

    pageX/pageY,距页面左/上的距离,它与clientX/clientY的区别是不随滚动条的位置变化;

    screenX/screenY,距计算机显示器左/上的距离,拖动你的浏览器窗口位置可以看到变化;

    layerX/layerY与offsetX/offsetY值一样,表示距有定位属性的父元素左/上的距离。

    之所以有那么多值一样的情况,就是由于浏览器兼容的原因。那我们平时该如何使用呢?请看下面的表格,列出了各属性的浏览器支持情况。(+支持,-不支持)

    offsetX/offsetY:W3C- IE+ Firefox- Opera+ Safari+ chrome+

    x/y:W3C- IE+ Firefox- Opera+ Safari+ chrome+

    layerX/layerY:W3C- IE- Firefox+ Opera- Safari+ chrome+

    pageX/pageY:W3C- IE- Firefox+ Opera+ Safari+ chrome+

    clientX/clientY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+

    screenX/screenY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+​

  说明:该表摘自其他文章,我未做全部验证,但从最新版本的现代浏览器来看,这些属性貌似是都支持了,为了更好的兼容性,通常我们选择W3C支持的就可以了。若你想看更加细致的相关描述,请点击这里

2.其他常用属性

    target:发生事件的节点;

    currentTarget:当前正在处理的事件的节点,在事件捕获或冒泡阶段;

      timeStamp:事件发生的时间,时间戳。

    bubbles:事件是否冒泡。

    cancelable:事件是否可以用preventDefault()方法来取消默认的动作;

    keyCode:按下的键的值;

3. event对象的方法

    event. preventDefault()//阻止元素默认的行为,如链接的跳转、表单的提交;

    event. stopPropagation()//阻止事件冒泡

    event.initEvent()//初始化新事件对象的属性,自定义事件会用,不常用

    event. stopImmediatePropagation()//可以阻止掉同一事件的其他优先级较低的侦听器的处理,(我没有用过)

四、事件的三种模型

由于复杂的历史原因,事件模型是不统一的,当然作为前端开发人员这种事情已经见怪不怪了。尽管W3C已经制定了DOM2标准来规范事件的定义,但由于顽固的IE6、7、8存在,我们还是得清楚IE的那一套定义。那么来看看三种模型都有哪些吧。

1.     原始事件模型

    在原始事件模型中(也有说DOM0级),事件发生后没有传播的概念,没有事件流。事件发生,马上处理,完事,就这么简单。监听函数只是元素的一个属性值,通过指定元素的属性值来绑定监听器。书写方式有两种:

    ①   HTML代码中指定属性值:<input type=”button” onclick=”func1()” />

    ②   在js代码中指定属性值:document.getElementsByTagName(‘input’)[0].onclick = func1

    优点:所有浏览器都兼容

    缺点:1)逻辑与显示没有分离;2)相同事件的监听函数只能绑定一个,后绑定的会覆盖掉前面的,如:a.onclick = func1; a.onclick = func2;将只会执行func2中的内容。3)无法通过事件的冒泡、委托等机制(后面系列会讲到)完成更多事情。

    在当前web程序模块化开发以及更加复杂的逻辑状况下,这种方式显然已经落伍了,所以在真正项目中不推荐使用,平时写点博客小例子啥的倒是可以,速度比较快。

2.     IE事件模型

    在参考其他资料时,我有看到这样的一句话“IE不把该对象传入事件处理函数,由于在任意时刻只会存在一个事件,所以IE把它作为全局对象window的一个属性”,为求证其真伪,我用IE8执行了代码alert(window.event),结果弹出是null,说明该属性已经定义,只是值为null(与undefined不同)。我想难道这个全局对象的属性是在监听函数里才加的?于是执行下面代码:

    window.onload = function (){alert(window.event);}

    setTimeout(function(){alert(window.event);},2000);

    结果第一次弹出【object event】,两秒后弹出依然是null。由此可见IE是将event对象在处理函数中设为window的属性,一旦函数执行结束,便被置为null了。IE的事件模型只有两步,先执行元素的监听函数,然后事件沿着父节点一直冒泡到document。冒泡机制后面系列会讲,此处暂记。IE模型下的事件监听方式也挺独特,绑定监听函数的方法是:attachEvent( "eventType","handler"),其中evetType为事件的类型,如onclick,注意要加’on’。解除事件监听器的方法是 detachEvent("eventType","handler" )

    IE的事件模型已经可以解决原始模型的三个缺点,但其自己的缺点就是兼容性,只有IE系列浏览器才可以这样写。

3.     DOM2事件模型

    此模型是W3C制定的标准模型,既然是标准,那大家都得按这个来,我们现在使用的现代浏览器(指IE6~8除外的浏览器)都已经遵循这个规范。W3C制定的事件模型中,一次事件的发生包含三个过程:

    (1)capturing phase:事件捕获阶段。事件被从document一直向下传播到目标元素,在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行。

    (2)target phase:事件处理阶段。事件到达目标元素,执行目标元素的事件处理函数.

    (3)bubbling phase:事件冒泡阶段。事件从目标元素上升一直到达document,同样依次检查经过的节点是否注册了该事件的监听函数,有则执行。

    所有的事件类型都会经历captruing phase但是只有部分事件会经历bubbling phase阶段,例如submit事件就不会被冒泡。

    你可能会有疑问,为什么是这个样子的呢?流程有点太多了吧?事情的缘由还得从网景公司与微软争霸开始说起。在W3C的规范还没有出生的时候,市场上已经有两家强劲的浏览器厂商,产品分别是微软的IE和网景的Netspace Navigator(后面简称NN),IE的事件模型上面已介绍,事件是可以冒泡的。然而NN却不这么认为,它的模型中,事件是从上往下走的,即只有捕获阶段。两家都没有谁对谁错,因为按照他们的模型都可以完成事件的处理。然后W3C珊珊来迟,要制定标准,要统一,所以也就只能两家的都采纳,谁也不得罪,然后用标准制定者的口吻宣布:W3C模型工作良好。从此天下太平。

    说远了,赶紧来看看标准的事件监听器该如何绑定:addEventListener("eventType","handler","true|false");其中eventType指事件类型,注意不要加‘on’前缀,与IE下不同。第二个参数是处理函数,第三个即用来指定是否在捕获阶段进行处理,一般设为false来与IE保持一致,除非你有特殊的逻辑需求。监听器的解除也类似:removeEventListner("eventType","handler","true!false");

        以上便是事件的三种模型,我们在开发的时候需要兼顾IE与非IE浏览器,所以注册一个监听器应该这样写:

var a = document.getElementById('a');
if(a.attachEvent){
a.attachEvent('onclick',func);
}
else{
a.addEventListener('click',func,false);
}

  感觉很麻烦吧?因此我们一般会借助现有框架或类库已经封装好的,比如jQuery,后面将会介绍jQuery中强大的事件监听方式。

    系列一到此结束,作者本人技术水平有限,文章内容都是自己的理解写出来的,欢迎各路高手指点纠错。

上一篇:浏览器与Node的事件循环(Event Loop)有何区别?


下一篇:跨平台运行ASP.NET Core 1.0