JavaScript和HTML的交互是通过事件实现的
可以用仅仅在事件发生时执行的监听器订阅事件
17.1 事件流
事件流描述了页面接收事件的顺序
17.1.1 事件冒泡
IE事件流被称为事件冒泡
当事件触发后,会从被点击的元素一直沿着DOM树往上爬,直到document对象为止
17.1.2 事件捕获
还有一种事件流叫事件
当事件触发后,会从document对象一直沿着DOM树往下,直到被点击的元素为止
17.1.3 DOM事件流
事件流分为3个阶段:
事件捕获 到达目标 事件冒泡
先向下到达事件,再回到document
17.2 事件处理程序
为响应事件而调用的函数被称为事件处理程序(监听器)
监听器的名字以"on"开头 onclick,onload...
17.2.1 HTML事件处理程序
特定元素的每个事件都可以使用事件处理程序的名字,以HTML的形式来指定
<body>
<button onclick="showMsg()">click me</button>
<script>
function showMsg(){
console.log("I was clicked");
}
</script>
</body>
这种方式会创建一个函数封装属性的值,产生一个特殊的局部变量event
document和元素自身的成员都可以当成局部变量访问到
17.2.2 DOM0事件处理程序
每个元素都有事件程序属性,给这个值赋函数即可
<body>
<button class="myBth">click me</button>
<script>
let btn = document.querySelector('.myBth')
btn.onclick = function(e){
console.log(e);
}
</script>
17.2.3 DOM2事件处理程序
定义了两个方法:addEventListener() 和 removeEventListener()
接收三个参数:
事件名
事件处理函数
布尔值: true代表在捕获阶段指向,false代表在冒泡阶段调用
<body>
<button class="myBtn">click me</button>
<script>
let btn = document.querySelector('.myBtn')
btn.addEventListener(
'click',
(e) => {
console.log(e)
},
true
)
</script>
</body>
removeEventListener()则需要使用和addEventListener()一样的参数来移除
这意味着向上面一样使用匿名函数创建的addEventListener()无法被移除
但是像下面的例子就有效
<body>
<button class="myBth">click me</button>
<script>
let btn = document.querySelector('.myBth')
let handler = function (e) {
console.log(e)
}
btn.addEventListener('click', handler, true)
btn.removeEventListener('click', handler, true)
</script>
</body>
一般情况下,推荐在冒泡阶段进行程序捕获,因为兼容性好
17.2.4 IE事件处理程序
attachEvent() 和 detachEvent() 现在好像用不了了
17.2.5 跨浏览器事件处理程序
可以封装一个如下类:支持DOM0,DOM2和IE浏览器的事件处理程序
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false)
} else if (element.attachEvent()) {
element.attachEvent('on' + type, handler)
} else {
element['on' + type] = handler
}
},
removeHandler: function (element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false)
} else if (element.detachEvent()) {
element.detachEvent('on' + type, handler)
} else {
element['on' + type] = null
}
}
}
17.3 事件对象
再DOM中发生事件时,所有相关信息都会被收集并且放在一个evevt对象中
17.3.1 DOM事件对象
event对象是传给事件处理程序的唯一参数.不管以哪种方式指定,都会传入
<body>
<button class="myBtn">click me</button>
<script>
let btn = document.querySelector('.myBtn')
btn.onclick = function(e){
console.log(e.type);
}
btn.addEventListener('click',(e)=>{console.log(e.type);},false)
</script>
</body>
event的实行和方法:p499
在处理程序内部
this对象 = currentTarget
target只包含事件的实际目标
preventDefault()阻止事件默认动作,如<a>的跳转
stopPropagation()阻止时间在DOM流中传播
eventPhase可以用来确定事件流所处阶段
17.3.2 IE事件对象
IE事件对象只是window对象的一个属性
17.4 事件类型
DOM3定义了如下事件类型:
用户界面事件 UIEvent
焦点事件 FocusEvent
鼠标事件 MouseEvent
滚轮事件 WhellEvent
输入事件 InputEvent
键盘事件 KeyboardEvent
合成事件 CompositionEvent
17.4.1 用户界面事件
load 加载完成时触发
unload 完全卸载后触发
abort <object>被用户终止下载时触发
select 在文本框上选择了一个或多个字符时触发
resize 窗口被缩放时触发
scroll 滚动滚动条时触发
大多数HTML事件和window对象和表单控件有关
17.4.2 焦点事件
blur 失去焦点触发 不冒泡
focus 获得焦点触发 不冒泡
focusin 获得焦点时触发 focus的冒泡版
focusout 失去焦点时触发blur的冒泡版
17.4.3 鼠标和滚轮事件
click 左键单击或键盘回车
dbclick 左键双击
mousedown 任意鼠标键
mouseenter 光标从元素外移到元素内 不冒泡
mouseleave 光标从元素内移到元素外 不冒泡
mousemove 光标在元素上移动时反复触发
mouseout 光标从一个到另一个元素时触发
mouseover 光标从元素外部到元素内部时触发
mouseup 释放鼠标按键时触发
客户端坐标
事件对象event中的clientX和clientY表示事件发生时鼠标在视口中的坐标
事件对象event中的pageX和pageY表示事件发生时鼠标在页面中的坐标
事件对象event中的screenX和screenY表示事件发生时鼠标在屏幕中的坐标
修饰键
event对象中的下面四个属性:
shiftKey
ctrlKey
altKey
metaKey
分别对应键盘的四个按键
相关元素
mouseover和mouseout存在事件相关的其他元素
mouseover主要目标是获得光标的元素,相关目标是失去光标的元素
mouseout则相反
鼠标按键
event对象会有一个button属性,
0表示左键 1表示中键 2表示右键
额外事件信息
event上定义了detail属性,表示在给定位置上的单击次数
如果鼠标在mousedown和mouseup之间移动了,则会清零
mousewheel事件
mousewheel的event对象中有一个wheelData的新属性,通过其正负可以知道滚轮的滚动方向
触摸屏设备
不支持dbclick
单指点可以触发mousemove事件
mousemove也会触发mouseover和mouseout
双指点屏幕并滑动会触发mousewheel和scroll
17.4.4 键盘与输入事件
keydown 按下键盘触发,并且一直按住会一直触发
keypress 废弃
keyup 抬起键盘时触发
textInput是对keypress的扩展,textInput会在文本被插入到文本框之前触发
1.键码
在keyup和keydown事件中
在event的keyCode中会保存一个键码,和小写的ASCLL码一致
2.字符编码
event上设置了charCode属性,只要发生了keypress事件这个属性才会被设置值,包含的时按键字符对应的ASCLL编码
一旦有了字符编码,就可以用String.fromCharCode()把其转化为实际的字符
3.DOM3的变化
DOM3未定义charCode,而是定义了key和char
key的值为文本字符/键名
char属性在按下非字符时为null
4.textInput
textInput只在可编辑节点上触发,而且只有新增字符时才触发(删除时不触发)
textInput的event事件上有data属性,始终包含输入的字符(不是编码)
还有inputMethod方法,表示输入的手段 p521
17.4.5 合成事件
合成事件时DOM3新增的,用于处理IME输入时的复杂输入序列
合成事件有以下三种:
compositionstart IME文本系统打开时触发,表示输入即将开始
compositionupdate 在新字符插入输入字段时触发
compositionend IME文本合成系统关闭时触发,表示恢复正常键盘
事件目标是接收文本的输入字段,唯一增加的属性是data
17.4.6 变化事件
变化事件已经被Mutation Observers取代
17.4.7 HTML5事件
DOM规范并未涵盖所有浏览器支持的事件,下面列出了H5得到了浏览器较好支持的一些事件
1.contextmenu事件
自定义菜单
window.addEventListener('load', (e) => {
let div = document.querySelector('#myDiv')
div.addEventListener('contextmenu', (e) => {
e.preventDefault() //防止默认菜单弹出
let menu = document.getElementById('myMenu')
menu.style.left = e.clientX + 'px'
menu.style.top = e.clientY + 'px'
menu.style.visibility = 'visible'
})
document.addEventListener('click', (e) => {
document.getElementById('myMenu').style.visibility = 'hidden'
})
})
2.beforeunload
给开发者提供阻止页面被卸载的机会
事件会向用户显示一个确认框
3.DOMContentLoaded事件
DOMContentLoaded事件会在DOM树构建完成后立即触发,因此DOMContentLoaded事件可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能更快的与页面交互
4.readystatechange事件
提供文档或元素加载状态的信息
每个对象都有一个readyState属性:
uninitialized: 对象没有初始化
loading: 对象正在加载数据
loaded: 对象已经加载完数据
interactive: 对象可以交互,但是没有加载完成
complete: 对象加载完成
5.pageshow和pagehide事件
处理程序添加给window
往返缓存,在前进和后退时加快速度,实际上是吧整个页面都存在内存里
如果页面来自内存,就不会触发load事件
pageshow会在页面显示时触发,无论是否来自缓存
事件对象中persisted属性可以检测页面有无缓存
pageshow会在页面被卸载时触发
事件对象中persisted属性表示页面在卸载之后会不会被保存在往返缓存中
6.hashchange事件
当URL散列值发生变化时通知
17.4.8 设备事件
设备事件可以确定用户使用设备的方式
1.orientationchange事件
表示用户当前处于竖放还是平放
2.deviceorientation事件
获取用户加速计信息
3.devicemotion事件
用于提示设备实际上在移动
17.4.9 触摸以及手势事件
1.触摸事件
touchstart: 手指放到了屏幕上
touchmove: 手指在屏幕上滑动时连续触发 可以用preventDefault()阻止滚动
touchend: 手指从屏幕移开时触发
touchcancel: 系统停止跟踪触摸时触发
2.手势事件
gesturestart: 一个手指已经放到屏幕上,再把另一个手指放上去时触发
gesturechange: 任何一个手指在屏幕上的位置变化时触发
gestureend: 任何一个手指离开屏幕时触发
17.4.10 事件参考
p535
17.5 事件性能
在JavaScript中,页面中事件处理程序的数量与页面整体性能直接相关
可以使用下面的方法改善页面性能:
17.5.1 事件委托
事件委托利用冒泡,可以只使用一个事件处理程序来管理一种类型的事件
这意味着可以为整一个页面指定一个onclick,而不用为每个元素都指定一个
let list = document.getElementById('myLinks')
list.addEventListener('click', (e) => {
let target = e.target
switch (target.id) {
case 'doSomething':
document.title = 'title changed'
break
case 'goSomewhere':
location.href = 'url'
break
case 'sayhi':
console.log('hi')
break
}
})
17.5.2 删除事件处理程序
及时删除事件处理程序可以有效提高性能
无用事件长驻内存会导致性能不佳
原因有两个:
1.删除了带有事件处理程序的节点
在删除之前注销程序
let btn = document.getElementById("myBtn")
btn.onclick = function(){
//删除事件
btn.onclick = null
document.getElementById("myBtn").innerText = "Processing ... "
}
2.页面残留
页面卸载之后如果事件处理程序没有被清理,则还会残留在内存中
最好在onunload事件中删除所有事件
17.6 模拟事件
通过JavaScript可以任何时候触发任意事件
p543