# JS事件

前言

  • javascript操作CSS称为脚本化CSS,而javascript与HTML的交互是通过事件实现的。
  • 事件就是文档或浏览器窗口中发生的一些特定的交互瞬间,而事件流(又叫事件传播)描述的是从页面中接收事件的顺序。

# 事件流

事件流又称为事件传播,DOM2级事件规定的事件流包括三个阶段:

  • 事件捕获阶段(capture phase)
  • 处于目标阶段(target phase)
  • 事件冒泡阶段(bubbling phase)

event

# 事件处理程序

事件处理程序又叫事件侦听器,实际上就是事件的绑定函数。事件发生时会执行函数中相应代码。

# Html事件处理程序

缺点

结构(html)与行为(js)未分离,不易维护、扩展。

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。

<!-- 在事件处理程序函数内部,this值等于事件的目标元素 -->
<div 
    id="box1" 
    style="height:30px;width:200px;background-color:pink;"
    onclick = "this.innerHTML+= '1';">
</div>

在HTML中定义的事件处理程序也可以调用在页面其他地方定义的脚本

<div 
    id="box1" 
    style="height:30px;width:200px;background-color:pink;"
    onclick = "testFun()">
</div>
<script>
    function testFun(){ 
        document.getElementById('box1').innerHTML+= '1'; 
    }    
</script>

HTML事件处理程序会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。通过event变量,可以直接访问事件对象,不用自己定义它,也不用从函数的参数列表中获取

<div 
    id="box2" 
    style="height:30px;width:200px;background-color:red"
    onclick = "this.innerHTML+= event.type;">
</div>

# DOM0级事件处理程序

  • 通过javascript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。
  • 具有简单、跨浏览器的优势。
  • 每个元素都有自己的事件处理程序属性,这些属性通常全部小写,将这种属性的值设置为一个函数,就可以指定事件处理程序

注意

  • 以DOM0级方式添加的事件处理程序会在事件流的冒泡阶段被处理
  • DOM0级事件处理程序的缺点是围绕着每个事件目标对于每种事件类型只能添加一个事件处理程序,添加相同事件会被后面的覆盖
<div id="DOM0" style="height:30px;width:200px;background-color:green;"></div>
<script>
    var Dom0 = document.getElementById('DOM0');
    Dom0.onclick = function(){
        this.innerHTML += 'DOM0事件处理 ';
    }    
    // 将事件处理程序属性设置为null来删除事件处理程序
    Dom0.onclick = null;
</script>  

# DOM2级事件处理程序

  • DOM2级事件处理程序定义了两个方法用于处理指定和删除事件处理程序的操作:addEventListener()removeEventListener()
  • 所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名作为事件处理程序的函数和一个布尔值
  • 最后的布尔值参数(可选|默认为false)如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
  • 使用DOM2级事件处理程序的好处是可以添加多个事件处理程序,并按照他们添加的顺序触发。弥补了DOM0级事件处理程序的缺点。

注意

IE8-浏览器不支持DOM2级事件处理程序
<!-- 输出12 -->
<div id="box" style="height:30px;width:200px;background-color:pink;"></div>
<script>
    var box = document.getElementById('box')
    box.addEventListener('click',function(){this.innerHTML += '1'},false);
    box.addEventListener('click',function(){this.innerHTML += '2'},false);    
</script>    

向监听函数传参

<!-- 向监听函数传递参数,可以用匿名函数包装一下监听函数 -->
<div 
    id="box" 
    style="height:30px;width:30px;background-color:red;">
</div>
<script>
box.addEventListener("click",function(){
    test('100');
},false);
function test(x){ box.innerHTML += x; }
</script>

移除事件

  • 通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除,
  • 移除时传入的参数与添加处理程序时使用的参数相同。这意味着,addEventListener()添加的匿名函数将无法移除
<div id="box" style="height:30px;width:200px;background-color:#eee;"></div>
<script>
/*  
    // 失效
    box.addEventListener("click",function(){
    this.innerHTML += '1'
    },false);
    box.removeEventListener('click',function(){
        this.innerHTML += '1'
    },false);    
*/
var handle = function(){this.innerHTML += '1'};
box.addEventListener("click",handle,false);
box.removeEventListener('click',handle,false);    
</script>

# IE事件处理程序

  • IE实现了与DOM2级处理程序中类似的两个方法:attachEvent()detachEvent()
  • 这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。
  • 由于IE8-浏览器只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到事件冒泡阶段
  • attachEvent()方法的第一个参数是onclick,而非DOM的addEventListener()方法中的"click"

