你若触发,我就处理——浅谈JavaScript的事件响应

1180 查看

每当猴子们问我JavaScript和DOM里啥东西最牛逼时,我都会一巴掌打回去:卧槽还用问么当然是事件响应了啊!没它你能有时间和我讨论这个?你早去工地搬砖去了好么!浏览器没有事件响应就没有行为交互,那简直就是一夜回到解放前的感觉啊。此外,以事件驱动使得功能解耦也是个相当高端大气的技巧了,嘛,以此为主的Node.js 现在可是风生水起的说。

那现在我们就再抠抠事件监听的相关基础,让大家在心情愉悦的状态下获得更多的姿♂势。不过那些经常写<a href=”javascript:void(0)”>和在标签上写onclick=”foo()”的猴子们请自动回避,小心你看不懂又想不开,老衲徒增罪孽呀(偶八年前就解释了内联事件处理是自寻死路)。

再唠叨两句:本文的代码内容只涉及到原生JavaScript,像JQuery,YUI或Dojo什么的所提供的事件处理这里就不加以赘述了。我希望大家能够明白,使用这些库只是为了方便,但我们却不能完全依赖它。了解基础与理解本质是非常重要的,这样你就可以在不能使用傻瓜库的情况下,依旧可以提供一个牛逼的解决方案。

进化主义声明:这里我们使用的事件语法是“DOM Level 3 Events”规范定义的“addEventListener()。除了IE9以下版本以外的现代浏览器都支持。当然,我们可以使用JQuery,它会帮我解决这些浏览器兼容性的问题。但如果你还想让互联网可以良好发展和进化,你就应该立刻停止为兼容低级浏览器而再去写一坨屎一样的傻逼兼容代码。这条路虽然艰辛,但却无比正确。可以试着给你的产品进行功能降级,检测到是低级浏览器就不执行脚本,比如addEventListener()的DOMContentLoaded事件就能确保你的代码不在低级浏览器中运行,而页面可以将主体内容正常显示就OK的。

在我们进入事件的细节之前先看几个牛逼的演示,它利用onscroll事件得到了一个很nice的效果:

  • 因为要招设计师,Wealthfront的工程师们开发了Z轴滚动平移效果。这也是Beercamp 2011 website的一部分。Wealthfront的博客有细节介绍。
  • Stroll.js用的也是类似的手法,用户可以在滚动列表时看到很多种炫酷展现。
  • jQuery Scroll Path是一个JQ插件,它的功能是当用户在页面内能够跟随着一条路径去动态浏览内容。

以上所有都是基于浏览器的事件监听和处理功能,所以,让我们细细品味一下原汁原味的事件机制吧。

基础问题:啥是事件?

在浏览器中运行如上代码,亲们可以得到如下:

onmouseenter, onmouseleave, onafterprint, onbeforeprint, onbeforeunload, onhashchange, onmessage, onoffline, ononline, onpopstate, onpagehide, onpageshow, onresize, onunload, ondevicemotion, ondeviceorientation, onabort, onblur, oncanplay, oncanplaythrough, onchange, onclick, oncontextmenu, ondblclick, ondrag, ondragend, ondragenter, ondragleave, ondragover, ondragstart, ondrop, ondurationchange, onemptied, onended, onerror, onfocus, oninput, oninvalid, onkeydown, onkeypress, onkeyup, onload, onloadeddata, onloadedmetadata, onloadstart, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onmozfullscreenchange, onmozfullscreenerror, onpause, onplay, onplaying, onprogress, onratechange, onreset, onscroll, onseeked, onseeking, onselect, onshow, onstalled, onsubmit, onsuspend, ontimeupdate, onvolumechange, onwaiting, oncopy, oncut, onpaste, onbeforescriptexecute, onafterscriptexecute

一大坨事件就够你吃几天的了,用addEventListener()方法可以进行事件监听:

举个例子来说:

我们在一个element上加了个事件监听,就好像是在命令她,“你被客人摸了你就给我喊起来!” The ajaxloader()是监听事件的回调方法,就好像是,“你就在这儿给我盯着,妞要是喊了,你就过去给客人道歉!” 将第三个参数useCapture设置为false是为了表示这次是在事件冒泡阶段进行触发,而不是在事件捕获阶段。咳咳,这是一个漫长而艰巨的课题,你也可以看看Dev.Opera对capture的解释。哎呀反正不用管那么多啦,99.7434%的情况下设置useCapture为false准没错!其实它默认就是false,所以按理来说是可以不用填写的,但Opera这逗比例外…

在事件被触发之时,回调方法会接收到一个事件对象。请试着在恰当的环境中运行如下代码,也可以直接点击这里测试这个例子,对象内的属性又够吃一盆的:

你可以对同一事件进行多重监听,也可以对多个事件使用同一方法处理(如本例)。

参数ev是传回来的事件对象,下面是它所带的全部属性:

试一下本例中点击鼠标和按键盘,不同的事件触发传回来的结果是不同的。可以看看完整的标准事件属性列表

知晓基础之后:阻止事件默认行为的执行和获得触发事件的目标元素

我们需要了解浏览器中关于事件对象有两个很重要的功能。阻止浏览器执行事件默认行为的ev.preventDefault(),和可以获得事件目标元素的ev.target.

比如说一个链接被点击了,但因为业务需要,我们并不想让浏览器打开新页面。这时候可以给这个链接加个点击事件监听,然后在回调函数中调用 preventDefault()方法即可。昂,就如下面这个例子,请看HTML:

还有JavaScript:

注意: document.querySelector() 是合理获取DOM元素的一种方式。和jQuery的 $() 差不多。 可以读读 W3C’s specification 和MDN的 explanatory code snippets 去了解。

如果点击.prevent链接,会弹出个对话框,点“确定”后啥事都没发生,呵~呵~,因为处理中有执行ev.preventDefault(),所以不会跳到 http://smashingmagazine.com。没有 preventDefault()的就会在弹对话框,且跳链接咯。不信你可以试一下嘛

通常情况下,处理事件的方法想要访问触发元素只能通过变量和this什么的去关联,虽然看似简单方便,但addEventListener()给了我们更好的选择:事件目标(target),不过它可能被其他的一些东西混淆,所以用ev.currentTarget更保险些。通常想要在点击、悬停或输入事件的回调方法中访问触发元素都是用变量 this 。虽然方便,但这个关键字你懂得…于是 addEventListener() 提供给我们一个更好的获取方式:event.target。 不过它可能会被混淆( this 被绑到奇怪的东西上的时候),所以用 ev.currentTarget 更保险些。

事件代理:高端,大气,上档次!

用事件对象的 target 属性,你可以得到触发事件的元素。

事件被激活后,会像猴子一样沿着DOM树从监听节点下滑到触发节点,然后再上爬回监听节点。也就是说,如果你监听了一个DOM节点,那也就等于你监听了其所有的后代节点。代理的意思就是只监听父节点的事件触发,以来代理对其后代节点的监听,而你需要做的只是通过 target 属性得到触发元素并作出回应。来看我下面的例子