事件流
JavaScript操作css称为脚本化CSS,而JavaScript与HTML的交互是通过事件实现的。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间,而事件流(又叫事件传播)描述的是从页面中接收事件的顺序。
事件流的三个阶段
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
事件捕获
由不太具体的节点(window/document)更早的接收事件,往具体的节点进行传播,最具体的节点应该在最后接收到事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum=1,user-scalable=no">
<title>Document</title>
<style type="text/css">
#box{
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<div id="box"></div>
<script type="text/javascript">
var box = document.getElementById("box");
box.addEventListener('click',function(){
box.innerHTML += "div\n"
},true);//false 表示冒泡阶段
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 += "window\n"
},true);
</script>
</body>
</html>
事件冒泡
事件开始时由最具体的节点接收,然后逐级向上传播到较为不具体的节点(文档)
注意:ie9以下仅冒泡到document
测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum=1,user-scalable=no">
<title>Document</title>
<style type="text/css">
#box{
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<div id="box"></div>
<script type="text/javascript">
var box = document.getElementById("box");
box.onclick = function(){
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"
}
</script>
</body>
</html>
事件处理程序
事件处理程序又叫事件侦听器,实际上就是事件的绑定函数。事件发生时会执行函数中相应代码。
事件处理程序分为四种:
- HTML事件处理程序
- DOM 0级事件处理程序
- DOM 2 级事件处理程序
- IE 事件处理程序
HTML事件处理程序
直接在元素上绑定事件,不常用
在事件处理程序函数内部,this指向事件的目标元素。
<div id="box" onclick="this.innerHTML+='1'"></div>
这里的this就是当前对象(<div id="box" onclick="this.innerHTML+='1'"></div>)
等价于
<div id="box" onclick="test()"></div>
<script type="text/javascript">
var box = document.querySelector("#box");
function test(){
console.log(this);//此处的this指向window,函数独立调用,内部this指向window
box.innerHTML +='1';
}
</script>
缺点
html+js混合在一起,不易维护。
DOM 0级事件处理程序
将一个函数赋值给一个事件的处理程序的属性, 应用非常多(简单,跨浏览器)
注意:
以DOM 0级方式添加的事件处理程序会在事件流的冒泡阶段被处理,不存在捕获阶段
<div id="box"></div>
<script type="text/javascript">
var box = document.querySelector("#box");
box.onclick = function(){
this.innerHTML +='1';
}
//置空,删除事件的处理程序
box.onclick = null;
</script>
缺点
不能给同一个元素绑定相同的事件处理程序,如果绑定了,前者会被覆盖
DOM 2级事件处理程序
处理程序存在两种方法
- addEventListener()
- removeEventListener()
addEventListener()
事件监听,addEventListener("事件名字符串",function(){},布尔值),其中布尔值默认为false,可以不写,false表示处于冒泡阶段,为true表示处于捕获阶段。
<div id="box"></div>
<script type="text/javascript">
var box = document.querySelector("#box");
box.addEventListener('click', function(){
this.innerHTML +='1';
},false);
</script>
DOM 2级事件处理程序,能给同一个元素绑定相同的事件处理程序,同时调用。
注意:
ie8浏览器不支持DOM 2级事件处理程序
监听函数传参,可以用匿名函数包装一个监听函数。
<div id="box"></div>
<script type="text/javascript">
var box = document.querySelector("#box");
box.addEventListener('click', function(){
test(111);
},false);
function test(x){
alert(x);
}
</script>
removeEventListener()
移除事件
<div id="box"></div>
<script type="text/javascript">
var box = document.querySelector("#box");
box.addEventListener('click',handler,false);
function handler(){
this.innerHTML += 1;
}
box.removeEventListener('click',handler,false);
</script>
IE的事件处理程序
只能在IE浏览器中使用,处理程序存在两种方法。
在ie中此this指向window
- attachEvent()
- detachEvent()
attachEvent()
添加事件
<div id="box"></div>
<script type="text/javascript">
var box = document.querySelector("#box");
box.attachEvent('onclick',function(){
//this.innerHTML += '1';
//在ie中此this指向window
box.innerHTML += '1';
});
</script>
detachEvent()
移除事件
<div id="box"></div>
<script type="text/javascript">
var box = document.querySelector("#box");
box.attachEvent('onclick',handler);
function handler(){
box.innerHTML += '1';
}
box.detachEvent('onclick',handler);
</script>
事件绑定兼容写法
代码如下
<body>
<button type="button">haha</button>
<script type="text/javascript">
var btn = document.querySelector("[type=button]");
// btn.addEventListener('click',fn,false);
// btn.attachEvent('onclick',fn);
addEvent(btn,'click',function(){
console.log(this.innerHTML);
});
// 全浏览器事件处理程序的兼容性代码
function addEvent(target,eventType,handler){
if(target.addEventListener){
target.addEventListener(eventType,handler,false);
}
else{
target.attachEvent('on'+eventType,function(){
handler.call(target);
});
}
}
</script>
</body>
事件调用顺序总结
相同点
如果同时出现html事件处理程序和dom0级事件处理程序,后者会覆盖前者
不同点
1.chrome,safari,火狐以及IE11浏览器结果:dom0级 dom2级
2.IE9、10结果为:dom0级 dom2级 IE
3.IE8结果为:dom0级 IE
事件对象
在触发dom上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息
如何获取事件对象
1.event对象是事件程序的第一个参数,ie8不支持
<body>
<div id="box"></div>
<script type="text/javascript">
window.onload = function(){
var box = document.getElementById("box");
box.onclick = function(e){
box.innerHTML = e;
}
}
</script>
</body>
2.直接使用event变量,火狐浏览器不支持
<body>
<div id="box"></div>
<script type="text/javascript">
window.onload = function(){
var box = document.getElementById("box");
box.onclick = function(){
box.innerHTML = event;
}
}
</script>
</body>
3.兼容写法
<body>
<div id="box"></div>
<script type="text/javascript">
window.onload = function(){
var box = document.getElementById("box");
box.onclick = function(e){
e = e ||window.event;
box.innerHTML = e;
}
}
</script>
</body>
事件目标
有三个属性
- currentTarget
- target
- srcElement
currentTarget
返回事件当前所在的节点,正在执行的监听函数所绑定的节点
<body>
<ul id="box">
<li class="item">1</li>
<li class="item">2</li>
</ul>
<script type="text/javascript">
var box = document.getElementById("box");
box.onclick = function(e){
e = e || event;
console.log(e.currentTarget);
var items = document.querySelectorAll("[class]");
items[0].innerHTML = e.currentTarget;//[object HTMLUListElement]
}
</script>
</body>
target
返回的是事件的实际目标对象
this对象跟e.currentTarget属性是一样的,但 不支持ie8
<body>
<ul id="box">
<li class="item">1</li>
<li class="item">2</li>
</ul>
<script type="text/javascript">
var box = document.getElementById("box");
box.onclick = function(e){
e = e || event;
console.log(e.target);
console.log(e.target===this);
// this对象跟e.currentTarget属性是一样的
console.log(e.currentTarget===this);
var items = document.querySelectorAll("[class]");
items[0].innerHTML = e.target;//[object HTMLUListElement]
}
</script>
</body>
srcElement
与target属性一样,但target不支持ie8,srcElement属性在低版本的火狐上不支持
兼容
var box = document.getElementById("box");
box.onclick = function(e){
e = e || event;
var target = e.target || e.srcElement;
}
事件代理
由于事件会在冒泡阶段向上传递到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫做事件的代理,又叫事件委托。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum=1,user-scalable=no">
<title>Document</title>
<style type="text/css">
*{
padding: 0;
margin: 0;
}
ul{
list-style: none;
overflow: hidden;
margin-top: 80px;
}
li{
width: 100px;
float: left;
height: 30px;
text-align: center;
line-height: 30px;
background-color: red;
margin: 0px 10px;
color: white;
}
</style>
</head>
<body>
<ul id="box">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script type="text/javascript">
window.onload = function(){
// 常规方法
var lis = document.getElementsByTagName("li");
for (var i = 0;i<lis.length;i++){
lis[i].onmouseover = function(){
this.style.backgroundColor = 'blue';
}
lis[i].onmouseout = function(){
this.style.backgroundColor = 'red'
}
}
//事件代理方式的实现,要结合事件目标对象来实现
var box = document.getElementById('box');
box.onmouseover = function(e){
e = e ||event;
var target = e.target || e.srcElement;
target.style.backgroundColor = 'blue'
}
box.onmouseout = function(e){
e = e ||event;
var target = e.target || e.srcElement;
target.style.backgroundColor = 'red'
}
}
</script>
</body>
</html>
优点
提高性能、减少代码
事件冒泡
事件冒泡是事件流的第三个阶段,通过事件冒泡可以在这个阶段对事件做出事件响应。
关于冒泡,事件对象中包含四个相关的属性和方法
- bubbles
- cancelBubble
- stopPropagation()
- stopImmediatePropagation()
bubbles
返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性。
注意:
发生在文档上的大部分事件都会冒泡,但focus、blur、scroll事件不会冒泡
<body>
<button>按钮</button>
<input type="text" name="" id="" value="" />
<script type="text/javascript">
var btn = document.getElementsByTagName('button')[0];
var inP = document.querySelector('[type=text]');
btn.onclick = function(e){
e = e || window.event;
console.log(e.bubbles);//true
}
inP.onfocus = function(e){
e = e || window.event;
console.log(e.bubbles);//false
}
</script>
</body>
stopPropagation()
表示取消事件的进一步捕获或冒泡,无返回,ie8不支持
<body>
<button>按钮</button>
<script type="text/javascript">
var btn = document.getElementsByTagName('button')[0];
btn.onclick = function(e){
e = e || window.event;
// 阻止冒泡
e.stopPropagation();
this.innerHTML = '阻止冒泡';
}
document.body.onclick = function(e){
e = e || window.event;
console.log('body');
}
</script>
</body>
缺点
无法阻止同一事件的其他监听函数被调用
<body>
<button>按钮</button>
<script type="text/javascript">
var btn = document.getElementsByTagName('button')[0];
btn.addEventListener('click',function(e){
e = e || window.event;
e.stopPropagation();
this.style.backgroundColor = 'blue';
},false);
btn.addEventListener('click',function(e){
e = e || window.event;
// e.stopPropagation();
this.innerHTML = '阻止了';
},false);
document.body.onclick = function(e){
e = e || window.event;
console.log('body');
}
</script>
</body>
stopImmediatePropagation()
既可以阻止冒泡,又可以阻止同一事件的其他监听函数被调用
<body>
<button>按钮</button>
<script type="text/javascript">
var btn = document.getElementsByTagName('button')[0];
btn.addEventListener('click',function(e){
e = e || window.event;
e.stopImmediatePropagation();
this.style.backgroundColor = 'blue';
},false);
btn.addEventListener('click',function(e){
e = e || window.event;
// e.stopPropagation();
this.innerHTML = '阻止了';
},false);
document.body.onclick = function(e){
e = e || window.event;
console.log('body');
}
</script>
</body>
cancelBubble
只能用于阻止冒泡,无法阻止捕获阶段,该值可读写,默认为false,设置为true,即取消冒泡。
<body>
<button>按钮</button>
<script type="text/javascript">
var btn = document.getElementsByTagName('button')[0];
btn.onclick = function(e){
e = e || window.event;
e.cancelBubble = true;
console.log(e.bubbles);
}
document.body.onclick = function(e){
e = e || window.event;
console.log('body');
}
</script>
</body>
兼容
stopPropagation()和stopImmediatePropagation()ie8不支持
e.cancelBubble = true;全浏览器支持,不是标准写法
<body>
<button>按钮</button>
<script type="text/javascript">
var btn = document.getElementsByTagName('button')[0];
btn.onclick = function(e){
e = e || window.event;
if(e.stopPropagation){
e.stopPropagation();
}
else{
e.cancelBubble = true;
}
this.innerHTML = '修改了'
}
document.body.onclick = function(e){
e = e || window.event;
console.log('body');
}
</script>
</body>
事件流阶段(了解)
eventPhase
返回一个整数值,表示事件目前所处的事件流阶段
0表示事件没有发生,1表示捕获阶段,2表示目标阶段,3表示冒泡阶段,ie8浏览器不支持。
<body>
<button type="button">事件流</button>
<script type="text/javascript">
var btn = document.getElementsByTagName('button')[0];
//2 目标阶段
btn.onclick = function(e){
e = e || event;
console.log(e.eventPhase);
}
//1 捕获阶段
document.body.addEventListener('click',function(e){
e = e || event;
console.log(e.eventPhase);
},true)
//3冒泡阶段
document.body.addEventListener('click',function(e){
e = e || event;
console.log(e.eventPhase);
},false)
</script>
</body>
取消默认事件
平时的方法
<a href="javascript:void(0);">百度</a>
<a href="javascript:;">百度</a>
事件对象中的两个方法,阻止默认事件:
- preventDefault()
- returnValue
- return false
preventDefault()
ie8以下不支持
returnValue
火狐、ie8以上不支持
return false
小技巧,兼容所有浏览器
<body>
<a href="#">百度</a>
<a href="javascript:;">百度</a>
<script type="text/javascript">
var item = document.getElementsByTagName('a')[0];
item.onclick = function(e){
e = e||event;
/* // preventDefault()
e.preventDefault();
this.innerHTML = 'john'; */
/* //returnValue
e.returnValue = false; */
//兼容ie8以上
if(e.preventDefault){
e.preventDefault();
}else{
//兼容ie8以下
e.returnValue = false;
}
/* //小技巧
return false; */
}
</script>
</body>
事件对象属性
鼠标坐标位置
关于坐标位置,事件对象提供了clientX/Y、pageX/Y、screenX/Y、x/y、offsetX/Y、layerX/Y。
clientX/Y & x/y
相对于浏览器(浏览器的有效区域)的x轴和y轴的距离。
<body>
<div id="box"></div>
<script type="text/javascript">
var box = document.getElementsByTagName('div')[0];
box.onmousemove = function(e){
e = e || window.event;
console.log(e);
this.innerHTML = `clientX:${e.clientX};clientY:${e.clientY};x:${e.x};y:${e.y}`
}
</script>
</body>
screenX/Y
相对于显示器屏幕的x轴和y轴的距离。
pageX/Y
相对于页面x轴和y轴的距离,会随滚动条的变化而变化
offsetX/Y
相对于事件源的x轴和y轴的距离。