[注意]🐻‍❄️

  • attachEvent()方法只冒泡到document,且IE10-浏览器支持
  • IE事件处理程序与其他三个事件处理程序不同,IE事件处理程序的this指向window,而非被绑定事件的元素
// div body html document
box.attachEvent('onclick',function(){box.innerHTML += 'div\n';});
document.body.attachEvent('onclick',function(){box.innerHTML += 'body\n';});
document.documentElement.attachEvent('onclick',function(){box.innerHTML += 'html\n';});
document.attachEvent('onclick',function(){box.innerHTML += 'document\n';});
window.attachEvent('onclick',function(){box.innerHTML += 'window\n';});

# 触发顺序

使用attachEvent()方法添加的事件处理程序的触发顺序是有区别的。IE9、10浏览器是按正序执行的,而IE8-浏览器则是按倒序执行

<!--  
    IE9-10 输出12
    IE8    输出21
-->
<div id="box" style="height:30px;width:100px;background-color:green;"></div>
<script>
box.attachEvent('onclick',function(){
    box.innerHTML += '1';
});
box.attachEvent('onclick',function(){
    box.innerHTML += '2';
});
</script>

# 移除

注意点与DOM2移除一致

<div id="box" style="height:30px;width:200px;background-color:pink;"></div>
<script>
    var handle = function(){box.innerHTML += '1'};
    box.attachEvent("onclick",handle,false);
    box.detachEvent('onclick',handle,false);    
</script> 

# 总结 🦅

  • 由于IE8-浏览器不支持addEventListener()方法,所以需要配合attachEvent()方法来实现全浏览器的事件绑定兼容写法。
  • 同时,由于attachEvent()方法中的this指向window,所以需要对this进行显式修改。


 


 




function addEvent(target,type,handler){
    if(target.addEventListener){
        target.addEventListener(type,handler,false);
    }else{
        target.attachEvent('on'+type,function(event){
            return handler.call(target,event);
        });
    }
}

# 事件调用顺序

<div id="box" style="height:100px;width:100px;background:pink;" onclick = "this.innerHTML +='html\n'"></div>
<script>
    if(box.addEventListener){
        box.addEventListener('click',function(){this.innerHTML += 'DOM2级\n'})
    }    
    if(box.attachEvent){
        box.attachEvent('onclick',function(){box.innerHTML +='IE\n'})
    }
    box.onclick = function(){
        this.innerHTML += 'DOM0级\n';
    }
</script>

相同点

  • 如果同时出现HTML事件处理程序和DOM0级事件处理程序,DOM0级会覆盖HTML事件处理程序

不同点

  • chrome/opera/safari等webkit内核的浏览器会按照事件处理程序出现的顺序来排列,所以结果为:DOM2级 DOM0级
  • firefox浏览器和IE浏览器会将DOM0级事件优先调用, 所以firefox和IE11浏览器结果为:DOM0级 DOM2级
  • IE9、10浏览器结果为:DOM0级 DOM2级 IE
  • IE8-浏览器结果为:DOM0级 IE

# 事件冒泡

IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档) 💛查看事件冒泡示例🖐️ (opens new window)

注意

所有现代浏览器都支持事件冒泡,但在具体实现在还是有一些差别。IE9、Firefox、Chrome、Safari将事件一直冒泡到window对象

// 嵌套关系 div=>body=>html=>document=>window
var box = document.getElementById('box');
box.onclick = function() {
    box.innerHTML = null;
    box.innerHTML += 'div\n';
}
document.body.onclick = function() {
    box.innerHTML += 'body\n';
}
document.documentElement.onclick = function() {
    box.innerHTML += 'html\n';
}
document.onclick = function() {
    box.innerHTML += 'document\n';
}
window.onclick = function() {
    box.innerHTML += 'window    \n';
}

# 事件捕获

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前就捕获它

<!--  示例DOM结构 -->
<!DOCTYPE HTML>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>事件捕获</title>
    </head>
    <body>
        <div></div>
    </body>    
</html>

在事件捕获过程中,document对象首先接收到click事件,然后事件沿DOM树依次向下,一直传播到事件的实际目标,即<div>元素

(1)    document
(2)    <html>
(3)    <body>
(4)    <div>

注意

IE9、Firefox、Chrome、Safari等现代浏览器都支持事件捕获,但是从window对象开始捕获

