您的位置:首页 > Web前端 > JavaScript

[Javascript]:DOM绑定事件、事件流机制、事件委托、事件对象

2017-09-19 00:59 555 查看
本章节涉及到的知识点都是以DOM标准为主,所以可能会和IE浏览器不兼容,请见谅。

事件

        事件是指发生的事情。在网页中,事件通常是指用户或浏览器自身执行的某种动作,也可以表示为文档和浏览器窗口发生的交互行为。

事件处理程序

        也叫事件句柄、事件处理函数。它用于表示事件发生时要执行的操作。响应事件的处理函数就是事件处理程序。例如触发一个按钮的onclick事件,浏览器便会执行相应的事件处理程序,执行什么完全取决于你的Javascript代码。

简单的事件实例

<script>
function test(){
alert("警告一下");
if(confirm("想关闭网页吗")){
window.close(); //关闭当前窗口
}
}
</script>
<button onclick="test()">点击我</button>
当单击按钮时,触发onclick事件,然后执行它的事件处理程序:test()。这种添加事件方式就像HTML元素中的样式属性一样,和HTML标签耦合度太高。怎么改变呢?这里就需要提到绑定事件的几种方式和DOM事件模型。

DOM事件模型和绑定事件

DOM的事件模型分为DOM0、DOM2、和DOM3三个级别。因为DOM1没有定义和事件相关的内容,所以DOM1不存在。DOM3则是在DOM2的基础上,给事件做了一些分类,还提供了自定义事件,当然,我这里的只是提供大概知识,至于它们的具体细节还需要你自己去查阅相关资料。
绑定事件:事件和事件处理程序建立联系,触发事件,则执行相应事件处理函数。 也可以叫事件处理程序和事件监听,虽然它们的说法和写法不同,但本质都是让一个事件绑定一个事件处理函数,所以我更喜欢用绑定这个词汇。在后面会提及以上各种说法,别把它们搞成不同概念。

DOM0的两种绑定事件方式

一. HTML属性绑定事件

事件作为DOM元素节点的属性,属性值是事件处理程序。

<button onclick="alert('HTML属性绑定事件');">点击</button>

这里,字符串形式的事件处理程序的就是Javascript代码,只要符合规定,就会被执行。就像eval()函数一样。如有多个语句,就用分号隔开。

HTML属性绑定事件的缺点

        JS代码和HTML代码紧密相连,修改其中之一,另一个也要修改;存在页面加载问题,当页面数据过多,如有大量图片,而<script>脚本放在页面底部,页面还没加载完成时就触发事件,引发错误。所以,这种方式已经被基本淘汰了。能不用就不用。

二. Javascript绑定事件

把一个函数名赋值给一个事件属性。也就是把函数指针赋值给事件。

格式:elementNode.event = 函数名/匿名函数。
<button id="btn">点击我</button>
<script>
var btn = document.getElementById("btn"); //获取元素节点
function test1(){
alert("单击按钮");
}
btn.onclick = test1; //函数名后别加括号,那相当于执行该函数然后返回函数值。
//匿名函数形式
btn.onmouseout = function(){
alert("鼠标离开按钮");
}
</script>

除了上面使用普通函数声明和匿名函数来绑定事件,还能用Function对象的构造函数来动态编译函数并执行,但绑定事件时不推荐使用。

DOM0解除绑定事件

        为什么要解除绑定事件呢?为了避免重复绑定。当然,在实际中,解除绑定事件用得较少,但也不是没用。解除方式:例如:btn.onclick = null ,让一个事件指向空指针,表示该事件无效,相当于让它找不到事件处理函数,自然就不会执行。

Javascript绑定事件与HTML属性绑定事件比较:

        相比于HTML属性绑定事件,这种方式更简单直观,而且每个浏览器都支持,可以跨浏览器使用。Javascript代码和HTML标签分离,结构和行为分开。就算HTML代码先于JS代码加载完成,相应事件也要等待JS代码执行完后才会绑定。

DOM0事件的细节问题

        每个事件只能绑定一个事件处理函数,如果同类事件再次绑定,后面的会覆盖前面。就和JS函数一样,多个同名函数只会执行一个,且是最后一个(这里不讨论JS函数重载机制)。DOM0级的事件名都是on + 事件名,这和DOM2绑定事件时用的事件名称稍有不同,关于DOM2,后面会说到。
