JS事件就是发生在DOM元素、document对象、window对象上的一些特定的交互瞬间(比如点击一个元素发生onclick事件);JS和HTML的交互就是通过事件来实现的;
事件流就是描述页面元素接受事件的顺序;
冒泡流:就是由目标元素逐级向上传播一直到根节点,是由IE制定的事件触发顺序;所有浏览器都支持事件冒泡;(有的事件会跳过事件冒泡,例如 focus和blur事件);一般在事件冒泡阶段会触发目标事件
捕获流:从根节点开始逐级向下传播一直到目标元素,是由网景公司提出的事件触发顺序;事件捕获比冒泡更快一点,提升浏览器性能(在移动端的touch事件常用),IE9以下不支持事件捕获;在为了提升浏览器性能,现代浏览器也都会在捕获阶段触发目标事件
W3C考虑到IE所占的市场份额,就将IE主张的冒泡流和网景公司主张的捕获流综合了一下,于是DOM2级事件就分为了三个阶段:捕获阶段、目标阶段、冒泡阶段;
事件是用户或者浏览器自身执行的某种动作,而响应事件的那个函数就是事件处理程序;为元素添加事件的方式有三种:HTML内联式、属性绑定、事件监听;那么对应的事件处理程序也有三种方式HTML事件处理程序、DOM0事件处理程序、DOM2事件处理程序
HTML事件处理程序 :直接在DOM节点上定义要执行的事件和事件处理程序;缺点:1.事件处理程序直接写在HTML里,需要注意一些特殊字符的转义;2.事件处理程序写在js文件里,当页面加载的时候节点会先加载出来,但是事件处理程序不一定加载出来;3.事件绑定在html上,事件处理程序写在js文件里,修改代码的时候会很麻烦而且结构和行为的耦合度会比较高,不易维护;所以真正开发基本不会用到HTML事件绑定和处理
1 2 3 4 5 6 7 8 9 |
//事件绑定和处理程序都写在html里 <input type="text" onkeydown="console.log(this.value)"> //事件绑定在html,处理程序写在js <input type="text" onkeydown="handle()"> <script> function handle() { console.log(666); } </script> |
DOM0事件处理程序:就是属性绑定的事件的执行函数;注意:执行函数的this指向的是当前触发它的dom对象;每个执行函数都有一个event参数,它是当前事件的局部事件对象。DOM0级事件处理程序解决了HTML事件处理程序的缺点,但是也存在几个缺点:1.一个节点只能定义一个同名事件处理程序,多了会被覆盖;2,不能选择事件是在捕获阶段触发还是在冒泡阶段触发
1 2 3 4 5 |
var oDiv = document.getElementById("mydiv"); oDiv.onclick = function(event){//事件处理程序 console.log(this.id); // 输出 mybtn } oDiv.onclick = null;//删除事件处理程序 |
DOM2级事件处理程序:就是利用事件监听进行事件处理;IE9及以上和现代浏览器使用addEventlistener()和removeEventListener();IE9以下使用attachEvent()和detachEvent();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
//IE9及以上和现代浏览器 var oDiv = document.getElementById("mydiv"); function handle(event){//事件处理程序 console.log(this.id); } document.body.addEventListener('click',function (){ console.log(this.nodeName) },true)//body的click事件在捕获阶段触发 //oDiv.addEventListener('click',handle)//默认冒泡阶段触发事件 //oDiv.addEventListener('click',handle,true)//在捕获阶段触发事件 oDiv.addEventListener('click',handle,{capture:false,once:true,passive:true})//事件在冒泡阶段触发只触发一次不执行preventDefault() //oDiv.removeEventListener('click',handle)//删除事件 //IE9以下 function handle(){//事件处理程序 console.log(666); } oDiv.attachEvent('onclick',function (){ console.log(this == window);//true;this指针永远指向window }) oDiv.attachEvent('onclick',handle)//666;控制台会先打印666,再打印true oDiv.detachEvent('onclick',handle);//删除oDiv的handle函数的click事件 //兼容跨浏览器事件处理程序 var eventListen = { addListen:function(ele,type,handle,bubble){ if (ele.addEventListener) { ele.addEventListener(type,handle,bubble || false); }else if (ele.attachEvent) { ele.attachEvent('on' + type,handle); }else{ ele['on' + type] = null; } }, removeListen:function(ele,type,handle,bubble){ if (ele.removeEventListener) { ele.removeEventListener(type,handle,bubble || false); }else if (ele.detachEvent) { ele.detachEvent('on' + type,handle); }else{ ele['on' + type] = null; } } } eventListen.addListen(document.body,'click',handle,true); eventListen.addListen(oDiv,'click',handle,true); eventListen.removeListen(oDiv,'click',handle,true);//注意删除事件时如果bubble有值删除也要写,才能删除 |
事件优先级:1).定义在HTML上的事件会被DOM0和DOM2事件覆盖;DOM0和DOM2本质上都是属性事件,谁先定义谁先触发;2).按照事件的阶段来说捕获阶段的事件会最先触发,HTML事件和DOM0事件都只有目标阶段和冒泡阶段随后触发(按照事件定义的先后顺序触发);3).事件的执行顺序是依照HTML的DOM树顺序进行触发的,与CSS没有关系;(点击事件在移动设备上的事件穿透行为可能会影响到事件的触发顺序)
事件在触发的时候会产生一个event事件对象,这个对象种包含着所有与该事件的有关信息;所有浏览器都支持事件对象,但是支持的方式不同;每一种事件对象上的属性和方法都不一样,但是都有以下这些共有的属性和方法
事件对象的兼容处理:IE外浏览器的事件对象可以通过事件处理程序的参数获得(事件源参数对象),IE浏览器的事件对象是window的一个属性,需要写在事件处理程序中window.event;function(ev){var event = ev || window.event}
阻止事件冒泡行为:IE外浏览器:event.stopPropagation()
;IE浏览器:event.cancelBubble = true
;
1 2 3 4 5 6 7 8 9 10 |
//兼容阻止事件冒泡行为 function handleStopPropagation(ev){ var event = ev || window.event; console.log('Do something'); if(ev.soptPropagation){//现代浏览器阻止冒泡事件 event.stopPropagation(); }else{//IE阻止冒泡 event.cancelBubble = true; } } |
阻止事件默认行为:IE外浏览器:event.preventDefault()
;IE浏览器:event.returnValue = false
;
1 2 3 4 5 6 7 8 9 10 |
//阻止事件默认行为 function handlePreventDefault(ev){ var event = ev || window.event; console.log('Do something'); if(ev.preventDefault){//现代浏览器阻止事件默认行为 event.preventDefault(); }else{//IE浏览器阻止事件默认行为 event.returnValue = false; } } |
兼容事件处理程序及事件对象的一些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
//兼容跨浏览器的事件处理程序对象 var eventListen = { getEvent:function(ev){//兼容事件对象event return ev || window.event; }, getTarget:function(){//兼容事件目标 return event.target || event.srcElement; }, getRelatedTarget: function(){//返回相关元素信息(仅对于mouseover和mouseout事件) if (event.relatedTarget){ return event.relatedTarget; } else if (event.toElement){ return event.toElement; } else if (event.fromElement){ return event.fromElement; } else { return null; } }, addListen:function(ele,type,handle,bubble){//兼容添加监听事件 if (ele.addEventListener) { ele.addEventListener(type,handle,bubble || false); }else if (ele.attachEvent) { ele.attachEvent('on' + type,handle); }else{ ele['on' + type] = null; } }, removeListen:function(ele,type,handle,bubble){//兼容删除监听事件 if (ele.removeEventListener) { ele.removeEventListener(type,handle,bubble || false); }else if (ele.detachEvent) { ele.detachEvent('on' + type,handle); }else{ ele['on' + type] = null; } }, stopBubble:function(){//阻止冒泡行为 event.stopPropagation? event.stopPropagation() : event.cancelBubble = true; }, preventDefault:function(){//阻止默认行为 event.preventDefault? event.preventDefault() : event.returnValue = false; }, getWheelDelta:function(){//兼容滚轮事件 if (event.wheelDelta) { var userAgent = navigator.userAgent.toLowerCase();//用户信息 var isOpera = userAgent.indexOf('opera') > -1;//是否是opera浏览器 if (isOpera) { var operaVersion = userAgent.match(/opera.([\d.]+)/)[1];//获取opera浏览器的版本号 var operaVersionMoreThan = operaVersion.split('.',2).join('.') < 9.5//opera浏览器版本号是否小于9.5 } return (isOpera && operaVersionMoreThan? -event.wheelDelta : event.wheelDelta) }else{ return -event.detail*40 } }, getCharCode:function() {//兼容获取键盘按键键码。 return typeof event.charCode == 'number'? event.charCode:event.keyCode }, getButton:function(){//兼容鼠标按键值 if (document.implementation.hasFeature('MouseEvents','2.0')) { return event.button }else{ switch (event.button){ case 0: case 1: case 3: case 5: case 7: return 0; case 2: case 6: return 2; case 4: return 1; } } } } |
wheel
事件,deltaY
表示垂直滚动的距离; mousewheel
事件表示事件滚动;wheelDelta
表示一次滚动多少,一般向上120,向下-120 ( 数值不固定 ,opera9.5之前上下滚动得到的数值正负号是颠倒的);DOMMouseScroll
事件来表示滚轮事件;detail
表示一次滚动多少向上-3向下3;mousewheel
事件和DOMMouseScroll
事件都写上;clientX + scrollLeft - clientLeft
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
//pageX的兼容处理 function handle(ev){//事件处理程序 var event = ev || window.event; var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; var pageX = event.pageX; var pageY = event.pageY; if (pageX == undefined || pageY == undefined) { pageX = event.clientX + scrollLeft; pageY = event.clientY + scrollTop; } console.log(pageX,pageY); } eventListen.addListen(oDiv,'click',handle);//使用兼容的事件监听程序 //滚轮的兼容处理 var eventListen = { //省略其他代码 getWheelDelta:function(){ if (event.wheelDelta) {//除火狐外其他浏览器 var userAgent = navigator.userAgent.toLowerCase();//用户信息 var isOpera = userAgent.indexOf('opera') > -1;//是否是opera浏览器 if (isOpera) {//是opera再判断版本号 var operaVersion = userAgent.match(/opera.([\d.]+)/)[1];//获取opera浏览器的版本号 var operaVersionMoreThan = operaVersion.split('.',2).join('.') < 9.5//opera浏览器版本号是否小于9.5 } return (isOpera && operaVersionMoreThan? -event.wheelDelta : event.wheelDelta);//opera9.5以下版本符号是相反的 }else{//火狐浏览器 return -event.detail*40 } } } function getDelta(){ var event = eventListen.getEvent(); var delta = eventListen.getWheelDelta(); console.log(delta); } eventListen.addListen(myDiv,'DOMMouseScroll',getDelta)//兼容火狐 eventListen.addListen(myDiv,'mousewheel',getDelta)//除火狐其他浏览器 //button的兼容处理 var eventListen = { //省略其他代码 getButton:function(ev){ var event = ev || window.event; if (document.implementation.hasFeature('MouseEvents','2.0')) { return event.button }else{ switch (event.button){ case 0: case 1: case 3: case 5: case 7: return 0; case 2: case 6: return 2; case 4: return 1; } } } } function getBtn(){ var event = eventListen.getEvent(); var button = eventListen.getButton(); console.log(button); } eventListen.addListen(myDiv,'mousedown',getBtn);//IE8以下也是主键0中间键1副键2 |
orientationchange
事件 手机旋转方向时触发;该事件的window.orientation
属性中包含3个值:0表示肖像模式、90表示左旋转的横向模式、-90表示右旋转的横向模式 ;(在事件执行函数里直接调window.orientation
,event对象里什么都没有)deviceOrientationEvent
事件 主要是用来通过在x、y、z方向来告诉开发者设备的空间朝向deviceOrientationEvent
事件时event包含:alpha
:围绕z轴旋转,y轴的度数差;0~360的值beta
:围绕x轴旋转,z轴的度数差;-180~180的值gamma
:围绕y轴旋转,z轴的度数差;-90~90的值absolute
:表示设备是否返回一个绝对值 compassCalibrated
:表示设备的指南针是否校准过devicemotion
事件 主要是用来告诉开发者设备什么是什么时候移动的;可以用来判断移动设备是不是在往下掉,或者是不是被走着的人拿在手里devicemotion
事件时event包含:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//设备事件在手机端uc上正常显示;chrome除了设备旋转方向却都是null var oOri = document.getElementById('ori'); var oDeviceo = document.getElementById('deviceo'); var oDevicem = document.getElementById('devicem'); //旋转屏幕的时候触发事件得到值 function oriHandle(ev){ var event = eventListen.getEvent(); console.log(window.orientation) oOri.innerText = '旋转方向' + window.orientation; } eventListen.addListen(window,'load',oriHandle);//页面加载完之后执行一次oriHandle函数得到初始值 eventListen.addListen(window,'orientationchange',oriHandle); //手机空间朝向发生变化时触发事件得到想要的值 function deviceori(ev){ var event = eventListen.getEvent(); oDeviceo.innerText = '空间方向:绕Z轴:' + event.alpha + ';绕X轴:' + event.beta + ';绕Y轴:' + event.gamma } window.addEventListener("deviceorientation",deviceori); //移动设备时的各个方向的加速度变化时触发 function oDevicemot(){// var event = eventListen.getEvent(); oDevicem.innerText = '不考虑重力加速度,每个方向上的加速度:X方向:' + event.acceleration.x + ';Y方向:' + event.acceleration.y + ';Z方向:' + event.acceleration.z + '。考虑重力加速度,每个方向上的加速度:X方向' + event.accelerationIncludingGravity.x + ';Y方向:' + event.accelerationIncludingGravity.y + ';Z方向:' + event.accelerationIncludingGravity.z; console.log(event.acceleration) } window.addEventListener("devicemotion",oDevicemot); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var oTouch = document.getElementById('touch'); var movedX; function touchFn(){ var event = eventListen.getEvent(); if (event.touches.length == 1) {//只跟踪一次触摸膜 switch(event.type){ case 'touchstart': movedX = 0; movedX = event.touches[0].clientX;//记录开始滑动时的位置 oTouch.innerText = '水平方向开始滑动的位置:' + movedX; break; case 'touchmove': oTouch.innerText = '水平方向正在滑动:' + (event.changedTouches[0].clientX - movedX); break; } }else if (event.touches.length == 0) {//touchend事件的touches是空 movedX = event.changedTouches[0].clientX - movedX;//得到鼠标移动的距离 oTouch.innerText = '水平方向滑动的距离:' + movedX; } } eventListen.addListen(window,'touchstart',touchFn); eventListen.addListen(window,'touchmove',touchFn); eventListen.addListen(window,'touchend',touchFn); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//目前似乎只有苹果设备才支持,以下代码没有得到验证 var oGesture = document.getElementById('gesture'); var gesrota; function gestureFn(){ var event = eventListen.getEvent(); alert(event) switch(event.type){ case 'gesturestart': gesrota = event.rotation; oGesture.innerText = '旋转开始度数:' + event.rotation + '。两个手指间初始距离:' + event.scale; break; case 'gesturechange': oGesture.innerText = '旋转度数:' + event.rotation + '。两个手指间距离:' + event.scale; break; case 'gestureend': oGesture.innerText = '旋转了:' + (event.rotation - gesrota) + '。两个手指间距离:' + event.scale; break; } } eventListen.addListen(window,'gesturestart',gestureFn); eventListen.addListen(window,'gesturechange',gestureFn); eventListen.addListen(window,'gestureend',gestureFn); |
变动事件Mutation events
,主要是监听节点的变化做一些事情;但是它存在着一些问题:1).浏览器支持情况不是特别好,webkit内核浏览器不支持DOMAttrModified
,而且自己测试IE和FF都不支持Mutation events了;2). Mutation events
是同步加载的,每次调用变动事件都会从事件队列中取出事件、执行事件、移除事件,频繁改动事件队列会造成浏览器性能下降;3).变动事件采用的是冒泡流,在冒泡阶段又多次触发其他变动事件,可能会阻塞JS进程,导致浏览器崩溃;为此DOM4为我们提供了MutationObserver
对象来替代Mutation events
;
MutationObserver
对象会将变动记录放在一个数组里,当文档加载结束后再统一执行变动事件;是异步加载的;可以观察整个DOM也可以观察某一类DOM;浏览器支持情况:IE11+、edge12+、opera15+、safari7+、firefox14+,chrome26+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var oDiv = document.getElementById('div'); //观察节点 最好写在JS最上边,要不然可能会造成有些变化监听不到 function callback(record){//2.实例化回调函数;监听节点变化执行要做的操作 record.forEach(function(item){ console.log('type==>' + item.type,item.target,item.addedNodes,item.removedNodes,'attributeName==>' + item.attributeName,'oldValue==>' + item.oldValue); }) } var mutObs = new MutationObserver(callback);//1.实例化Mutationobserver对象 var options = {//配置信息 childList:true,//配置子节点观察 subtree:true, attributes:true,//配置属性观察 attributeOldValue:true, characterData:true,//配置文本节点观察 characterDataOldValue:true } mutObs.observe(document.body,options);//3.开启观察 setTimoeout(()=>mutObs.disconnect(),2000)//在需要解除观察的时候解除观察 //操作节点 var cDiv = document.createElement('div'); cDiv.id = 'grandson'; cDiv.className = 'show'; cDiv.innerText = '我是div'; oDiv.appendChild(cDiv);//添加节点 cDiv.lastChild.data = '我改变了文本节点内容';//改变文本节点内容 cDiv.classList.remove('show');//删除属性值 oDiv.removeChild(cDiv);//删除节点 |
事件可以增强浏览器交互能力;但是每一个函数都是一个对象,都需要占用一定内存,内存中对象越多性能就越差;还有就是事先定义的事件处理程序访问DOM节点,会延迟页面的就绪时间,也就是加长了页面加载时间;这样总是不好的,所以我们需要尽可能的优化我们的事件
我们知道一般的事件都会向上冒泡,逐次向上层元素触发该类事件,那么我们是不是可以在同类型的事件的最上层DOM节点上定义这个事件,然后判断当前点击对象再做出对应的响应;这就是事件委托;将相同的事件类型委托给最外层的节点,通过冒泡的方式触发,从而减少事件处理程序的定义;如果可以的话最好直接定义到document元素上,这样既节省了内存空间和页面加载时间而且可以在页面的任何生命周期快速添加事件处理程序; 最适合采用事件委托的事件包括:click、mousedown、mouseup、keydown、keyup 和 keypress
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//点击li执行不同的事件处理程序 var oUl = document.getElementById('ul'); function eventDelegation(ev){ var event = eventListen.getEvent(); var target = eventListen.getTarget();//通过target获取到当前点击的对象 switch(target.id){//为每个不同对象添加需要的操作 case 'li1': console.log('我是第一个li,我是老大'); break; case 'li2': console.log('我是老二'); break; case 'li3': console.log('我最小'); break; } } eventListen.addListen(oUl,'click',eventDelegation);//将所有事件委托给ul,在冒泡到ul上时触发当前事件 |
在我们确定不需要某个事件的时候,我们就需要将这个事件及时的移除掉,这样有利于浏览器的性能;一般是在删除元素removeChild()或者replaceChild()元素被删除,或者使用innerText替换掉了原先有事件的节点,这个时候事件就没用了,就可以移除了;还有就是页面卸载之后事件就都没用了,但是浏览器不一定能把所有事件处理程序都清理干净,特别是IE8及以前的浏览器,会留下比较多未被清理的事件处理程序在内存中,导致内存被无故占用,可以使用unload事件在页面彻底卸载之前把所有事件处理程序都清理了
JS模拟事件要分三个步骤:1.创建事件对象event; 2.初始化事件对象;3.触发事件
1.创建事件对象event:document为我们提供了一个createEvent()方法来创建event对象;此方法接收一个参数,即要创建的事件类型;可以是以下几种类型直以
2.初始化事件对象:使用与事件有关的信息对其进行初始化,每种类型的event对象都有一个特殊的方法,为它传入适当的数据就可以初始化该event对象,不同类型的方法的名字也不相同(一般是init开头的),具体要取决于createEvent()中使用的参数
3.触发事件 :最后调用dispatchEvent()方法,此方法接收一个参数,即要触发事件的event对象。
document.defaultView
document.defaultView
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//模拟鼠标事件 var mouseevent = document.createEvent("MouseEvents");//1.创建模拟事件对象 mouseevent.initMouseEvent("mouseclick",true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null);//2.初始化模拟事件对象 function mouseclickFn(ev){ var event = eventListen.getEvent() console.log('我是模拟出来的鼠标点击事件',event.isTrusted); } eventListen.addListen(oUl,'mouseclick',mouseclickFn);//监听模拟事件 oUl.dispatchEvent(mouseevent);//3.触发模拟事件对象 //模拟键盘事件 var keyevent = document.createEvent("KeyboardEvent"); keyevent.initKeyboardEvent("mykeydown",true,true,document.defaultView,"a",0,"Shift",0); function mykeydownFn(ev){ var event = eventListen.getEvent(); console.log('我是模拟键盘事件',event.isTrusted) } eventListen.addListen(document,'mykeydown',mykeydownFn) document.dispatchEvent(keyevent); //以上方式还是不太方便,我们可以使用JS为我们提供的特定对象实例化模拟事件 var mymouseEvent = new MouseEvent('mymouse',{ bubbles: true, cancelable: true, clientX: 666, clientY: 666 }); function mymouseFn(ev){ var event = eventListen.getEvent(); console.log('我是实例化模拟鼠标对象出来的鼠标模拟事件',event.clientX,event.isTrusted); } eventListen.addListen(document,'mymouse',mymouseFn) document.dispatchEvent(mymouseEvent); |
我们既然可以模拟事件,当然也可以自定义一个事件;与模拟事件步骤一样1.调用document.createEvent('CustomEvent')创建一个event对象2.初始化这个event对象所包含initCustomEvent对象,这个对象接收四个参数;3. 调用dispatchEvent()方法,触发定义的事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//自定义事件 //此方法已过时 var buildevent = document.createEvent('Event');//创建自定义event对象 buildevent.initEvent('build', true, true);//初始化自定义对象 document.addEventListener('build', function (e) {//添加监听 console.log(999) }, false); document.dispatchEvent(buildevent);//触发自定义对象 //可以使用以下方法 var customEvent = new CustomEvent('myevent',{'detail':'我是通过实例化自定义的事件'}); function myeventFn(ev){ var myevent = eventListen.getEvent(); console.log(myevent.detail,666) } eventListen.addListen(document,'myevent',myeventFn); document.dispatchEvent(customEvent); |