事件流
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轴的距离。