javascript与HTML之间交互就是经过事件实现的,事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。事件在浏览器中是以对象的形式存在的,即event,触发一个事件,就会产生一个事件对象event,该对象包含着全部与事件有关的信息,包括致使事件的元素、事件的类型以及其余与特定事件相关的信息。javascript
<button onclick="clickFun()">addEvent</button>
a. element.onclick=function(e){}
b. element.addEventListener(event,function(e),useCapture)java
c. element.attachEvent(event,function(e))node
<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):与事件捕获偏偏相反,事件冒泡顺序是由内到外进行事件传播,直到根节点。app
注:不管是事件捕获仍是事件冒泡,它们都有一个共同的行为,就是事件传播dom
event.stopPropagation() event.stopImmediatePropagation()
举个栗子:此栗子是阻止事件捕获,阻止事件冒泡原理同样函数
<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高级程序设计第三版)