<!-- DOM0级事件的同类型事件触发情况 -->
<button id="btn" onclick="alert('HTML属性绑定的事件onclick');">点击我</button>
<script>
var btn = document.getElementById("btn"); //获取元素节点
function test1(){
alert("JS绑定第一个");
}
btn.onclick = test1;
btn.onclick = function(){
alert("JS绑定第二个");
}
</script>
输出结果:JS绑定第二个 ,验证了DOM0级的事件只能调用一个事件处理函数的说法。

DOM2的绑定事件方式

DOM2事件模型的绑定事件也叫作事件监听,很符合方法名。这里只提供DOM的标准方法。

elementNode.addEventListener(event, function, useCapture)          添加事件监听

elementNode.removeEventListener(event, function, useCapture)    移除事件监听
参数说明
event :事件名称,去掉了on,例如”onclick“,填写时填‘’click“ 。

function:事件处理函数。

useCapture:是否在捕获阶段触发事件。true表示捕获阶段,false表示冒泡阶段。

添加事件监听实例

function listen(){
alert("事件监听");
}
//给元素节点添加事件监听
btn.addEventListener("click", listen, false); //冒泡阶段
btn.addEventListener("click", listen, true); //捕获阶段

移除事件监听实例

btn.removeEventListener("click", listen, false);

注意:添加事件监听的事件处理函数不能是匿名函数,因为找不到事件处理函数,所以没法移除。

除了常规用法,还可以只填入事件名和事件处理函数两个参数,这时就和DOM0的事件一样,默认使用冒泡阶段来调用事件处理函数。

btn.addEventListener("click", function (){
console.log("按钮");
}); //等同于添加false
div1.addEventListener("click", function (){
console.log("div1层");
},false);

相比于DOM0事件,DOM2的事件允许多次绑定。简要来说,就是一个节点上的同类型事件可以触发多次。

var btn = document.getElementById("btn");
btn.addEventListener("mouseover", function(){
alert("第一次放上去");
});
btn.addEventListener("mouseover", function(){
alert("第二次放上去");
});
btn.addEventListener("mouseover", function(){
alert("第三次放上去");
});


关于DOM2事件的绑定事件和移除事件方式已介绍完成,但其中涉及的冒泡阶段和捕获阶段又是什么?这就是我们下面要了解的重点。

DOM事件流机制和事件委托

事件流就是某个DOM节点触发事件时,事件会在DOM树的节点中一层一层的传递,这就形成了“流”。流具有方向,所以事件流就是触发事件的顺序。

JavaScript高级程序设计(第三版)关于事件流有一个很形象的比喻:

       可以想象画在一张纸上的一组同心圆,如果你把手指放在圆心上,那么你的手指指向的其实不是一个圆,而是纸上所有的圆。换句话说,在单击按钮的同时,你也单击了按钮的容器元素,甚至也单击了整个页面。

DOM2将事件流分为三个阶段:捕获阶段、处于目标阶段、冒泡阶段。

捕获阶段:从window对象出发,然后层层往下传递,直到目标节点事件之前(从外到内)。
目标阶段: 目标节点的触发事件。

冒泡阶段:从触发事件的目标节点的父节点开始,层层往上传递,直到window对象(从内到外)。DOM0事件的触发都在冒泡阶段。
用网上一张图来生动地说明这三个阶段:



事件流实例

<div id="div1">
<button id="btn">点击</button>
</div>
<script>
var btn = document.getElementById("btn");
var dvi1 = document.getElementById("div1"); //定位元素节点
//捕获阶段
window.addEventListener("click", function (){
console.log("window");
}, true);
document.addEventListener("click", function (){
console.log("document");
}, true);
btn.addEventListener("click", function (){
console.log("按钮");
}, true);
div1.addEventListener("click", function (){
console.log("div1层");
}, true);
//冒泡阶段  这里没用false,因为默认使用冒泡
window.addEventListener("click", function (){
console.log("window");
});
document.addEventListener("click", function (){
console.log("document");
});
btn.addEventListener("click", function (){
console.log("按钮");
});
div1.addEventListener("click", function (){
console.log("div1层");
});
</script>




通过实例,可以看出事件流的捕获阶段和冒泡阶段两者的触发事件顺序是相反的。而且事件触发后,在事件传递过程中,存在同类型事件的都会被激活。但有些事件是不应该被触发的,所以我们要阻止事件继续传播。

阻止事件传播

调用事件对象的stopPropagation()方法,可以阻止该事件继续传播到其他DOM节点上,就是说之后经过的节点不会再触发该类事件,在事件传播的任何阶段都能调用。