(1)    window
(2)    document
(3)    <html>
(4)    <body>
(5)    <div>

代码示例 🧠查看事件捕获示例🙌 (opens new window)

// addEventListener 第三个参数为Boolean 默认为false  true代表捕获阶段 false代表冒泡阶段
var box = document.getElementById('box');
box.addEventListener('click', function() {
    box.innerHTML += 'div\n';
}, true)
document.body.addEventListener('click', function() {
    box.innerHTML += 'body\n';
}, true)
document.documentElement.addEventListener('click', function() {
    box.innerHTML += 'html\n';
}, true)
document.addEventListener('click', function() {
    box.innerHTML += 'document\n';
}, true)
window.addEventListener('click', function() {
    box.innerHTML = null;
    box.innerHTML += 'window\n';
}, true)

# 事件对象

在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息(比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。)。

属性 说明
type 事件类型
target 事件目标对象,冒泡的父级通过该属性可以找到在哪个元素上执行了事件
currentTarget 当前执行事件的对象
timeStamp 事件发生时间
x 相对窗口的X坐标
y 相对窗口的Y坐标
clientX 相对窗口的X坐标
clientY 相对窗口的Y坐标
screenX 相对计算机屏幕的X坐标
screenY 相对计算机屏幕的Y坐标
pageX 相对于文档的X坐标
pageY 相对于文档的Y坐标
offsetX 相对于事件对象的X坐标
offsetY 相对于事件对象的Y坐标
layerX 相对于父级定位的X坐标
layerY 相对于父级定位的Y坐标
path 冒泡的路径
altKey 是否按了alt键
shiftKey 是否按了shift键
metaKey 是否按了媒体键
window.pageXOffset 文档参考窗口水平滚动的距离
window.pageYOffset 文档参考窗口垂直滚动的距离

# 获取事件对象

  • 一般地,event对象是事件程序的第一个参数

注意

IE8-浏览器输出undefined,其他浏览器则输出事件对象[object MouseEvent]

// 简要代码 xxx代表dom
xxx.onclick = function(a){
    xxx.innerHTML = a;
}
  • 直接使用event变量
xxx.onclick = function(){
    xxx.innerHTML = event;
}
// [object MouseEvent]

# 兼容写法🌿

xxx.onclick = function(e){
    e = e || window.event;
    xxx.innerHTML = e;
}

提示

事件对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都有些共有的属性和方法

# 事件类型

event.type

xxx.onclick = xxx.onmouseout = xxx.onmouseover =function(e){
    e = e || event;
    xxx.innerHTML = e.type;
}
// 鼠标移入dom显示mouseover
// 鼠标移出dom显示mouseout
// 点击显示click

# 事件目标

事件目标,共有currentTarget、target和srcElement这三个属性

# currentTarget

currentTarget属性返回事件当前所在的节点,即正在执行的监听函数所绑定的那个节点。

注意

  • IE8-浏览器不支持
  • 一般地,currentTarget与事件中的this指向相同。但在attachEvent()事件处理程序中,this指向window。

# target

  • target属性返回事件的实际目标节点
  • IE8-浏览器不支持
<!-- 点击该实际目标节点时,颜色变品红;移出时,颜色变浅蓝 -->
<style>
#box{background-color: lightblue;}
.in{height: 30px;}
</style>
<ul id="box">
    <li class="in">1</li>
    <li class="in">2</li>
</ul>
<script>
box.onclick = function(e){
    e = e || event;
    e.target.style.backgroundColor = 'pink';
}
box.onmouseout = function(e){
    e = e || event;
    e.target.style.backgroundColor = 'lightblue';
}
</script>

# srcElement

  • srcElement属性与target属性功能一致
  • 代码如上示例 target=>srcElement
  • 低版本火狐不支持

# 兼容性写法🏵️

var handler = function(e){
    e = e || event;
    var target = e.target || e.srcElement;
}

# 事件代理🤺事件委托

  • 由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。
  • 事件代理应用事件目标的targetsrcElement属性完成。利用事件代理,可以提高性能及降低代码复杂度

代码示例

<!-- 
需求:一个<ul>中有5个<li>,移入时变浅蓝,移出时变品红     
-->
<style>
#box{background-color: pink;}
.in{height: 30px;}
</style>
<ul id="box">
    <li class="in">1</li>
    <li class="in">2</li>
    <li class="in">3</li>
    <li class="in">4</li>
    <li class="in">5</li>
