这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要介绍了原生 JavaScript 事件,包括 DOM0 事件模型、DOM2 事件模型等的相关知识, 需要的朋友可以参考下
JQuery 这种 Write Less Do More 的框架,用多了难免会对原生 js 眼高手低。
小菜其实不想写这篇博客,貌似很初级的样子,但是看到网络上连原生 js 事件绑定和解除都说不明白,还是决定科普一下了。
首先声明,小菜懂的也不是很多,只是把我的思路和大家分享一下。
DOM0 事件模型
事件模型在不断发展,早期的事件模型称为 DOM0 级别。
DOM0 事件模型,所有的浏览器都支持。
直接在 dom 对象上注册事件名称,就是 DOM0 写法,比如:
- document.getElementById("test").onclick = function(e){};
意思就是注册一个 onclick 事件。当然,它和这种写法是一个意思:
- document.getElementById("test")["onmousemove"] = function(e){};
这没什么,只不过是两种访问 js 对象属性的方法,[] 的形式主要是为了解决属性名不是合法的标识符,比如:object.123 肯定报错,但是 object["123"] 就避免了这个问题,与此同时,[] 的写法,也把 js 写活了,用字符串表示属性名称,可以在运行时动态绑定事件。
言归正传,事件被触发时,会默认传入一个参数 e,表示事件对象,通过 e,我们可以获取很多有用的信息,比如点击的坐标、具体触发该事件的 dom 元素等等。
基于 DOM0 的事件,对于同一个 dom 节点而言,只能注册一个,后边注册的同种事件会覆盖之前注册的。例如:
- var btn = document.getElementById("test");
- btn.onmousemove = function(e){
- alert("ok");
- };
- btn["onmousemove"] = function(e){
- alert("ok1");
- };
结果会输出 ok1。
接下来再说说 this。事件触发时,this 就是指该事件在哪个 dom 对象上触发。例如:
- var btn = document.getElementById("test");
- btn.onmousemove = function(e){
- alert(this.id);
- };
结果输出 test。因为事件就是在 id 为 test 的 dom 节点上注册的,事件触发时,this 当然代表这个 dom 节点,可以理解为事件是被这个 dom 节点调用的。
所以,想解除事件就相当简单了,只需要再注册一次事件,把值设成 null,例如:
- var btn = document.getElementById("test");
- btn.onclick = function(e){
- alert("ok");
- };
- btn.onclick = null;
原理就是最后注册的事件要覆盖之前的,最后一次注册事件设置成 null,也就解除了事件绑定。
事情还没结束,DOM0 事件模型还涉及到直接写在 html 中的事件。例如:
- <div id="test" class="test" onclick="exec();" ></div>
通过这种方式注册的事件,同样遵循覆盖原则,同样只能注册一个,最后一个生效。
区别就是,这样注册的事件,相当于动态调用函数 (有点 eval 的意思),因此不会传入 event 对象,同时,this 指向的是 window,不再是触发事件的 dom 对象。
DOM2 事件模型
DOM2 事件模型相对于 DOM0,小菜仅仅了解如下两点:
· DOM2 支持同一 dom 元素注册多个同种事件。
· DOM2 新增了捕获和冒泡的概念。
DOM2 事件通过 addEventListener 和 removeEventListener 管理,当然,这是标准。
但 IE8 及其以下版本浏览器,自娱自乐,搞出了对应的 attachEvent 和 detachEvent,由于小菜才疏学浅,本文不做讨论。
addEventListener 当然就是注册事件,她有三个参数,分别为:"事件名称", "事件回调", "捕获 / 冒泡"。举个例子:
- var btn = document.getElementById("test");
- btn.addEventListener("click", function(e){
- alert("ok");
- }, false);
事件名称就不用多说了,相比 DOM0,去掉了前边的 on 而已。
事件回调也很好理解,事件触发了总得通知你吧!回调时和 DOM0 一样,也会默认传入一个 event 参数,同时 this 是指触发该事件的 dom 节点。
最后一个参数是布尔型,true 代表捕获事件,false 代表冒泡事件。其实很好理解,先来个示意图:
意思就是说,某个元素触发了某个事件,最先得到通知的是 window,然后是 document,依次而入,直到真正触发事件的那个元素 (目标元素) 为止,这个过程就是捕获。接下来,事件会从目标元素开始起泡,再依次而出,直到 window 对象为止,这个过程就是冒泡。
为什么要这样设计呢?这貌似是由于深厚的历史渊源,小菜也不怎么了解,就不乱说了。
由此可以看出,捕获事件要比冒泡事件先触发。
假设有这样的 html 结构:
- <div id="test" class="test">
- <div id="testInner" class="test-inner">
- </div>
- </div>
然后我们在外层 div 上注册两个 click 事件,分别是捕获事件和冒泡事件,代码如下:
- var btn = document.getElementById("test");
- //捕获事件
- btn.addEventListener("click", function(e){
- alert("ok1");
- }, true);
- //冒泡事件
- btn.addEventListener("click", function(e){
- alert("ok");
- }, false);
最后,点击内层的 div,先弹出 ok1,后弹出 ok。结合上边的原理图,外层 div 相当于图中的 body,内层 div 相当于图中最下边的 div,证明了捕获事件先执行,然后执行冒泡事件。
为什么要强调点击内层的 div 呢?因为真正触发事件的 dom 元素,必须是内层的,外层 dom 元素才有机会模拟捕获事件和冒泡事件,从原理图上就看出了。
如果在真正触发事件的 dom 元素上注册捕获事件和冒泡事件呢?
html 结构同上,js 代码如下:
- var btnInner = document.getElementById("testInner");
- //冒泡事件
- btnInner.addEventListener("click", function(e){
- alert("ok");
- }, false);
- //捕获事件
- btnInner.addEventListener("click", function(e){
- alert("ok1");
- }, true);
当然还是点击内层 div,结果是先弹出 ok,再弹出 ok1。理论上应该先触发捕获事件,也就是先弹出 ok1,但是这里比较特殊,因为我们是在真正触发事件的 dom 元素上注册的事件,相当于在图中的 div 上注册,由图可以看出真正触发事件的 dom 元素,是捕获事件的终点,是冒泡事件的起点,所以这里就不区分事件了,哪个先注册,就先执行哪个。本例中,冒泡事件先注册,所以先执行。
这个道理适用于多个同种事件,比如说一下子注册了 3 个冒泡事件,那么执行顺序就按照注册的顺序来,先注册先执行。例如:
- var btnInner = document.getElementById("testInner");
- btnInner.addEventListener("click", function(e){
- alert("ok");
- }, false);
- btnInner.addEventListener("click", function(e){
- alert("ok1");
- }, false);
- btnInner.addEventListener("click", function(e){
- alert("ok2");
- }, false);
结果当然是依次弹出 ok、ok1、ok2。
为了进一步理解事件模型,还有一种场景,假如说外层 div 和内层 div 同时注册了捕获事件,那么点击内层 div 时,外层 div 的事件一定是先触发的,代码如下:
- var btn = document.getElementById("test");
- var btnInner = document.getElementById("testInner");
- btnInner.addEventListener("click", function(e){
- alert("ok");
- }, true);
- btn.addEventListener("click", function(e){
- alert("ok1");
- }, true);
结果是先弹出 ok1。
假如外层 div 和内层 div 都是注册的冒泡事件,点击内层 div 时,一定是内层 div 事件先执行,原理相同。
细心的读者会发现,对于 div 嵌套的情况,如果点击内层的 div,外层的 div 也会触发事件,这貌似会有问题!
点击的明明是内层 div,但是外层 div 的事件也触发了,这的确是个问题。
其实,事件触发时,会默认传入一个 event 对象,前边提过了,这个 event 对象上有一个方法:stopPropagation,通过此方法,可以阻止冒泡,这样外层 div 就接收不到事件了。代码如下:
- var btn = document.getElementById("test");
- var btnInner = document.getElementById("testInner");
- btn.addEventListener("click", function(e){
- alert("ok1");
- }, false);
- btnInner.addEventListener("click", function(e){
- //阻止冒泡
- e.stopPropagation();
- alert("ok");
- }, false);
终于要说说怎么解除事件了。解除事件语法:btn.removeEventListener("事件名称", "事件回调", "捕获 / 冒泡");
这和绑定事件的参数一样,详细说明下:
· 事件名称,就是说解除哪个事件呗。
· 事件回调,是一个函数,这个函数必须和注册事件的函数是同一个。
· 事件类型,布尔值,这个必须和注册事件时的类型一致。
也就是说,名称、回调、类型,三者共同决定解除哪个事件,缺一不可。举个例子:
- var btn = document.getElementById("test");
- //将回调存储在变量中
- var fn = function(e){
- alert("ok");
- };
- //绑定
- btn.addEventListener("click", fn, false);
- //解除
- btn.removeEventListener("click", fn, false);
要想注册过的事件能够被解除,必须将回调函数保存起来,否则无法解除。
DOM0 与 DOM2 混用
事情本来就很乱了,这又来个混合使用,还让不让人活了。。。
别怕,混合使用完全没问题,DOM0 模型和 DOM2 模型各自遵循自己的规则,互不影响。
整体上来说,依然是哪个先注册,哪个先执行,其他就没什么了。
后记
至此,原生 js 事件已经讲的差不多了,小菜仅仅知道这些而已,欢迎读者补充其他知识点。
在实际应用中,真正的行家不会傻傻的真的注册这么多事件,一般情况下,只需在最外层 dom 元素注册一次事件,然后通过捕获、冒泡机制去找到真正触发事件的 dom 元素,最后根据触发事件的 dom 元素提供的信息去调用回调。
也就是说,行家会自己管理事件,而不依赖浏览器去管理,这样即可以提高效率,又保证了兼容性,JQuery 不就是这么做的嘛~
好了,教程到此结束,希望对读者有所帮助!
来源: http://www.phperz.com/article/17/0417/273655.html