前言
javascript是事件驱动型语言。当用户在网页中进行某种操作时,就产生了一个“事件”(Event)。
事件几乎可以是任何事情:单击一个网页元素、拖动鼠标等均可视为事件。JavaScript是事件驱动的,当事件发生时,它可以对之做出响应。具体如何响应某个事件由编写的事件处理函数完成。
事件基础
事件分为系统派发事件和自定义派发事件。
- addEventListener();//事件侦听方法,仅用于EventTarget对象
- dispatchEvent();//抛发事件方法,派发事件,仅用于EventTarget对象
注意:
- 必须要先侦听再派发
- 侦听和派发的对象是同一个,可以是DOM元素,也可以是EventTarget,或者继承EventTarget的类
- 侦听和派发的事件类型完全相同,其实事件类型就是一个任意字符串
- 系统派发的事件字符串是固定的,自定义派发的事件,字符串可以任意
- 事件侦听回调函数,不能传参,因此事件回调函数中有且仅有一个参数,这个参数即为事件对象
- 事件都可以手动派发,手动派发的话,页面加载完后就会执行
//document就是侦听派发的对象
//click就是这里的事件类型
document.addEventListener("click",clickHandler);
//div元素的继承为下:
//Object-->EventTarget-->Node-->Element-->HTMLElement-->HTMLDivElement
//EventTarget叫事件目标对象
var target=new EventTarget();
target.addEventListener("chilema",clickHandler);
//Event实例的对象叫事件对象
var evt=new Event("chilema");
evt.num=10;
target.dispatchEvent(evt);//派发事件
//
function clickHandler(e){
//e.type为派发的事件类型
console.log(e.type);
}
事件的高内聚低耦合
目的是使程序模块的可重用性、移植性大大增强。一个好的内聚模块应当恰好做一件事。
obj 和 obj1 互相调用,但是不相互影响,删除任意一个函数后,也不会报错。
// 高内聚低耦合
var obj={
a:function(){
document.addEventListener("b",this.b);
},
b:function(e){
var num=e.num+10;
var evt=new Event("a");
evt.num=num;
document.dispatchEvent(evt);
}
}
var obj1={
a:function(){
document.addEventListener("a",this.b);
var evt=new Event("b");
evt.num=2;
document.dispatchEvent(evt);
},
b:function(e){
console.log(e.num);//12
}
}
obj.a();
obj1.a();
事件原理(三个阶段)
- 事件捕获(由外到内)
- 目标阶段
- 事件冒泡(由内到外)
事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。即子标签发生事件后,向父级发送该事件,一直追溯到document。如:点击一个嵌套在 body中的button,则该button的onclick事件也会传递给body、document中,触发他们的onclick里触发的函数。
理解事件原理:
var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");
var div3 = document.getElementById("div3");
div1.addEventListener("click", clickHandler1);
div2.addEventListener("click", clickHandler2);
div3.addEventListener("click", clickHandler3);
function clickHandler1(e) {
console.log("div1")
}
function clickHandler2(e) {
console.log("div2")
}
function clickHandler3(e) {
console.log("div3")
}
阻止事件冒泡
当对子元素添加了事件侦听后,执行的时候会触发父元素相同类型的事件,此时需要阻止事件冒泡。
早期IE是没有捕获阶段的,只有冒泡,cancelBubble为阻止冒泡。后来的stopPropagaiton,既有阻止冒泡的功能,也有阻止捕获的功能,但如果译为阻止传播,那么跟cancel就是两个东西了,所以还是叫做阻止冒泡。阻止事件冒泡(传播)的方法是:
- e.stopPropagation();
- e.cancelBubble=true; IE8及以下浏览器
var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");
var div3 = document.getElementById("div3");
div1.addEventListener("click", clickHandler1);
div2.addEventListener("click", clickHandler2);
div3.addEventListener("click", clickHandler3);
function clickHandler1(e) {
e.stopPropagation();//阻止事件冒泡
// e.cancelBubble=true;//阻止事件冒泡 IE8一下浏览器
console.log("div1")
}
function clickHandler2(e) {
console.log("div2")
}
function clickHandler3(e) {
console.log("div3")
}
事件委托
事件侦听添加(注册事件)占有内存的,尽量减少事件侦听的数量,将子元素的事件委托给父元素来执行,叫做事件委托。
当删除对象时,一定要将对象上的侦听事件移除,否则会造成内存泄露。
- e.currentTarget 是事件侦听事件对象(什么对象执行addEventListener函数就是谁)
- e.target 事件的目标对象 事件实际触发的目标阶段最后对象,
- e.srcElement 事件的目标对象,兼容IE
- 事件函数中this默认等同于e.currentTarget
案例:点击 li ,让其子元素 ul 切换显示。
布局代码:
<ul id="menu">
<li>北京
<ul>
<li>海淀</li>
<li>昌平
<ul>
<li>沙河</li>
<li>回龙观</li>
<li>天通苑</li>
</ul>
</li>
<li>朝阳</li>
<li>东城</li>
</ul>
</li>
<li>河北</li>
<li>天津</li>
<li>河南</li>
<li>山西</li>
</ul>
js代码:
var menu=document.querySelector("#menu");
//给最外层的ul侦听点击事件
menu.addEventListener("click",clickHandler);
function clickHandler(e){
//如果点击的不是li标签,直接退出
if(e.target.constructor!==HTMLLIElement)return;
//如果点击的li没有子元素,直接退出
if(!e.target.firstElementChild) return;
//阻止事件冒泡
e.stopPropagation();
//控制li下的ul显示隐藏
if(!e.target.bool) e.target.firstElementChild.style.display="none";
else e.target.firstElementChild.style.display="block";
e.target.bool=!e.target.bool;
}
扩展-addEventListener()
- 参数:addEventListener(事件类型,事件回调函数,是否捕获阶段执行(默认false)||对象{once:true})//是否只执行一次
//给body侦听点击事件,冒泡阶段执行
document.body.addEventListener("click", clickHandler);
//给body侦听点击事件,捕获阶段执行
document.body.addEventListener("click", clickHandler,true);
//给body侦听点击事件,只执行一次
document.body.addEventListener("click", clickHandler,{once:true});
- 事件侦听添加(注册事件)占有内存的,所以当删除对象时,一定要将对象上的侦听事件移除
document.body.removeEventListener("click", clickHandler);
addEventListener() 和 onclick的区别:
- onclick 不能同时执行两个函数,addEventListener()可以执行两个不同的函数
- 移除事件侦听的方式不同
document.onclick=function(){
document.onclick=null;//移除事件侦听
console.log("a")
}
//事件会覆盖上面的事件
document.onclick=function(){
console.log("b");
}
document.addEventListener("click",clickHandler1);
document.addEventListener("click",clickHandler2);
function clickHandler1(e){
//移除事件侦听
document.removeEventListener("click",clickHandler1);
console.log("a");
}
function clickHandler2(e){
console.log("b")
}
- 事件匿名函数的不断迭代就会造成回调地狱,而使用事件注册时用的是命名函数就会减少造成回调地狱
// 事件匿名函数的不断迭代就会造成回调地狱
document.onclick=function(){
var bn=document.querySelector("button");
bn.onclick=function(){
console.log("aaa");
}
}
- addEventListener可以在捕获阶段和冒泡阶段触发,而onclick只能冒泡阶段触发
document.body.addEventListener("click", clickHandler);//冒泡阶段执行
document.body.addEventListener("click", clickHandler,true);//捕获阶段执行
- onclick支持IE低版本,addEventListener不支持IE8一下,低版本的IE使用 attachEvent 进行事件侦听;使用 detachEvent 移除事件侦听。
document.attachEvent("onclick",clickHandler);//IE8及以下使用,其他版本和其他浏览器不支持
function clickHandler(e){
console.log("aaa");
document.detachEvent("onclick",clickHandler);//移除事件侦听
}
事件侦听和移除的兼容写法:
function addEvent(elem,type,eventHandler){
try{
elem.addEventListener(type,eventHandler);
}catch(e){
elem.attachEvent("on"+type,eventHandler);
}
}
function removeEvent(elem,type,eventHandler){
try{
elem.removeEventListener(type,eventHandler);
}catch(e){
elem.detachEvent("on"+type,eventHandler);
}
}
addEvent(document,"click",clickHandler);
function clickHandler(){
}