javascript事件基础知识
什么是事件
javascript与HTML之间交互就是通过事件实现的,事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。事件在浏览器中是以对象的形式存在的,即event,触发一个事件,就会产生一个事件对象event,该对象包含着所有与事件有关的信息,包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。
DOM元素添加事件
1.DOM元素事件属性
<button onclick="clickFun()">addEvent</button>
2.javascript脚本添加事件
a. element.onclick=function(e){}
b. element.addEventListener(event,function(e),useCapture)
- 适用范围:现代浏览器(IE9+, Firefox , chorme, safari, opera)
- 参数介绍:event必须,事件名称。(注:不含“on”,比如“click”、“mouseover”等)。function必须,指定事件触发时执行的函数。useCapture可选,布尔值,指定事件是否在捕获或冒泡阶段执行,默认false(冒泡)。
c. element.attachEvent(event,function(e))
- 适用范围:IE 6、7、8
- 参数介绍:event 必须,字符串,事件名称。(注:含“on”,比如“onclick”、“onmouseover”等)。function必须,指定事件触发时执行的函数。
<button id="myBtn">点击这里</button> <script> document.getElementById("myBtn").onclick=function(){clickFun()}; document.getElementById("myBtn").addEventListener("click",function(event){ clickFun(); },false); //采用事件冒泡方式给dom元素注册click事件 document.getElementById("myBtn").addEventListener("click",function(event){ clickFun(); },true); //采用事件捕获方式给dom元素注册click事件 document.getElementById("myBtn").attachEvent("click",function(event){ clickFun(); }); </script>
扩展:原生Javascript写法,跨浏览器添加事件
var EventUtil = { // 添加事件监听 add: function(element, type, callback){ if(element.addEventListener){ element.addEventListener(type, callback, false); } else if(element.attachEvent){ element.attachEvent('on' + type, callback); } else { element['on' + type] = callback; } } // 移除事件监听 remove: function(element, type, callback){ if(element.removeEventListener){ element.removeEventListener(type, callback, false); } else if(element.detachEvent){ element.detachEvent('on' + type, callback); } else { element['on' + type] = null; } } }
<button id="myBtn">点击这里</button> <script> var myBtn = document.getElementById("myBtn"); EventUtil.add(myBtn, 'click', function(){ console.log('被点击了'); });
事件捕获、事件冒泡
事件捕获
事件捕获(event capturing):当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
事件冒泡
事件冒泡(dubbed bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点。
注:无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事件传播
阻止事件捕获/事件冒泡
event.stopPropagation() event.stopImmediatePropagation()
- event.stopPropagation()
会阻止事件继续分发到其他document节点,但是当前节点绑定的多个事件会继续按注册的顺序执行 - event.stopImmediatePropagation()
不仅阻止事件继续分发到其他document,还会将事件分发就地停止,在当前事件之后注册的其他事件,都不会执行
举个栗子:此栗子是阻止事件捕获,阻止事件冒泡原理一样
<div id="div1"> <button id="button1">测试</button> </div> <script type="application/javascript"> var div1 = document.getElementById("div1"); var btn = document.getElementById("button1"); div1.addEventListener("click", function () { alert("div1第一次执行"); event.stopPropagation(); }, true); div1.addEventListener("click", function () { alert("div1第二次执行"); }, true); btn.addEventListener("click", function (event) { alert("button 执行"); }, true); </script> //执行结果 alert("div1第一次执行"); alert("div1第二次执行");
<div id="div1"> <button id="button1">测试</button> </div> <script type="application/javascript"> var div1 = document.getElementById("div1"); var btn = document.getElementById("button1"); div1.addEventListener("click", function () { alert("div1第一次执行"); event.stopImmediatePropagation(); }, true); div1.addEventListener("click", function () { alert("div1第二次执行"); }, true); btn.addEventListener("click", function (event) { alert("button 执行"); }, true); </script> //执行结果 alert("div1第一次执行");
事件代理
什么是事件代理
事件代理也可以称之为事件委托,事件代理就是在祖先级DOM元素绑定一个事件,当触发子孙级DOM元素的事件时,利用事件冒泡的原理来触发绑定在祖先级DOM的事件。
那这是什么意思呢?网上的各位大牛们讲事件代理基本上都用了同一个例子,就是取快递来解释这个现象,借花献佛,大家认真领会一下事件代理到底是一个什么原理:
有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工,前台MM也会在收到寄给新员工的快递后核实并代为签收。
这里其实有2层意思的:
第一,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的;
第二,新员工也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的。
为什么要用事件代理
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件代理,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;
每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了(内存不够用,是硬伤),比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,那只能呵呵了,如果用事件代理,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好。
事件代理的实现
在介绍事件代理的方法之前,先来看一段一般方法的栗子:
<ul id="ulDelegate"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul> <script> window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); var liDelegate= ulDelegate.getElementsByTagName('li'); for(var i=0;i<liDelegate.length;i++){ liDelegate[i].onclick = function(){ alert('noDelegate'); } } } </script>
上面的代码很简单,我们看看有多少次的dom操作,首先要找到ul,然后遍历li,然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li。
用事件代理的方式来实现,上栗子:
window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); ulDelegate.onclick = function(){ alert('Delegate'); } }
这里用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这里当点击ul的时候,也是会触发的。
那么问题就来了,如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办,比如说只有点击li才会触发,这时,Event对象就派上用场了,Event提供了一个属性叫target,可以返回事件的目标节点,我们称为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,我们可以转成小写再做比较,或者不转。
window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); ulDelegate.onclick = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert('Delegate'); alert(target.innerHTML); } } }
这样就只有点击li会触发事件了,且每次只执行一次dom操作,如果li数量很多的话,将大大减少dom的操作,优化的性能可想而知!
上面的栗子是说li操作的是同样的效果,如果每个li被点击的效果都不一样,上栗子:
<ul id="ulDelegate"> <li id='add'>添加</li> <li id='delete'>删除</li> <li id='edit'>修改</li> </ul> <script> window.onload = function(){ var ulDelegate= document.getElementById("ulDelegate"); ulDelegate.onclick = function (ev) { var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLocaleLowerCase() == 'li'){ switch(target.id){ case 'add' : alert('添加'); break; case 'delete' : alert('删除'); break; case 'edit' : alert('移动'); break; } } } } </script>
再来最后一个事件代理的栗子,当我们新增一个li元素的时候,同样先看一下一般写法:
<ul id="ulDelegate"> <li>111</li> <li>222</li> <li>333</li> <li>444</li> </ul> <button id="addLi">新增li</button> <script> var ulDelegate= document.getElementById("ulDelegate"); var liDelegate= ulDelegate.getElementsByTagName('li'); var addLi = document.getElementById("addLi"); function addClick() { for(var i=0;i<liDelegate.length;i++){ liDelegate[i].onclick = function(){ alert('noDelegate'); } } } addClick(); addLi.onclick = function(){ var addLiElement = document.createElement('li'); addLiElement .innerHTML = 10000*Math.random(); ulDelegate.appendChild(addLiElement ); addClick(); } </script>
再来看一下事件代理的写法:
window.onload = function(){ var ulDelegate = document.getElementById("ulDelegate"); var addLi = document.getElementById("addLi"); ulDelegate.onclick = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert('Delegate'); alert(target.innerHTML); } }; addLi.onclick = function(){ var addLiElement = document.createElement('li'); addLiElement .innerHTML = 10000*Math.random(); ulDelegate.appendChild(addLiElement ); } }
看,上面是用事件单例的方式,新添加的子元素是带有事件效果的,我们不需要去遍历元素的子节点,只需要给父级元素添加事件就好了,其他的都是在js里面的执行,这样可以大大的减少dom操作,这才是事件代理的精髓所在。
总结
事件是将javascript与网页联系在一起的主要方式,在使用事件时,需要考虑如下一些内存与性能方面的问题。
- 有必要限制夜歌页面中事件处理程序的数量,数量太多会导致占用大量内存,而且也会让用户页面反应不够灵敏
- 建立在事件冒泡机制之上的时间委托技术,可以有效的减少时间处理程序的数量
- 建议在浏览器卸载页面之前移除页面中的所有事件处理程序
事件是javascript中最重要的主题之一,深入理解事件的工作机制以及它们对性能的影响是至关重要的
(总结摘自javascript高级程序设计第三版)