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事件绑定和处理
|
//事件绑定和处理程序都写在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,不能选择事件是在捕获阶段触发还是在冒泡阶段触发
|
var oDiv = document.getElementById("mydiv"); oDiv.onclick = function(event){//事件处理程序 console.log(this.id); // 输出 mybtn } oDiv.onclick = null;//删除事件处理程序 |
DOM2级事件处理程序:就是利用事件监听进行事件处理;IE9及以上和现代浏览器使用addEventlistener()和removeEventListener();IE9以下使用attachEvent()和detachEvent();
- addEventlistener()有三个参数 (this指向当前触发事件的DOM对象;执行顺序:谁先定义谁先执行)
- 第一个参数是要执行事件的事件名(不带on;eg:click)
- 第二个参数是事件处理函数
- 第三个参数一般是一个布尔值,true表示在捕获阶段触发事件,false表示在冒泡阶段触发事件(默认值是false);也可以是一个对象,这个对象有三个属性对应的也是布尔值(默认值都是false)
- capture:false 表示在冒泡阶段触发事件
- once:true 表示事件处理程序只执行一次
- passive:true 表示事件执行程序永远不会调用preventDefault(),可以安心的运行,有利于提升浏览器性能,解决滚动或者缩放时候的卡顿等现象。即使在事件执行程序里调用里preventDefault()也会被忽略
- IE9以下事件监听程序attachEvent()
- 接收两个参数;第一个参数是要执行事件的事件名(带on;eg:onclick);第二个参数是要处理事件的执行函数
- this指针指向的是全局作用域
- 他的执行顺序是反着的;最后定义的事件监听最先执行,最先定义的事件监听最后执行
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事件对象,这个对象种包含着所有与该事件的有关信息;所有浏览器都支持事件对象,但是支持的方式不同;每一种事件对象上的属性和方法都不一样,但是都有以下这些共有的属性和方法
- 事件对象的一些属性
- target 派发事件的目标对象(触发事件的源对象)
- currentTarget 当前触发事件的对象;与this时刻保持一致
- type 事件的类型(eg:click)
- cancelable 事件能否取消默认行为
- eventPhase 事件流正在经历的阶段 1---捕获阶段,2---目标阶段,3---冒泡阶段
- defaultPrevented 是否调用了preventDefault()
- trusted 当前事件是否为浏览器自带事件
- timeStamp 事件发生在距离打开计算机的毫秒数
- 事件对象的一些方法
- stopPropagation() 取消事件冒泡
- preventDefault() 取消事件默认行为
- stopImmediatePropagation() 取消事件的冒泡,同时阻止任何定义在其后边事件处理程序
事件对象的兼容处理:IE外浏览器的事件对象可以通过事件处理程序的参数获得(事件源参数对象),IE浏览器的事件对象是window的一个属性,需要写在事件处理程序中window.event;function(ev){var event = ev || window.event}
阻止事件冒泡行为:IE外浏览器:event.stopPropagation()
;IE浏览器:event.cancelBubble = true
;
|
//兼容阻止事件冒泡行为 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
;
|
//阻止事件默认行为 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; } } } } |
事件类型
UI与焦点事件
- UI事件:用户与页面上的元素进行交互的时候触发(不一定与用户操作有关)
- load:页面(图像,JS文件,CSS文件等外部资源)完全加载后触发;(支持load的标签有img,script,link,video,audio等)
- unload:当页面完全卸载之后触发;(例如用户从当前页面切换到其他页面就会触发)
- resize:窗口大小发生变化的时候触发
- scroll:滚动滚动条的时候触发
- error:JavaScript发生错误的时候触发
- abort:正在加载资源被终止的时候触发
- DOMContentLoaded:页面形成完整的DOM树之后就触发,不理会图片JS、CSS外部文件等资源是否已下载完毕;H5新增, IE9+,safari3.1+,opera9+, firefox,chrome
- beforeunload:页面卸载之前触发;H5新增, IE,safari,opera11+, firefox,chrome
- haschanged:URL中哈希值发生变化的时候触发;event对象额外多两个属性:oldURL,newURL保存变化前后的完整URL(只有firefox6+,chorme,opera支持,所以还是使用location.hash来记录的好);H5新增, IE8+,safari5+,opera10.6+, firefox3.6+,chrome
- readystatechange:文件或者元素的加载状态发生变化的时候触发,多用于Ajax请求;H5新增
- readystatechange事件对象event新增readyState属性
- uninitialized:未初始化
- loading:正在加载数据
- loaded:数据加载完成
- interactive:可以进行交互
- complete:加载完成
- pageshow和pagehide:用户使用浏览器前进或者后退的时候触发;event对象新增persisted(页面是否通过缓存加载);H5新增 IE10+,safari5+,opera, firefox,chrome
- 焦点事件:在页面元素获得或者失去焦点的时候触发;通常配合document.hasFocus()页面时候获得焦点和document.activeElement()获得焦点的元素得知用户在页面上的行踪
- blur:元素失去焦点的时候触发;不会冒泡
- focus:元素获得焦点的时候触发;不会冒泡
- focusin:元素获得焦点触发;冒泡;IE5.5+,safari5.1+,opera11.5+,chrome
- focusout:元素失去焦点触发; IE5.5+,safari5.1+,opera11.5+,chrome
鼠标与滚轮事件
- 鼠标点击相关事件
- click:点击鼠标主键触发(一般是时左键)
- dblclick:双击鼠标主键触发(鼠标事件的触发顺序:mousedown-mouseup-click-mousedown-mouseup-click-dblclick;IE8会跳过第二次的mousedown和click)
- mousedown:鼠标按键按下触发
- mouseup:鼠标按键抬起触发
- contextmenu:点击鼠标的副键时触发(一般是右键) H5新增
- 鼠标移动相关
- mouseover:鼠标移入时触发(移入子节点也触发);有冒泡行为
- mouseout:鼠标移出时触发(移出子节点也触发);有冒泡行为
- mousemove:鼠标在元素上移动的时候触发
- mouseenter:鼠标进入时触发(进入子节点不触发);无冒泡
- mouseleave:鼠标离开时触发(离开子节点不触发);无冒泡
- 鼠标滚轮
- 不考虑浏览器兼容(IE9+)可以直接使用
wheel
事件,deltaY
表示垂直滚动的距离; - IE,opera,chrome,safari等用
mousewheel
事件表示事件滚动;wheelDelta
表示一次滚动多少,一般向上120,向下-120 ( 数值不固定 ,opera9.5之前上下滚动得到的数值正负号是颠倒的); - Firefox有自己特有的
DOMMouseScroll
事件来表示滚轮事件;detail
表示一次滚动多少向上-3向下3; - 兼容处理:将
mousewheel
事件和DOMMouseScroll
事件都写上;
- 鼠标事件对象event的一些属性
- 鼠标位置位置相关
- screenX(screenY) 鼠标相对于整个电脑屏幕的左上角的距离
- clientX(clientY) 鼠标相对于可视区域(浏览器窗口)左上角的距离
- pageX(pageX) 鼠标点击相对于整个页面左上角的距离(IE9+和现代浏览器); 兼容IE8:
clientX + scrollLeft - clientLeft
- layerX(layerX) 鼠标当前位置距离触发对象左上角的距离;以border为参考点
- offsetX(offsetX) 鼠标当前位置距离触发对象左上角的距离;以content为参考点
- 按键按钮相关
- ctrlKey 鼠标点击的时候是否按下了ctrl键;布尔值
- shiftKey 鼠标点击的时候是否按下了shift键;布尔值
- aultKey 鼠标点击的时候是否按下了ault键;布尔值
- metaKey 鼠标点击的时候是否按下了meta键(window键);布尔值
- button 0表示主键,1表示中建,2表示副键;但是在IE8及以下版本,1表示主键,4表示中建,2表示副键
- buttons 同时按下了鼠标的那几个键的键值和
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 |
键盘与文本事件
- 键盘事件
- keydown 用户按下任意键时触发;按下不松会重复触发
- keypress 用户按下字符键时触发; 按下不松会重复触发
- keyup 用户抬起按键时触发
- 键盘事件对象Event属性
- keyCode:按下键的键码值;(keypress事件也可以用charCode属性获取ASCII编码);获得键码或者编码之后可以通过String.fromCharCode(event.keyCode)来获得实际按下的字符
- shiftKey、ctrlKey、altKey、metaKey(IE不支持metaKey,FF有问题):快捷键是否按下;布尔值
- 文本事件(DOM3新增,只有IE9+,safari,chrome支持)textInput 当用户再可编辑区域输入字符时触发该事件(自己测试IE也不支持)
- 文本事件对象Event属性data:按下的那个键的字符
设备、触摸与手势事件
- 设备事件 可以让开发人员确定用户怎样在使用设备的一些事件
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包含:- acceleration:包含x、y、z属性的对象,不考虑重力的情况下,每个方向的加速度
- accelerationIncludingGravity:包含x、y、z属性的对象,在考虑自然重力加速度的情况下,每个方向上的加速度
- interval:毫秒值,必须在另一个devicemotion事件触发前传入
- rotationRate:包含表示方向的alpha、beta和gamma属性的对象
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); |
- 触摸事件 (触摸屏幕时发生事件的顺序:touchStart-mouseover-mousemove-mousedown-mouseup-click-touchend)
- touchstart:触摸屏幕时触发
- touchmove:滑动时触发,调用preventDefault()函数可阻止滚动
- touchend:当手指从屏幕上移开时触发
- touchcancel:当系统停止跟踪触摸时触发
- 触摸事件对象event增加属性
- touches:表示当前跟踪的触摸操作的Touch对象的数组
- targetTouches:特定于事件目标的Touch对象的数组
- changedTouches:表示自上次触摸以来发生了什么改变的Touch对象的数组
- 每个touch对象又有以下属性
- clientX:视口中的x坐标
- pageX:触摸目标在页面中的x坐标
- screenX:触摸目标在屏幕中的x坐标
- target:触摸DOM节点目标
- identifier:标识触摸的唯一ID
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); |
- 手势事件 现在只有ios设备支持
- gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发
- gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发
- gestureend:当任何一个手指从屏幕上面移开时触发
- 手势事件对象event增加属性
- rotation:表示旋转的角度,从0开始,负值表示逆时针旋转,正值表示顺时针旋转
- scale:表示两个手指间距离的变化,从1开始,随着距离拉大而增加,随着距离缩小而减小
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); |
变动事件和MutationObserver对象
- 变动事件 DOM结构发生变化的时候触发的事件 (IE8及以下版本不支持)
- DOMSubtreeModified DOM结构发生任何变化时都触发
- DOMNodeInserted 插入节点的时候触发
- DOMNodeRemoved 移除节点的时候触发
- DOMNodeInsertedIntoDocument 插入文档的时候触发
- DOMNodeRemovedFromDocument 节点被直接从文档中删除的时候触发
- DOMAttrModified 在特性被修改的之后触发
- DOMCharacterDataModified 文本节点的值发生变化的时候触发
变动事件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+
- MutationObserver对象上的方法
- observe(DomNode,options) 启动观察,设置观察的对象和观察内容
- DomNode:要观察的节点
- options:要观察什么的配置对象
- childList :观察子元素变动(增、删、改)
- attributes:观察属性变动
- characterData:观察节点内容或者文本节点内容变动
- subtree:是否观察所有子节点及后代节点的变动;布尔值;(不能单独使用,需配合一种设置属性使用)
- attributeOldValue:attributes变动时是否记录之前的属性值;布尔值
- characterDataOldValue: characterData变动时是否记录之前的值;布尔值
- attributesFilter:需要观察的特定属性的数组;值为数组
- disconnect() 停止观察;触发之后,Dom的变化就不会再被观察记录了
- takeRecords() 清除变动记录, 即不再处理未处理的变动
- MutationRecord对象的属性 (跟事件对象event很像,与event不同的是它是由多个MutationRecord实例组成的数组) Dom每发生一次变化就产生一个MutationRecord实例对象,这个对象里包含着所有与变动相关的信息,mutationObserver处理的就是一个个mutationRecord组成的实例数组
- type:观察变动的类型(childList、attributes...)
- target:发生变动的DOM对象
- addedNodes:新增的DOM对象
- removedNodes:删除的DOM对象
- previousSibling:前一个同级节点,没有返回null
- nextSibling:后一个同级节点,没有返回null
- attributeName:发生变动的属性;如果设置了attributeFilter就只返回预先指定的属性
- oldValue:变动前的值;只对attributes和characterData的变动有效
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对象;此方法接收一个参数,即要创建的事件类型;可以是以下几种类型直以
- UIEvents:一般化的UI事件,鼠标事件和键盘事件都继承自UI事件,DOM3级中是UIEvent
- MouseEvents:一般化的鼠标事件,DOM3级中是MouseEvent;
- MutationEvents:一般化的DOM变动事件,DOM3级中是MutationEvent
- HTMLEvents:一般化的HTML事件,没有对应的DOM3级事件,被分散到 其他类别中
2.初始化事件对象:使用与事件有关的信息对其进行初始化,每种类型的event对象都有一个特殊的方法,为它传入适当的数据就可以初始化该event对象,不同类型的方法的名字也不相同(一般是init开头的),具体要取决于createEvent()中使用的参数
3.触发事件 :最后调用dispatchEvent()方法,此方法接收一个参数,即要触发事件的event对象。
- 模拟鼠标事件的事件对象包含属性
- type:事件类型
- bubble:是否冒泡
- cancelable:是否可取消默认行为
- view:与事件关联的视图,一般为
document.defaultView
- detail:一般为0,一般只有事件处理程序使用
- screenX:事件相对于屏幕的X坐标
- clientX:事件相对于视口的X坐标
- ctrlKey、 altKey 、 shiftKey 、 metaKey :是否按下了相应的键,默认为false
- button:表示按下了哪个鼠标键,默认为0
- relatedTarget:表示与事件相关的对象。一般只有在模拟mouseover与mouseout时使用
- 模拟键盘事件的事件对象包含的属性
- type:事件类型
- bubbles:是否应该冒泡
- cancelable:是否可以取消默认行为
- view:与事件关联的视图。一般为
document.defaultView
; - key:按下的键的键码
- location:表示按下哪里的键。0为主键盘;1为左;2为右;3为数字键盘;4为虚拟键盘;5为手柄
- modifiers:空格分隔的修改键列表,如“shift”
- repeat:在一行中按下了多少次这个键
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()方法,触发定义的事件
- 自定义的event事件对象包含的四个参数
- type:事件类型;字符串
- bubbles:时间是否应该冒泡;布尔值
- cancelable:表示事件是否可以取消;布尔值
- detail:任意值,保存在event对象的detail属性中;对象
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); |