事件流是js dom操作的核心,对这块的基础知识其实并不太难,但是有些概念性的东西也是需要反复回味和总结,方能有新的感悟。
dom 事件流
事件流包括三个阶段 —— 开始从文档的根节点流向目标对象(捕获阶段),然后在目标对象上被触发(目标阶段),之后再回溯到文档的根节点(冒泡阶段)。冒泡过程非常有用。它将我们从对特定元素的事件监听中释放出来,相反,我们可以监听 DOM 树上更上层的元素,等待事件冒泡的到达。如果没有事件冒泡,在某些情况下,我们需要监听很多不同的元素来确保捕获到想要的事件(所有的事件都要经过捕捉阶段和目标阶段,但是有些事件会跳过冒泡阶段。例如,让元素获得输入焦点的 focus 事件以及失去输入焦点的 blur 事件就都不会冒泡)。
事件处理程序
1.HTML 事件处理程序
HTML中直接定义的事件处理程序,也可以调用其它地方定义的脚本。通过 HTML 指定的事件处理程序都需要HTML的参与,即结构和行为相耦合,不易维护。
<!-- 输出 click -->
<input type="button" value="Click Me" onclick="console.log(event.type)">
<!-- 输出 Click Me this 值等于事件的目标元素 -->
<input type="button" value="Click Me" onclick="console.log(this.value)">
2.DOM0 级事件处理程序
将一个函数赋值给一个事件处理程序的属性,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。要删除事件将 btn.onclick 设置为 null 即可。
<input type="button" value="Click Me" id="btn">
<script>
var btn=document.getElementById("btn");
btn.onclick=function(){
console.log(this.id);
}
</script>
3.DOM2 级事件处理程序
DOM2 级事件定义了addEventListener() 和 removeEventListener()两个方法。所有 DOM 节点都包含这两个方法,它们接受3个参数:要处理的事件名 、作为事件处理程序的函数 和 一个布尔值。最后的布尔值参数是 true 表示在捕获阶段调用事件处理程序,如果是 false(默认) 表示在冒泡阶段调用事件处理程序。
<input type="button" value="Click Me" id="btn">
<script>
var btn=document.getElementById("btn");
var handler = function(){
console.log(this.id);
}
btn.addEventListener("click", handler, false);
btn.removeEventListener("click",handler, false);
</script>
4.IE 事件处理程序
IE通常添加和删除事件处理程序的方法分别是:attachEvent() 和 detachEvent()。同样接受事件处理程序名称与事件处理程序函数两个参数,但跟addEventListener()的区别是:
- 事件名称需要加“on”,比如“onclick”;
- 没了第三个布尔值,IE8及更早版本只支持事件冒泡;
- 仍可添加多个处理程序,但触发顺序是反着来的。
DOM0 和 DOM2 级的方法,其作用域都是在其所依附的元素当中,
attachEvent()则是全局,即如果像之前一样使用this.id,访问到的就不是 button 元素,而是 window,就得不到正确的结果。
跨浏览器事件处理程序的兼容写法(适配不同类型的浏览器):
var EventUtil={
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
} else if(element.attachEvent){
element.attachEvent(“on”+ type,handler);
} else {
element[“on” + type]=handler;
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
} else if(element.detachEvent){
element.detachEvent(“on”+ type,handler);
} else {
element[“on” + type]=null;
}
}
}
5.阻止事件冒泡/停止传播(Stopping Propagation)
通过调用事件对象的 stopPropagation 方法,在任何阶段(捕获阶段或者冒泡阶段)中断事件的传播。此后,事件不会在后面传播过程中的经过的节点上调用任何的监听函数。但是不阻止当前节点上此事件其他监听函数被调用。如果要阻止当前节点上的其他回调函数被调用,可以使用 event.stopImmediatePropagation()方法。
<input type="button" value="Click Me" id="btn">
<script>
var btn=document.getElementById("btn");
btn.onclick = function (event) {
console.log("Clicked"); // 触发
event.stopPropagation();
}
document.body.onclick = function (event) {
console.log("Body clicked"); // 传播被阻断,不触发
}
</script>
6.阻止浏览器默认行为
当特定事件发生的时候,浏览器会有一些默认的行为作为反应。最常见的事件不过于 link 被点击。当一个 click 事件在一个<a>元素上被触发时,它会向上冒泡直到 DOM 结构的最外层 document,浏览器会解释 href 属性,并且在窗口中加载新地址的内容。我们要阻止浏览器针对点击事件的默认行为,而使用我们自己的处理方式。这时,我们就需要调用 event.preventDefault().
我们可以阻止浏览器的很多其他默认行为。比如,我们可以在 HTML5 游戏中阻止敲击空格时的页面滚动行为,或者阻止文本选择框的点击行为。
事件类型
不同的事件类型具有不同的信息,常用的大致可分为如下几类:
- UI:load、 error(错误触发)、select、resize、scroll等
- 焦点:blur、focus、change(当用户提交对元素值的更改时触发)等
- 鼠标:click、dblclick、mousedown/up、mouseenter/leave、mousemove、mouseover等
- 键盘:keydown/up、keypress
- 触摸:touchstart(即使已经有一个手指放在屏幕上也会触发)、touchend、touchmove等
- 手势:gesturestart、gestureend、gesturechange等
- 设备:orientationchange(检测设备屏幕旋转)、deviceorientation(检测设备方向变化)等 上面这些,大都是人为操作,还有些事件是网页状态带来的,比如:网页加载完成、提交表单、网页出错等。 除此之外,还有变动事件,复合事件,HTML5新加入的一些事件。完整的事件列表可在这里查看 Web Events
事件操作
事件委托/代理事件监听
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click 事件会一直冒泡到 document 层次,也就是说,我们可以为整个页面指定一个 onclick 事件处理程序,而不必为每个可点击的元素分别添加事件处理程序。
<ul id="test">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
<script>
window.onload = function(){
var test = document.getElementById("test");
test.onclick = function(){
alert(123);
}
}
</script>
移除事件处理程序
内存中留有那些过时不用的“空事件处理程序”,也是造成 web 应用程序内存与性能问题的主要原因(主要针对ie浏览器)。
第一种情况: 从文档中移除带有事件处理程序的元素时。纯粹的DOM操作,例如使用removeChild()和replaceChild()方法,但更多地是发生在使用 innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被 innerHTML 删除了,那么原来添加到元素中的事件处理程序极有可能被当作垃圾回收。
<div id="myDiv">
<input type="button" value="ClickMe" id="myBtn">
</div>
<script>
var btn = document.getElementById("myBtn");
btn.onclick=function(){
btn.onclick=null; // 我们设置<div>的innerHTML属性前,先移除了按钮的事件处理程序
document.getElementById("myDiv").innerHTML="Processing…";
}
</script>
第一种情况: 卸载页面中的时候。如果在页面被卸载之前没有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页面间来加切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序占用的内存并没有被释放。
一些常用的事件操作
// 事件对象的封装(跨浏览器兼容)
EventUtil = {
addHandler: function(element,type,handler){
// 省略代码
},
removeHandler: function(element,type,handler){
// 省略代码
},
getEvent: function(event){
return event?event:window.event;
},
getTarget: function(event){
return event.target || event.srcElement;
},
preventDefault: function(event){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue = false;
}
},
stopProgagation: function(event){
if(event.stopProgagation){
event.stopProgagation();
}else{
event.cancelBubble = true;
}
}
};
load 事件:
load 事件可以在任何资源(包括被依赖的资源)被加载完成时被触发,这些资源可以是图片,css,脚本,视频,音频等文件,也可以是 document 或者 window。
// image 元素 load
EVentUtil.addHandler (window, "load", function () {
var image = new Image();
// 要在指定 src 属性之前先指定事件
EVentUtil.addHandler (image, "load", function () {
console.log("Image loaded!");
});
image.src = "smile.gif";
})
// script 元素 load
EVentUtil.addHandler (window, "load", function () {
var script = document.createElement("script");
EVentUtil.addHandler (script, "load", function (event) {
console.log("loaded!");
});
script.src = "example.js";
document.body.appendChild(script);
})
onbeforeunload 事件(HTML5事件):
window.onbeforeunload 让开发人员可以在想用户离开一个页面的时候进行确认。这个在有些应用中非常有用,比如用户不小心关闭浏览器的 tab,我们可以要求用户保存他的修改和数据,否则将会丢失他这次的操作。
EVentUtil.addHandler (window, "onbeforeunload", function (event) {
if (textarea.value != textarea.defaultValue) {
return 'Do you want to leave the page and discard changes?';
}
});
resize 事件:
在一些复杂的响应式布局中,对 window 对象监听 resize 事件是非常常用的一个技巧。仅仅通过 css 来达到想要的布局效果比较困难。很多时候,我们需要使用 JavaScript 来计算并设置一个元素的大小。
EVentUtil.addHandler (window, "resize", function (event) {
// update the layout
});
error 事件:
当应用在加载资源的时候发生了错误,我们很多时候需要去做点什么,尤其当用户处于一个不稳定的网络情况下。Financial Times 中,我们使用 error 事件来监测文章中的某些图片加载失败,从而立刻隐藏它。由于“DOM Leven 3 Event”规定重新定义了 error 事件不再冒泡,我们可以使用如下的两种方式来处理这个事件。
imageNode.addEventListener('error', function(event) {
image.style.display = 'none';
});
获取鼠标在网页中的坐标:
// 鼠标事件参数 兼容性封装
var EventUtil = {
getEvent : function(e){
return e || window.event;
},
getTarget : function(e){
return this.getEvent(e).target || this.getEvent(e).srcElement;
},
getClientX : function(e){
return this.getEvent(e).clientX;
},
getClientY : function(e){
return this.getEvent(e).clientY;
},
// 水平滚动条偏移
getScrollLeft : function(){
return document.documentElement.scrollLeft || // 火狐 IE9及以下滚动条是HTML的
window.pageXOffset || // IE10及以上 window.pageXOffset
document.body.scrollLeft; // chrome 滚动条是body的
},
// 垂直滚动条偏移
getScrollTop : function(){
return document.documentElement.scrollTop || // 火狐 IE9 及以下滚动条是 HTML 的
window.pageYOffset || // IE10 及以上 window.pageXOffset
document.body.scrollTop; // chrome 滚动条是body的
},
getPageX : function(e){
return (this.getEvent(e).pageX)?( this.getEvent(e).pageX ):( this.getClientX(e)+this.getScrollLeft() );
},
getPageY : function(e){
return (this.getEvent(e).pageY)?( this.getEvent(e).pageY ):( this.getClientY(e)+this.getScrollTop() );
}
};