HTML依托于JavaScript来实现用户与WEB网页之间的动态交互,接收用户操做并作出相应的反馈,而事件在此间则充当桥梁的重要角色。java
平常开发中,常常会为某个元素绑定一个事件,编写相应的业务逻辑,在元素被点击时执行,并反馈到用户操做界面。浏览器
这个过程当中,事件就像一个侦听器,当点击动做发生时,才会执行对应的程序。这种模式可称之为观察员模式。函数
接下来就讲讲DOM事件相关知识。性能
事件就是用户或浏览器自身执行的某种动做
经常使用的DOM事件有click/mouseover/mouseout/keyup/keydown
等。优化
事件流描述的是从页面中接收事件的顺序
HTML描述的是一个DOM文档结构,而事件流所描述的是DOM文档节点接收事件顺序。this
而事件流有两种事件模式,捕获/冒泡,二者所描述的事件传递顺序对立相反。spa
事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,而后逐级向上传播到较为不具体的节点(文档)
规范要求事件冒泡到document
对象,而浏览器则会将事件一直冒泡到window
对象。设计
全部浏览器都支持事件冒泡(包括IE9如下)。代理
事件捕获:(与事件冒泡相反)事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件
与冒泡同样,虽然规定事件应该从document
对象开始传播,但浏览器广泛都是从window
对象开始捕获。code
IE9如下不支持事件捕获
"DOM2级事件"规定事件流包括三个阶段,顺序进行
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
TIPS: 实际的目标元素在捕获阶段不会接收到事件,在处于目标阶段时接收事件发生处理,并被当作是冒泡阶段的一部分。
尽管"DOM2级事件"规范明确要求捕获阶段不会涉及事件目标,但浏览器会在捕获阶段触发事件对象上的事件。
响应某个事件的函数方法,咱们称之为事件处理程序(或事件侦听器)
window.onclick = function() { //... } // 这里的function(){}就是事件处理程序
HTML中元素支持的事件,可使用一个同名的HTML特性来指定,而这个特性的值就是js能执行的代码或表达式。写法上能够看出相似HTML中id/type/class
等属性的写法,都是on+'...'
缺点:HTML是结构层(显示层),而JavaScript是行为层(业务层)。在显示层上去编写业务逻辑代码处理,会使得HTML与JavaScript代码耦合过于紧密,很差维护。
DOM级别一共能够分为四个级别:DOM0级、DOM1级、DOM2级和DOM3级。
而DOM事件分为3个级别:DOM 0级事件处理程序,DOM 2级事件处理程序和DOM 3级事件处理程序。DOM 1级中没有规范事件的相关内容,因此没有DOM 1级事件处理。
每一个元素(HTML元素)都有本身的事件处理程序属性,属性名一般以on开头,例如onclick/onmouseover
。为这个属性的值设置一个函数,就能够指定事件处理程序。而将其属性值赋值为null,则完成解绑。(同个元素没法绑定多个同名事件)
var myBtn = document.getElementById('myBtn'); // 为myBtn绑定事件处理程序, 只能绑定一个 myBtn.onclick = function() { alert('Hello world!'); } // 解绑 myBtn.onclick = null;
"DOM2级事件"定义了两个方法,addEventListener()/removeEventListener()
,用于为元素绑定和解绑事件。
(可绑定多个事件,区别于DOM0级/HTML仅能绑定一个)。
el.addEventListener(eventName, callBack, useCapture)
var myBtn = document.getElementById('myBtn'); var handleClick = function() { alert('Hello world!'); } // 绑定事件处理程序 myBtn.addEventListener('click', handleClick, false); // 解绑 myBtn.removeEventListener('click', handleClick);
TIPS:DOM2级事件处理程序,解绑时function
必须与传入addEventListener
相同
// 绑定 myBtn.addEventListener('click', function() { // 匿名函数 }); // 解绑 myBtn.removeEventListener('click',function() { // 匿名函数 }); // add/remove 分别绑定了两个匿名函数(函数为引用类型),因此两个函数并不相同,因此没法成功解绑
TIPS:绑定多个事件处理程序时,执行顺序按绑定顺序执行
myBtn.addEventListener('click', function() { // step1... }) myBtn.addEventListener('click', function() { // step2... }) // 执行顺序:step1 -> step2
浏览器支持状况:IE9如下不支持DOM2级事件处理程序
IE9如下不支持DOM2级事件,但IE提供了与DOM2级事件相似的两个方法,attachEvent()/detachEvent
,IE9如下不支持事件捕获,因此attachEvent
仅支持冒泡阶段触发,只接收两个参数(eventName, function)。
// 绑定 myBtn.attachEvent('onclick', handleClick); // 解绑 myBtn.detachEvent('onclick', handleClick);
TIPS:
function
必须与传入attachEvent
相同,这点与DOM2级事件相同attachEvent
事件处理在全局,this
指向window
myBtn.attachEvent('click', function() { // step1... }) myBtn.attachEvent('click', function() { // step2... }) // 执行顺序:step2 -> step1
event.preventDefault()
阻止默认事件
event.stopPropagation()
阻止事件流发生传递(冒泡/捕获)
event.stopImmediatePropagation()
阻止剩余事件处理函数的执行,并阻止当前事件在事件流上传递
event.currentTarget
当前绑定事件的元素
event.target
当前触发事件的元素
同个元素绑定多个同名事件时,stopImmediatePropagation
不只阻止了冒泡,并且会阻止后续事件的执行,能够理解为增强版的stopPropagation
myBtn.addEventListener('click', function(event) { // step1; event.stopImmediatePropagation(); }) myBtn.addEventListener('click', function(event) { // step2; // 我被stopImmediatePropagation阻止掉了!!! })
事件处理程序内部,this等于currentTarget(当前绑定事件的元素),而target(当前触发事件的元素)
// currentTarget == target myBtn.addEventListener('click', function(event) { event.target == event.currentTarget; // true -> myBtn }) // currentTarget != target 捕获/冒泡 document.body.addEventListener('click', function(event){ event.target == event.currentTarget; // false // event.target -> myBtn // event.currentTarget -> body })
WEB网页是运行在浏览器客户端的,而计算机分配给浏览器的内存及CPU占用是有限制的。虽然说浏览器引擎不断地发展优化,可是内存占用多了, 性能难免会损耗。
为元素指定事件绑定程序,事实上是赋值了一个函数方法,而函数在javaScript中是一种引用类型的数据格式,既然是数据那就须要用到内存储存。函数建立多了,消耗掉内存。
为元素指定事件绑定程序,首先须要对DOM进行查询,找出要绑定事件的元素。而这也会形成DOM元素的访问次数增长。DOM的操做一直是网页性能的一个优化点。
了解完事件绑定带来内存跟性能的原理,咱们来看一个例子,例如咱们有一个ul>li
的列表,要监听每个li
的点击事件,并触发事件处理程序。
单独绑定的话,10个li
就要对DOM元素查询10次,建立的匿名函数就有10个(固然能够共同建立同个函数引用),若是还有20个,30个,100个,那么这种为每一个li
元素单独绑定事件的方法,绝对不是最优解。
这就引出下面的优化方案:"事件委托"。
对"事件处理程序绑定过多"的问题,最好的解决方案就是"事件委托"。它的原理是利用了事件流的"冒泡"机制,事件目标元素会把事件向上层传递,直到document
(浏览器会传到window
),因此父级节点是能够接收子节点的事件传递。
以刚刚ul>li
的例子,li
有不少个, 但它们有一个共同的父节点ul
。li
的点击事件会冒泡到ul
,所以咱们能够在ul
上绑定一个事件处理程序,处理全部li
的点击事件,而后经过event.target
能够肯定触发事件的元素。
var ulParent = document.getElementById('parent'); ulParent.addEventListener('click', function(event) { var taget = event.target; })
经过"事件委托"减小了DOM元素的查询,以及多个函数的内存占用,并且还有一个好处,当咱们的li
是动态的,增长和移除时,都无需再作绑定和解绑事件操做,由于它都会冒泡到父级节点。
文档中移除了绑定了事件的DOM元素,如innerHTML/removeChild()/replaceChild()
等能够对DOM进行替换,而移除的DOM元素原先所绑定的事件处理程序,并不能有效被浏览器垃圾回收,因此占用一直存在。
因此建议在移除某个DOM元素时,若是其绑定了事件处理程序,需手动解除绑定,释放内存。
除了为元素绑定支持的事件之外,咱们还能够经过Event/CustomEvent
来建立开发者自定义事件。
二者不一样的是CustomEvent
可传递一个Object
对象来传输数据。
// Event var eve = new Event('custome'); // CustomeEvent 可传参数 var eve = new CustomeEvent('custome', { detail: { name: 'KenTsang', age: 28 } }); // 为DOM元素添加事件监听 ele.addEventListener('custome', function(event) { console.log(event.detail); }) // 触发ele绑定的自定义事件 ele.dispatch(eve);
事件这块还剩下一部分知识点,后续文章会再就模拟事件这块知识点进行拆分详解。
天冷了,更文不易,望你们多多点赞。
做者:以乐之名本文原创,有不当的地方欢迎指出。转载请指明出处。