事件
JavaScript与HTML之间的交互是通过事件实现的
一、事件流
事件流描述的是从页面接收事件的顺序()
- IE提出的是:事件冒泡流
- Netscape Communicator提出的是:事件捕获流
1、事件冒泡
事件开始从最具体的元素接收,然后逐级向上传播到较为不具体的节点
比如:单击了一个div,事件传播顺序:
- div
- body
- html
- document
IE5.5 以前事件冒泡会跳过html元素,从body直接跳到document。 IE9/Firefox/Chrome/Safari则将事件一直冒泡到window对象
2、事件捕获
最具体的元素最后接收到事件
比如:单击了一个div,事件传播顺序:
- document
- html
- body
- div
3、事件流
DOM2级事件 规定事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
IE8以前的版本不支持DOM事件流
二、事件处理程序
事件就是用户或浏览器自身执行的某种动作,而响应某个事件的函数就叫做事件处理程序
1、HTML事件处理程序
<div onclick="alert('click me!')">Click Me</div>
2、DOM0级事件处理程序
let app = document.querySelector('#app')
app.onclick = function() {
alert(this.id); // app
}
将事件处理函数赋值为null,即删除这种方式指定的事件处理程序
3、DOM2级事件处理程序
DOM2级事件 定义了两个方法:
addEventListener():处理指定的事件处理程序removeEventListener():删除事件处理程序- 参数:
- 第一个参数:事件名
- 第二个参数:事件处理程序函数
- 第三个参数:布尔值;true表示在捕获阶段调用事件处理程序,false表示在冒泡阶段调用时机处理程序
- 参数:
let app = document.querySelector('#app')
app.addEventListener('click', function() {
alert(this.id); // app
}, false)
为了最大限度的兼容各种浏览器,我们不推荐在事件捕获阶段注册事件处理程序。通常都是事件处理程序添加到事件流的冒泡阶段
4、IE事件处理程序
IE实现了与DOM类型的两个方法:
attachEvent()detachEvent()- 参数:
- 第一个参数:事件名
- 第二个参数:事件处理程序函数
- 参数:
因为IE8以前只支持冒泡,那么他添加的事件处理程序默认都是添加到冒泡阶段
let app = document.querySelector('#app')
app.attachEvent('onclick', function() {
alert(this === window); // true
}, false)
注意:这里是onclick,而不是DOM中的click
在IE中使用attachEvent方法与使用DOM0级方法主要区别在于事件处理程序的作用域。
- 在使用DOM0级方法,事件处理程序会在其所属的元素作用域中运行
- 使用attachEvent方法,事件处理程序会在全局作用域中运行,this等于window
并且attachEvent可以绑定多个不同的事件处理程序,但是执行顺序是反过来的,而不是添加他们的顺序执行。
支持IE事件处理程序的浏览器有IE和Opera
5、 跨浏览器的事件处理程序
没有考虑到所有浏览器问题。例如在IE中的作用域问题。不过,是用他们添加移除事件处理程序足够了
let app = document.querySelector('#app')
let EventUtil = {
// 分别使用DOM0级方法、DOM2级方法、IE方法来添加事件
addHandler: function(el, type, handler) {
if(el.addEventListener) {
el.addEventListener(type, handler, false)
}else if(el.attachEvent) {
el.attachEvent('on' + type, handler)
}else { // DOM0级方法
el['on' + type] = handler
}
},
removeHandle: function(el, type, handler) {
if(el.removeEventListener) {
el.removeEventListener(type, handler, false)
}else if(el.detachEvent) {
el.detachEvent('on' + type, handler)
}else {
el['on' + type] = null
}
}
}
let handler = function() {
alert('clicked')
}
EventUtil.addHandler(app, 'click', handler);
// EventUtil.removeHandle(app, 'click', handler);
三、跨浏览器的事件对象
let EventUtil = {
addHandler: function() {},
removeHandle: function() {},
// 在IE中,event对象在window下
getEvent: function(event) {
return event ? event : window.event;
},
// 返回事件的目标
getTarget: function(event) {
return event.target || event.srcElement;
},
// 取消事件的默认行为
preventDefault: function(event) {
if(event.preventDefault) {
event.preventDefault();
}else {
event.returnValue = false;
}
},
// 阻止事件流
stopPropagation: function(event) {
if(event.stopPropagation) {
event.stopPropagation();
}else {
event.cancelBubble = true;
}
}
}
四、事件类型
- UI事件
- 焦点事件
- 鼠标事件
- 滚轮事件
- 文本事件
- 键盘事件
- 合成事件
- 变动事件
1、UI事件
UI事件事件指的是那些不一定与用户操作有关的事件
现有的UI事件如下:
- load:当页面完全加载后在window上触发
- unload:当页面完全卸载后在window上触发
- abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在object元素上触发
- error:当发生在JavaScript错误时在window上触发...
- select:当用户选择文本框中的一或多个字段时触发
- resize:当窗口或框架的大小变化时在window上触发
- scroll:当用户滚动到滚动条的元素中的内容时触发
这些事件在DOM2级事件中都归为:HTML事件。
要确定浏览器是否支持DOM2级事件,可以使用:document.implementation.hasFeature('HTMLEvents', '2.0')
要确定浏览器是否支持DOM3级事件,可以使用:document.implementation.hasFeature('UIEvent', '3.0')
load事件
有两定义onload事件处理程序的方式:
- 使用JS代码
window.addEventListener('load', function(e){})
- 为body元素添加onlaod属性
<body onload='alert("loaded!")'>
unload事件
与load事件对应的是unload事件,在文档被完全卸载时触发。比如用户从一个页面切换到另一个页面,就会发生unload事件 最多的情况是清除引用,以避免内存泄漏
- 使用JS代码
window.addEventListener('unload', function(e){})
- 为body元素添加onlaod属性
<body onunload='alert("loaded!")'>
resize事件
浏览器窗口变化时触发
- 使用JS代码
window.addEventListener('resize', function(e){})
scroll事件
虽然scroll事件是在window对象上发生的,但他实际表示的则是页面中相应元素的变化
可以通过body元素的scrollLeft和scrollTop来监控
window.addEventListener('scroll', function(e){
if(document.compatMode === 'CSS1Compat') {
console.log(document.documentElement.scrollTop)
}else {
console.log(document.body.scrollTop)
}
})
2、焦点事件
会在页面获得或失去焦点时触发
利用这些事件与document.hasFocus()方法及document.activeElement属性配合。
- blur:在元素失去焦点时触发,不会冒泡
- DOMFocusIn:元素获得焦点时触发,不会冒泡
- DOMFocusOut:元素失去焦点时触发(DOM3级事件中废弃,选择了focusout)
- focus:在元素获得焦点时触发,不会冒泡
- focusin:在元素获得焦点时触发
- focusout:在元素失去焦点时触发
3、鼠标与滚轮事件
鼠标事件:
-
click
-
dblclick:双击鼠标
-
mousedown:用户
按下鼠标触发 -
mouseup:用户
释放鼠标时触发 -
mouseenter:在用户把鼠标光标从
元素外部移到元素内部时触发。(不冒泡) -
mouseleave:在用户把鼠标光标从
元素内部移到元素外部时触发。(不冒泡) -
mousemove:在鼠标光标在
元素上移动时反复触发 -
mouseout:在用户把鼠标光标从
一个元素移到另一个元素上触发 -
mouseover:在用户把鼠标光标从
元素外部移动到元素内部时触发
鼠标事件在DOM3 Events中对应的类型是MouseEvent
滚轮事件:
- mousewheel
1、客户端坐标
- clientX
- clientY
2、页面坐标
- pageX
- pageY
在页面没有滚动时,页面坐标和客户端坐标相同
3、屏幕坐标
- screenX
- screenY
4、修饰键
- shiftKye
- ctrlKey
- altKey
- metaKey
5、鼠标按键
event对象上有button属性:
- 0:鼠标主键
- 1:鼠标滚轮
- 2:鼠标右键
IE8及更早版本提出了button属性,与前面说的完全不同
6、额外事件信息
event对象上提供了detail属性: 包含一个数值,单击一次增加一。
IE还为每个鼠标事件提供了以下额外信息:
- altLeft:左alt键是否按下
- ctrlLeft
- offsetX:光标相对于目标元素边界的X坐标
- offsetY
- shiftLeft
7、mousewheel事件
每次都是滚动120
window.addEventListener('mousewheel', (e) => {
console.log(e.wheelDelta);
})
8、触摸屏设备
iOS和Android不支持鼠标操作
- 不支持dblclick
- 单指点触屏幕的可点击元素(有默认动作的元素)会触发mousemove
- 如果操作会引起内容变化,则不在触发其他事件
- 如果没有引起内容变化,则会相继触发mousedown、mouseup和click事件
- mousemove事件也会触发mouseover和mouseout
- 双指触点屏幕并滑动导致页面滚动时会触发mousewheel和scroll事件
4、键盘与输入事件
- keydown:用户按下键盘上的某一个键触发,而且持续按住会重复触发
- keypress:当用户按下键盘上的字符键时触发。DOM3废弃,而推荐textInput事件
- keyup:用户释放键盘的键时触发
1、键码
keyup事件中的event对象的keyCode属性对应键盘的键。
例如数字键7的keyCode值为55
2、字符编码
要想以跨浏览器的方式取得字符编码,必须首先检测charCode属性是否可用,不可用则使用keyCode
getCharCode: function(event) {
if(typeof event.charCode == 'number') {
return event.charCode
}else {
return event.keyCode
}
}
取得了字符编码后,就可以使用String.fromCharCode()将其转换成实际的字符
3、DOM3变化
键盘事件中,不在包含charCode属性,而是包含两个新属性:key和char
等等等还有一些变化...
4、textInput事件
只有在编辑区域中输入字符,才触发这个事件(删除字符不会触发)
他的event对象中有data属性,他的值就是输入的字符
let input = document.querySelector('input')
input.addEventListener('textInput', (e) => {
console.log(e.data);
})
5、复合事件(合成事件)
用于处理IME输入时的复杂输入序列
- compositionstart
- compositionupdate
- compositionend
6、变动事件(变化事件)
DOM2的变化事件,为了在DOM变化时提供通知。 注意:这些事件已经被废弃~
7、HTML5事件
1、contextmenu事件
自定义上下文菜单事件
2、beforeunload事件
在页面卸载前阻止这一操作(这个事件会在浏览器卸载页面之前触发)
3、DOMContentLoaded事件
用于添加事件处理程序或执行其他DOM操作(在load事件之前触发)
DOMContentLoaded事件会在DOM树构建完毕后立即触发,不用等待图片、JS文件、CSS文件或其他文件加载完成。
对于不支持DOMContentLoaded事件的,可以使用超时为0的setTimeout函数来设置。
4、readystatechange事件
提供文档或元素加载状态的信息
支持该事件的每个对象身上都有一个readyState属性,该属性具有以下可能出行的字符串值:
- uninitialized:对象存在并尚未初始化
- loading:对象正在加载数据
- loaded:对象已经加载完数据
- interactive:对象可以交互,但尚未加载完成
- complete:对象加载完成
5、pageshow和pagehide事件
firefox 和 opera开发了一个名为往返缓存功能
往返缓存:在使用浏览器前进和后退时加快页面之间的切换。不仅存储页面数据,页存储DOM和JS状态。如果页面存在缓存,那么就不会触发load事件
- pageshow
- event.persisted:、
- 默认为false
- true:表示页面在往返缓存中加载的
- event.persisted:、
- pagehide
- event.persisted
- 默认为true:页面在卸载之后被保存在往返缓存中
- true
- event.persisted
6、hashchange事件
用于在URL散列值(URL最后#后边的部分)发生变化时,通知开发者
该事件必须添加给window,他有两个属性:
- oldURL:变化前的URL
- newURL:变化后的URL
想确定当前的散列值,最好用location对象:location.hash
8、设备事件
针对手机、平板计算机
1、orientationchange事件
苹果公司在移动Safari浏览器上创建了orientationchange事件
判断用户的设备是处于垂直模式还是水平模式
window上暴露了window.orientation属性,他有三个值:
- 0:垂直模式
- 90:左转水平模式
- -90:右转水平模式
2、deviceorientation事件
是DeviceOrientationEvent规范定义的事件
获得设置的加速计信息,只反映了设备在空间中的朝向,不涉及移动相关的信息
event有五个属性:
- alpha
- beta
- gamma
- absolute
- compassCalibrated
3、devicemotion事件
是DeviceOrientationEvent规范定义的事件
用于提示设备实际上在移动,而不是仅仅改变了朝向
event有以下属性:
- acceleration:对象,包含xyz属性,在不考虑重力情况下各个维度的加速信息
- accelerationIncludingGravity:对象,包含xyz属性,反映各个维度的加速信息,包含z轴自然重力加速度
- interval:毫秒,距离下次触发devicemotion事件的时间。
- rotationRate:对象包含alpha、beta、gamma属性,表示设备朝向
注意:如果无法提供该属性信息,则他们为null
9、触摸及手势事件
1、触摸事件
触摸事件有以下几种:
- touchstart:手指放到屏幕上时触发
- touchmove:手指在屏幕上滑动时连续触发
- touchend:手指离开屏幕时触发
- touchcancel:系统停止跟踪触摸时触发
以上事件都会冒泡
触摸事件还提供了以下属性用于跟踪触点:
- touches:Touch对象的数组,表示当前屏幕上的每个触点
- targetTouches:Touch对象的数组,表示特定于事件目标的触点
- changedTouches:Touch对象数组,表示自上次用户动作之后变化的触点
每个Touch对象都包含以下属性:
- clientX
- clientY
- identifier:触点ID
- pageX
- pageY
- screenX
- screenY
- target:触摸事件的事件目标
2、手势事件
iOS2.0中的Safari还增加了一种手势事件。
- gesturestart
- gesturechange
- gestureend
五、内存与性能
1、事件委托
原本在元素自身上绑定事件,利用事件冒泡特性可以给他的父元素绑定事件,最终都会由这个函数来处理。
优点:
- document对象随时可用,任何时候都可以给他添加事件处理程序
- 节省花在设置页面事件处理程序上的时间。只指定一个时间处理程序可以节省DOM引用,节省时间
- 减少整个页面所需的内存,提示整体性能
最适合使用事件委托的事件包括:click、mousedown、mouseup、keydown、keypress、mouseover、mouseout
2、删除事件处理程序
事件处理程序越多,页面性能就越差。
btn.onclick = null // 删除事件处理程序
注意:onload事件处理程序中做了什么,最好在unonload事件处理程序中恢复
六、总结
优化内存与性能问题的建议:
- 利用事件冒泡和事件委托来限制事件处理程序数量的问题
- 最好在页面卸载之前删除所有的事件处理程序
- 最好限制一个页面中事件处理程序的数量,他们会占用过多内存,导致页面响应缓慢