事件
事件
JavaScript和HTML的交互是通过事件实现的。JavaScript采用异步事件驱动编程模型,当文档、浏览器、元素或与之相关对象发生特定事情时,浏览器会产生事件。如果JavaScript关注特定类型事件,那么它可以注册当这类事件发生时要调用的句柄
- 事件是某个行为或者触发,比如点击、鼠标移动
- 当用户点击鼠标时
- 当网页已加载时
- 当图像已加载时
- 当鼠标移动到元素上时
- 当用户触发按键时
事件流
事件流描述的是从页面中接收事件的顺序,比如有两个嵌套的div,点击了内层的div,这时候是内层的div先触发click事件还是外层先触发?目前主要有三种模型
-
事件冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的元素
-
事件捕获:不太具体的节点更早接收事件,而最具体的元素最后接收事件,和事件冒泡相反
-
DOM事件流:DOM2级事件规定事件流包括三个阶段,事件捕获阶段,处于目标阶段,事件冒泡阶段,首先发生的是事件捕获,为截取事件提供机会,然后是实际目标接收事件,最后是冒泡阶段
Opera、Firefox、Chrome、Safari都支持DOM事件流,IE不支持事件流,只支持事件冒泡
如下面代码结构
<!DOCTYPE html > <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <title>Test Page</title> </head> <body> <div>Click Here</div> </body> </html>
点击div区域,相关的三种事件模型过程如下
事件传播机制
事件传播机制:
- 事件捕获阶段:事件被从Document一直向下传播到目标元素。在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行。(IE低版本没有捕获)
- 事件处理阶段:事件到达目标元素,执行目标元素的事件处理函数。
- 冒泡传播:从捕获到事件源的元素开始往父级元素进行事件冒泡,DOM0级事件中,事件绑定函数的触发都发生在冒泡阶段。
事件处理程序
我们也称之为事件侦听器(listener),事件就是用户或浏览器自身执行的某种动作。比如click、load、mouseover等,都是事件类型(俗称事件名称),而响应某个事件的方法就叫做事件处理程序或者事件监听器
也就是我们需要提前定义好某些事件发生了该怎么处理,这个过程叫做绑定事件处理程序,了解了这些,我们看看如何给元素添加事件处理程序
HTML内联方式
元素支持的每个事件都可以使用一个相应事件处理程序同名的HTML属性指定。这个属性的值应该是可以执行的JavaScript代码,我们可以为一个button添加click
事件处理程序
<input type="button" value="Click Here" onclick="alert('Clicked!');" />
在HTML事件处理程序中可以包含要执行的具体动作,也可以调用在页面其它地方定义的脚本,刚才的例子可以写成这样
<input type="button" value="Click Here" onclick="showMessage();" />
在HTML中指定事件处理程序书写很方便,但是有两个缺点。所以我们并不推荐这样的方式进行书写。
-
存在加载顺序问题,如果事件处理程序在html代码之后加载,用户可能在事件处理程序还未加载完成时就点击按钮之类的触发事件,存在时间差问题
-
这样书写html代码和JavaScript代码紧密耦合,维护不方便
JavaScript指定事件处理程序(DOM0级事件处理程序)
通过JavaScript指定事件处理程序就是把一个方法赋值给一个元素的事件处理程序属性。
每个元素都有自己的事件处理程序属性,这些属性名称通常为小写,如onclick
等,将这些属性的值设置为一个函数,就可以指定事件处理程序,如下
<input id="btnClick" type="button" value="Click Here" /> <script type="text/javascript"> var btnClick = document.getElementById('btnClick'); btnClick.onclick = function showMessage() { alert(this.id); }; </script>
这样处理,事件处理程序被认为是元素的方法,事件处理程序在元素的作用域下运行,this就是当前元素,所以点击button结果是:btnClick
这样还有一个好处,我们可以删除事件处理程序,只需把元素的onclick属性赋为null即可
DOM2事件处理程序
DOM2级事件定义了两个方法用于处理指定和删除事件处理程序的操作:
- addEventListener
- removeEventListener
所有的DOM节点都包含这两个方法,并且它们都接受三个参数:
- 事件类型
- 事件处理方法
- 布尔参数,如果是true表示在捕获阶段调用事件处理程序,如果是false,则是在事件冒泡阶段处理
刚才的例子我们可以这样写
<input id="btnClick" type="button" value="Click Here" /> <script type="text/javascript"> var btnClick = document.getElementById('btnClick'); btnClick.addEventListener('click', function() { alert(this.id); }, false); </script>
上面代码为button添加了click事件的处理程序,在冒泡阶段触发,与上一种方法一样,这个程序也是在元素的作用域下运行,不过有一个好处,我们可以为click事件添加多个处理程序
<input id="btnClick" type="button" value="Click Here" /> <script type="text/javascript"> var btnClick = document.getElementById('btnClick'); btnClick.addEventListener('click', function() { alert(this.id); }, false); btnClick.addEventListener('click', function() { alert('Hello!'); }, false); </script>
这样两个事件处理程序会在用户点击button后按照添加顺序依次执行。
通过addEventListener添加的事件处理程序只能通过removeEventListener移除,移除时参数与添加的时候相同,这就意味着刚才我们添加的匿名函数无法移除,因为匿名函数虽然方法体一样,但是句柄却不相同,所以当我们有移除事件处理程序的时候可以这样写
<input id="btnClick" type="button" value="Click Here" /> <script type="text/javascript"> var btnClick = document.getElementById('btnClick'); var handler=function() { alert(this.id); } btnClick.addEventListener('click', handler, false); btnClick.removeEventListener('click', handler, false); </script>
事件对象
在触发DOM上的某个事件的时候会产生一个事件对象event,这个对象包含着所有与事件有关的信息,包括产生事件的元素、事件类型等相关信息。所有浏览器都支持event对象,但支持方式不同。
DOM中的事件对象
兼容DOM的浏览器会产生一个event对象传入事件处理程序中。应用一下刚才我们写的addEvent方法
var btnClick = document.getElementById('btnClick'); addEvent(btnClick, 'click', handler);
点击button的时候我们可以看到弹出内容是click的弹窗
event对象包含与创建它的特定事件有关的属性和方法,触发事件的类型不同,可用的属性和方法也不同,但是所有事件都会包含
在事件处理程序内部,this
始终等同于currentTarget
,而target是事件的实际目标。
要阻止事件的默认行为,可以使用preventDefault()
方法,前提是cancelable值为true,比如我们可以阻止链接导航这一默认行为
document.querySelector('#btn').onclick = function (e) { e.preventDefault(); }
stopPropagation()
方法可以停止事件在DOM层次的传播,即取消进一步的事件捕获或冒泡。我们可以在button的事件处理程序中调用stopPropagation()
从而避免注册在body上的事件发生
var handler = function (e) { alert(e.type); e.stopPropagation(); } addEvent(document.body, 'click', function () { alert('Clicked body')}); var btnClick = document.getElementById('btnClick'); addEvent(btnClick, 'click', handler);
若是注释掉e.stopPropagation();
在点击button的时候,由于事件冒泡,body的click事件也会触发,但是调用这句后,事件会停止传播
事件冒泡
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style media="screen"> .container, .box, .target { padding: 10px; border: 1px solid; } </style> </head> <body> <p style="font-size: 12px; color:grey;">查看此demo效果请打开console调试台 并配合js源码食用 </p> <div class="container">container <div class="box">box <div class="target">target </div> </div> </div> </body> </html> <script> var container = document.querySelector('.container') var box = document.querySelector('.box') var target = document.querySelector('.target') //加参数true,为捕获 container.addEventListener('click',function(){ console.log('container 捕获 click...') },true) box.addEventListener('click',function(){ console.log('box 捕获 click...') },true) target.addEventListener('click',function(){ console.log('target 捕获 click...') },true) //默认不带参数,为冒泡 container.addEventListener('click',function(){ console.log('container 冒泡 click...') }) box.addEventListener('click',function(){ console.log('box 冒泡 click...') }) target.addEventListener('click',function(){ console.log('target 冒泡 click...') }) </script>
取消事件进一步捕获或冒泡 / 取消事件默认行为
使用stopPropagation()
取消事件进一步捕获或冒泡,使用preventDefault()
取消事件默认行为
示例demo代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style media="screen"> .container, .box, .target { padding: 10px; border: 1px solid; } </style> </head> <body> <p style="font-size: 12px; color:grey;">查看此demo效果请打开console调试台 并配合js源码食用 </p> <div class="container">container <div class="box">box <div class="target">target </div> </div> </div> <a href="https://www.baidu.com">链接 点我</a> </body> </html> <script> var container = document.querySelector('.container') var box = document.querySelector('.box') var target = document.querySelector('.target') //加参数true,为捕获 container.addEventListener('click',function(e){ console.log('container 捕获 click...') },true) box.addEventListener('click',function(e){ e.stopPropagation() //使用stopPropagation在捕获阶段就取消事件进一步捕获 console.log('box 捕获 click...') },true) target.addEventListener('click',function(e){ console.log('target 捕获 click...') },true) //加参数为false,为冒泡 container.addEventListener('click',function(e){ console.log('container 冒泡 click...') },false) box.addEventListener('click',function(e){ console.log('box 冒泡 click...') },false) target.addEventListener('click',function(e){ console.log('target 冒泡 click...') },false) //取消事件默认行为 var newA = document.querySelector('a') newA.addEventListener('click',function(e){ e.preventDefault() //使用preventDefault取消事件默认行为,在这里即取消了a链接的默认的点击跳转行为 console.log(this.href) //在控制台console输出href地址 if(/https:\/\/www.baidu.com/.test(this.href)){ //用正则匹配,如果地址newA.href地址和https://www.baidu.com匹配为true,则输出yes! 并进行跳转 console.log('yes!') location.href = this.href //跳转链接为此正则匹配为true的链接 } }) </script>
表单示例:
<form action="/login"> <input type="text" name="username"> <input type="submit"> </form> <script> document.querySelector('form').addEventListener('submit', function(evt){ evt.preventDefault() if(document.querySelector('input[name=username]').value === 'evenyao'){ this.submit() } }) </script>
事件代理
<!-- html --> <ul class="ct"> <li>欢 迎</li> <li>Hello</li> <li>World</li> </ul> <input class="ipt-add-content" placeholder="添加内容"/> <button id="btn-add-start">开头添加</button> <button id="btn-add-end">结尾添加</button> <!-- js --> <script> var ctClass = document.querySelector('.ct') var iptContent = document.querySelector('.ipt-add-content') var addStart = document.querySelector('#btn-add-start') var addEnd = document.querySelector('#btn-add-end') //使用事件代理思想,在父级ul标签上进行事件绑定 //这样做的好处是,下面使用add按钮生成的新的li也可以进行分配到事件 ctClass.addEventListener('click',function(e){ console.log(e.target.innerText) }) addStart.addEventListener('click',function(){ if(iptContent.value!==''){ var liClass = document.createElement('li') liClass.innerHTML = iptContent.value ctClass.insertBefore(liClass,ctClass.children[0]) //在ctClass下的第一个子元素之前添加,使用insertBefore } }) addEnd.addEventListener('click',function(){ if(iptContent.value!==''){ var liClass = document.createElement('li') liClass.innerHTML = iptContent.value ctClass.appendChild(liClass) //在ctClass下紧接着末尾添加,使用appendChild } }) </script>
常用HTML事件
<button id="btn">点我</button> <button id="btn1">点我1</button> <div class="ct" style="font-size: 20px"> <div class="box">hello</div> </div> <div class="ct1"> <div class="box1"></div> </div> <input id="input-name" type="text"> <form id="form" action="/upload"> <input id="username" name="username" type="text"> <p class="msg"></p> <input id="btn-submit" type="submit" value="注册"> </form> <img src="http://images.cnblogs.com/cnblogs_com/evenyao/1237127/o_WX20180707-154309.png" alt=""> <script> function $(selector){ return document.querySelector(selector); } $('#btn').addEventListener('click', function(){ console.log('click') console.log(this) }) $('#btn1').addEventListener('dblclick', function(){ console.log('dblclick') console.log(this) }) $('.ct').addEventListener('mouseover', function(){ console.log('mouseover') console.log(this) // this.style.borderColor = 'blue' this.classList.add('hover') }) $('.ct').addEventListener('mouseout', function(){ console.log('mouseout...') // this.style.borderColor = 'red' this.classList.remove('hover') }) $('.ct1').addEventListener('mouseenter', function(){ console.log('mouseenter...') //this.style.borderColor = 'blue' this.classList.add('hover') }) $('.ct1').addEventListener('mouseleave', function(){ console.log('mouseleave...') //this.style.borderColor = 'blue' this.classList.remove('hover') }) $('#input-name').addEventListener('focus', function(){ console.log('focus...') console.log(this.value) }) $('#input-name').addEventListener('blur', function(){ console.log('blur...') console.log(this.value) }) $('#input-name').addEventListener('keyup', function(e){ console.log('keyup...') console.log(this.value) console.log(e) this.value = this.value.toUpperCase() }) $('#input-name').addEventListener('change', function(e){ console.log('change...') console.log(this.value) console.log(e) this.value = this.value.toUpperCase() }) $('#form').addEventListener('submit', function(e){ e.preventDefault(); if(/^\w{6,12}$/.test($('#username').value)){ $('#form').submit(); }else{ $('#form .msg').innerText = '出错了' $('#form .msg').style.display = 'block' console.log(' no submit...'); } }) window.addEventListener('scroll', function(e){ console.log('scroll..') }) window.addEventListener('resize', function(e){ console.log('resize..') }) //页面所有资源加载完成 window.onload = function(){ console.log('window loaded') } //DOM 结构解析完成 document.addEventListener('DOMContentLoaded', function(){ console.log('DOMContentLoaded ') }) console.log($('img').width) //0 $('img').onload = function(){ console.log(this.width) //此时才能得到图片的真实大小 } </script> <style> body{ color: blue; } .ct,.ct1{ width: 100px; height: 100px; border: 1px solid red; background-color: yellow; margin: 20px; } .box,.box1{ width: 50px; height: 50px; background-color: blue; } .ct.hover, .ct1.hover{ border-color: blue; background-color: pink; } .box3{ list-style: none; background: yellow; margin: 0; padding: 0; } .box3>li{ background: pink; margin: 5px; padding: 10px; } .box3>li.hover{ background-color: blue; } .msg{ display: none; } </style>
鼠标事件
onmousedown, onmouseup, onclick, ondbclick, onmousewheel, onmousemove, onmouseover, onmouseout
触摸事件
ontouchstart, ontouchend, ontouchmove
键盘事件:
onkeydown, onkeyup, onkeypress
页面相关事件:
onload, onmove(浏览器窗口被移动时触发), onresize(浏览器的窗口大小被改变时触发), onscroll(滚动条位置发生变化时触发)
表单相关事件
onblur(元素失去焦点时触发), onchange(元素失去焦点且元素内容发生改变时触发), onfocus(元素获得焦点时触发), onreset(表单中reset属性被激活时触发), onsubmit(表单被提交时触发);oninput(在input元素内容修改后立即被触发,兼容IE9+)
编辑事件
onbeforecopy:当页面当前的被选择内容将要复制到浏览者系统的剪贴板前触发此事件;
onbeforecut:当页面中的一部分或者全部的内容将被移离当前页面[剪贴]并移动到浏览者的系统剪贴板时触发此事件;
onbeforeeditfocus:当前元素将要进入编辑状态;
onbeforepaste:内容将要从浏览者的系统剪贴板传送[粘贴]到页面中时触发此事件;
onbeforeupdate:当浏览者粘贴系统剪贴板中的内容时通知目标对象;
oncontextmenu:当浏览者按下鼠标右键出现菜单时或者通过键盘的按键触发页面菜单时触发的事件;
oncopy:当页面当前的被选择内容被复制后触发此事件;
oncut:当页面当前的被选择内容被剪切时触发此事件;
onlosecapture:当元素失去鼠标移动所形成的选择焦点时触发此事件;
onpaste:当内容被粘贴时触发此事件;
onselect:当文本内容被选择时的事件;
onselectstart:当文本内容选择将开始发生时触发的事件;
拖动事件
ondrag:当某个对象被拖动时触发此事件 [活动事件];
ondragdrop:一个外部对象被鼠标拖进当前窗口时触发;
ondragend:当鼠标拖动结束时触发此事件;
ondragenter:当对象被鼠标拖动的对象进入其容器范围内时触发此事件;
ondragleave:当对象被鼠标拖动的对象离开其容器范围内时触发此事件;
ondragover:当某被拖动的对象在另一对象容器范围内拖动时触发此事件;
ondragstart:当某对象将被拖动时触发此事件;
ondrop:在一个拖动过程中,释放鼠标键时触发此事件;
自定义事件
var EventCenter = { on: function(type, handler){ document.addEventListener(type, handler) }, fire: function(type, data){ return document.dispatchEvent(new CustomEvent(type, { detail: data })) } } EventCenter.on('hello', function(e){ console.log(e.detail) }) EventCenter.fire('hello', '你好')