</ul>
<script>
//常规方法
var tags = box.getElementsByTagName('li');
for(var i = 0; i < tags.length; i++){
    tags[i].onmouseover = function(e){
        this.style.backgroundColor = 'lightblue';
    }
    tags[i].onmouseout = function(e){
        this.style.backgroundColor = 'pink';
    }
}
</script>

<script>
//事件代理方法
box.onmouseover = function(e){
    e = e || event;
    var target = e.target || e.srcElement;
    target.style.backgroundColor = 'lightblue';
}
box.onmouseout = function(e){
    e = e || event;
    var target = e.target || e.srcElement;
    target.style.backgroundColor = 'pink';
}
</script>

提示

如果可行的话,也可以考虑为document添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。这样做与采取传统的做法相比有以下优点:

  • 1、document对象很快就可以访问,而且可以在页面生命周期的任何时间点上为它添加事件处理程序,而无需等待DOMContentLoaded或load事件。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能
  • 2、在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少
  • 3、整个页面占用的内存空间更少,能够提升整体性能 最适合使用事件委托技术的事件包括clickmousedownmouseupkeydownkeyupkeypress

封装一个可以使用事件委托的事件绑定函数

function bindEvent(elem,type,selector,fn){
  if(fn == null){
    fn = selector;
    selector = null;
  }
  elem.addEventListener(type,function(e){
    var target;
    if(selector){
      target = e.target;
      if(target.matches(selector)){
        fn.call(target,e);
      }
    }else{
      fn(e);
    }
  })
}

# 事件冒泡

  • 事件冒泡是事件流的第三个阶段,通过事件冒泡可以在这个阶段对事件做出响应
  • 关于冒泡,事件对象中包含bubblescancelBubblestopPropagation()stopImmediatePropagation()这四个相关的属性和方法

# bubbles

TIP

  • bubbles属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性
  • 发生在文档元素上的大部分事件都会冒泡,但focus、blur和scroll事件不会冒泡。所以,除了这三个事件bubbles属性返回false外,其他事件该属性都为true
<button id="btn" style="height:30px;width:30px;"></button>
<script>
//点击按钮时,按钮内容为true,说明click事件默认可冒泡
btn.onclick = function(e){
    test.innerHTML =e.bubbles; // true
}
</script>
<div id="scrollBox" 
     style="height: 50px;
     width: 200px;
     overflow:scroll;
     background:pink;
     line-height:60px;">
    内容
</div>
<script>
//滚动时,div内容变成false,说明scroll事件默认不可冒泡
scrollBox.onscroll = function(e){
    scrollBox.innerHTML =e.bubbles; // false
}
</script>

# stopPropagation()

stopPropagation()方法表示取消事件的进一步捕获或冒泡,无返回值

[注意]IE8-浏览器不支持

<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
//点击按钮时,按钮内容为'button',因为阻止了<button>向<body>的冒泡
btn.onclick = function(e) {
    e = e || event;
    e.stopPropagation(); // 注释后显示button body
    btn.innerHTML = null
    btn.innerHTML += 'button\n';
}
document.body.onclick = function(e) {
    btn.innerHTML += 'body\n'
}
</script>

# stopImmediatePropagation()

stopImmediatePropagation()方法不仅可以取消事件的进一步捕获或冒泡,而且可以阻止同一个事件的其他监听函数被调用,无返回值

[注意]IE8-浏览器不支持

  • 使用stopIPropagation()方法,可以阻止冒泡,但无法阻止同一事件的其他监听函数被调用 如下示例:
<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
//使用stopIPropagation()方法,<button>内部变为'button',且背景颜色变成浅蓝
btn.addEventListener('click',function(e){
    e = e || event;
    e.stopPropagation();
    btn.innerHTML +='button\n';    
})
btn.addEventListener('click',function(e){
    e = e || event;
    btn.style.background = 'lightblue';    
})
document.body.onclick = function(e){
    btn.innerHTML += 'body\n'
}
</script>

使用stopImmediatePropagation()方法,即可以阻止冒泡,也可以阻止同一事件的其他监听函数被调用

<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
//使用stopImmediatePropagation()方法,<button>内部变为'button',且背景颜色不变
btn.addEventListener('click',function(e){
    e = e || event;
    e.stopImmediatePropagation();
    btn.innerHTML +='button\n';    
})
btn.addEventListener('click',function(e){
    e = e || event;
    btn.style.background = 'lightblue';    
})
document.body.onclick = function(e){
    btn.innerHTML += 'body\n'
}
</script>