注意,它阻止的只是同类事件不再传播,并不会阻止其他类型事件的触发。

【上面提到了事件对象,这里先不介绍,后面会讲到】

以冒泡阶段为例,在div节点处调用事件对象的stopPropagation()

//冒泡阶段
document.addEventListener("click", function (){
console.log("document");
});
btn.addEventListener("click", function (){
console.log("按钮");
});
div1.addEventListener("click", function (event){
console.log("在div处停止传播");
event.stopPropagation(); //在此处阻止事件传播
});
执行结果:
按钮
div处停止传播
如果换成捕获阶段,那么结果就是document 、 div。 

事件委托

委托的意思:将自己的事务嘱托他人代为处理。放在这里理解就是:通过别的DOM节点来绑定事件,从而管理当前节点上的事件。

事件委托工作原理:根据DOM事件流的冒泡阶段,当子节点触发事件,会冒泡到父节点的相同事件上,所以可以通过父节点来监控子节点的冒泡事件。

不使用事件委托和事件委托的对比:

<script>
//传统的循环绑定方式,批量绑定事件不推荐 id
var ul = document.getElementById("ul_1");
var li_obj = ul.getElementsByTagName("li");//拿到子节点中所有的li标签节点
for(var i = 0; i < li_obj.length; i++){
li_obj[i].addEventListener("click", function(){
alert(this.childNodes[0].nodeValue); //拿到触发事件的节点对象的子文本节点
});
}
//事件委托方式
var ul = document.getElementById("ul_1");
ul.addEventListener("click", function(event){
var target = event.target; //拿到事件源。也就是触发该事件的节点
alert(target.childNodes[0].nodeValue);
});
</script>

观察这两个实例,虽然看起来差别不大,但当子节点越多越来,绑定事件就成了负担。如果这些子节点频繁删除和新增时,事件还没办法动态绑定,所以需要改进。

而使用事件委托则不用担心这些问题,通过事件冒泡,当父节点触发相同事件时,只需通过事件对象获取触发事件的真正来源。就可以针对触发事件的节点作相应处理。事件对象获取事件源方式:event.target。

事件对象

event对象在事件第一次触发时创建,并且一直伴随着事件在DOM节点结构中流转,直到生命周期结束。也就是说,事件源只有一个,这也是为什么事件委托中可以准确找到事件目标节点。
event对象会被作为事件处理函数的第一个参数传递给事件处理函数中。

event对象用于记录触发事件的一些信息,但是不同的事件对象具有不同的信息,例如键盘事件对象可以获取按了什么键。

event对象的常用属性和方法:

bubbles                     返回布尔值,指示事件是否是冒泡事件类型。                     

eventPhase                返回事件传播的当前阶段。

currentTarget             返回其事件监听器触发该事件的元素。   

target                         返回触发此事件的元素(事件的目标节点)。 

type                           返回当前 Event 对象表示的事件的名称。

preventDefault()         通知浏览器不要执行与事件关联的默认动作。

stopPropagation()      停止事件传播,不再派发事件。

阻止浏览器的默认事件行为

当触发某些事件时,浏览器会有自身的默认执行方式,例如最常见的a标签,如果我们只想点击链接拿到地址,但却不想跳转到该链接的目标URL,所以我们需要阻止事件的传播。这里就用到了事件流和事件对象知识。但这次除了提到事件对象的stopPropagation()方法,还介绍了额外两种方式:

event.preventDefault()
event.stopPropagation()
return false

preventDefault()         阻止特定事件的默认行为(只有 cancelable 设置为 true 的事件才可以使用)。
stopPropagation()      停止事件在DOM节点中传播。不会阻止默认行为。

return false                 之后所有相关的触发事件都不会被执行。阻止事件继续传播,事件冒泡和默认行为都被阻止。

点击超链接后阻止浏览器跳转

<a href="前端学习/img/girl.png">获取地址不跳转</a>
<p>显示目标地址</p>
<script>
var atest = document.querySelector("a");
var p = document.querySelector("p");
atest.onclick = function(event){
var attr = this.getAttribute("href");
p.innerHTML = attr;
return false;
}
</script>


禁用右键菜单

window.onload = function(){
document.oncontextmenu = function(){
return false;
}
}


关于DOM事件基本涉及到的知识到此介绍完成,其中大量的知识都来源网上,鄙人做的也只是以个人的理解将其整理一番。当然,这些都是些基础,不怎么深入。如果你能从中学习到知识,那将是极好的,如果其中有不足之处或出现错误,请给予指正,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息