# cancelBubble()

cancelBubble属性只能用于阻止冒泡,无法阻止捕获阶段。该值可读写,默认值是false。当设置为true时,cancelBubble可以取消事件冒泡

[注意]

🐈该属性全浏览器支持,但并不是标准写法

<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
btn.onclick = function(e){
    e = e || event;
    e.cancelBubble = true;
    btn.innerHTML = null
    btn.innerHTML +='button\n';
}
document.body.onclick = function(e){
    btn.innerHTML += 'body\n'
}
</script>

【注意🐽】

当使用stopIPropagation()方法或stopImmediatePropagation()方法时,关于cancelBubble值的变化,各浏览器表现不同

//chrome/safari/opera中,cancelBubble的值为false
//IE9+/firefox中,cancelBubble的值为true
<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
btn.onclick = function(e){
    e = e || event;
    e.stopPropagation();
    btn.innerHTML = e.cancelBubble;
}
</script> 

# 兼容写法

var handler = function(e){
    e = e || event;
    if(e.stopPropagation){
        e.stopPropagation();
    }else{
        e.cancelBubble = true;
    }
}

# 事件流

# eventPhase

eventPhase属性返回一个整数值,表示事件目前所处的事件流阶段

TIP

  • 0表示事件没有发生,
  • 1表示捕获阶段,
  • 2表示目标阶段,
  • 3表示冒泡阶段

示例代码:

目标阶段

<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
btn.onclick = function(e){
    e = e || event;
    btn.innerHTML = e.eventPhase;
}
</script>

捕获阶段

<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
document.addEventListener('click',function(e){
    e = e || event;
    btn.innerHTML = e.eventPhase;
},true);
</script>

冒泡阶段

<button id="btn" style="height: 30px;width: 200px;"></button>
<script>
document.addEventListener('click',function(e){
    e = e || event;
    btn.innerHTML = e.eventPhase;
},false);
</script>

# 取消默认行为

  • 常见的默认行为有点击链接后,浏览器跳转到指定页面;或者按一下空格键,页面向下滚动一段距离。

  • 关于取消默认行为的属性包括cancelabledefaultPreventedpreventDefault()returnValue

使用

TIP

1、在DOM0级事件处理程序中取消默认行为,使用returnValue、preventDefault()和return false都有效 2、在DOM2级事件处理程序中取消默认行为,使用return false无效 3、在IE事件处理程序中取消默认行为,使用preventDefault()无效

# cancelable

cancelable属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性。返回true时,表示可以取消。否则,表示不可取消

[注意]IE8-浏览器不支持

<a id="ahref" href="#">链接</a>
<script>
ahref.onclick= function(e){
    e = e || event;
    ahref.innerHTML = e.cancelable;  // true
}
</script>

# preventDefault()

preventDefault()方法取消浏览器对当前事件的默认行为,无返回值

[注意]IE8-浏览器不支持

<a id="ahref" href="http://www.biying.com">必应</a>
<script>
ahref.onclick= function(e){
    e = e || event;
    e.preventDefault();   // 阻止默认行为后不会跳转到必应
}
</script>

# returnValue

returnValue属性可读写,默认值是true,但将其设置为false就可以取消事件的默认行为,与preventDefault()方法的作用相同

[注意]firefox和IE9+浏览器不支持

<a id="ahref" href="http://www.biying.com">必应</a>
<script>
ahref.onclick= function(e){
    e = e || event;
    e.returnValue = false;
}
</script>

# 兼容写法

var handler = function(e){
    e = e || event;
    if(e.preventDefault){
        e.preventDefault();
    }else{
        e.returnValue = false;
    }
}

# return false

取消默认事件还可以使用return false

<a id="ahref" href="http://www.biying.com">必应</a>
<script>
ahref.onclick= function(e){
    return false;
}
</script>

# defaultPrevented

defaultPrevented属性表示默认行为是否被阻止,返回true时表示被阻止,返回false时,表示未被阻止

[注意]IE8-浏览器不支持

<a id="ahref" href="http://www.juncaihe.com">blog 地址</a>
<script>
ahref.onclick= function(e){
    e = e || event;
    if(e.preventDefault){
        e.preventDefault();
    }else{
        e.returnValue = false;
    }
    ahref.innerHTML = e.defaultPrevented;
}
</script>
Last Updated: 12/18/2022, 2:20:01 PM