JavaScript与HTML之间的交互是通过事件实现的!
具体的实现是通过监听器预定事件,然后事件发生时候执行对应的代码! 在软件工程中被称为观察者模式的模型!
01|事件流
-
事件冒泡:IE事件流 开始由最具体的元素接受逐级向上传播到不具体的节点! 由子到父
-
事件捕获:Netscape事件流 不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件 由父到子
-
DOM事件流:规定的事件流有三个阶段
- 事件捕获阶段
- 事件的目标阶段
- 事件的冒泡阶段
事件捕获(截获事件) ===> 对应的目标接收到事件 ===> 冒泡阶段(在现阶段对事件做出响应)
02|事件处理程序
响应某个事件的函数就叫做 事件处理程序 事件处理程序一般以 "on"开头!
<input type="button" value="click me!" onclick="showMessage()" />
<script>
let showMessage = ()=>{console.log("Hello World!");}
</script>
其中可以通过event变量,直接访问事件对象!
- DOM0级别事件处理程序
let button = document.getElementById("mybtn");
button.onclick = function(){
console.log(this.id);
}
DOM0 级方法指定的事件处理程序被认为是元素的方法,这时候的事件处理程序是在元素的作用域中运行的!
- DOM2级别事件处理程序
DOM2级别事件处理程序中指定了两个方法处理指定和删除事件处理程序的操作:
- addEventListener
- removeEventListener
对应的两个方法分别接受三个参数:
- 事件名
- 事件处理程序
- 布尔值:boolean值 表示捕获阶段
- true 捕获阶段调用事件处理程序
- false 冒泡阶段调用事件处理程序
其实也是依附于元素的作用域中进行的! 并且添加事件监听器(addEventListener)可以
添加多个事件处理程序!
let button = document.getElementById("myBtn");
button.addEventListener('click',function(){
console.log(this.id);
});
button.addEventListener('click',function(){
console.log("hello Jerry!")
});
删除事件监听器:
let button = document.getElementById("myBtn");
button.addEventListener('click',function(){
console.log(this.id);
});
/*
xxxx
*/
button.removeEventListener("click",function(){
console.log(this.id);
})
//无效
因为对应的 事件处理函数看似相同 其实并不是同一个函数!(匿名函数)
正确做法如下所示:
let button = document.getElementById("myBtn");
const handle = function(){
console.log(this.id);
}
button.addEventListener('click',handle);
button.removeEventListener("click",handle);
其实大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段! 因为可以最大限度的兼容各种浏览器
03|IE事件处理程序
IE中实现了与DOM类中相似的两个方法:
- attachEvent
- detachEvent
对应的两个方法分别接收两个参数:
- 事件处理程序名称 一般都是on开头的事件处理程序名
- 事件处理程序
同样的使用IE事件处理程序可以对同一个事件添加多个事件处理函数!
看起来好像DOM0级别的事件处理程序,有什么不同吗?
其中最大的不同就是事件处理程序的作用域不同!
let button = document.getElementById("myBtn");
const handle = function(){
console.log(this);
}
button.addEventListener('click',handle);//button
button.attchEvent('onclick',handle);//window
04|跨浏览器的事件处理程序
其实就是通过浏览器的不同写出兼容不同浏览器的添加事件处理器的程序函数!
const EventUtil = {
addHandler(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if(element.attchEvent){
element.attchEvent(`on${type}`,handler);
}else{
element[`on${type}`] = handler;
}
},removeHandler(element,type,handler){
if(element.addEventListener){
element.removeEventListener(type,handler,false);
}else if(element.attchEvent){
element.detachEvent(`on${type}`,handler);
}else{
element[`on${type}`] = handler;
}
}
}
05|事件对象
1. 属性或方法
type // 被触发的事件类型
target // 事件的目标
currentTarget // 事件处理程序当前正在处理事件的那个元素
注: 在事件处理程序内部,对象this始终等于currentTarget的值,而target只包含事件的实际目标
*** 一个函数处理多个事件可以使用switch(event.type)的方式
event.preventDefault() // 阻止事件的默认行为
event.stopPropagation() // 阻止事件冒泡
属性/方法 | 类型 | 读写 | 说明 |
---|---|---|---|
bubbles | Boolean | 只读 | 表明事件是否冒泡 |
cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 |
currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素 |
defaultPrevented | Boolean | 只读 | 为 true 表示已经调用了 preventDefault()(DOM3级事件中新增) |
detail | Integer | 只读 | 与事件相关的细节信息 |
eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段 |
preventDefault() | Function | 只读 | 取消事件的默认行为。如果cancelable是true,则可以使用这个方法 |
stopImmediatePropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3级事件中新增) |
stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 |
target | Element | 只读 | 事件的目标 |
trusted | Boolean | 只读 | 为true表示事件是浏览器生成的。为false表示事件是由开发人员通过 JavaScript 创建的(DOM3级事件中新增) |
type | String | 只读 | 被触发的事件的类型 |
view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 |
其中有一个属性:event.eventPhase
可以用来确定当前事件位于事件流的哪个阶段!
- eventPhase:1 捕获阶段
- eventPhase:2 目标对象
- eventPhase:3 冒泡阶段
属性/方法 | 类型 | 读写 | 说明 |
---|---|---|---|
cancelBubble | Boolean | 读/写 | 默认值为false,但将其设置为true就可以取消事件冒泡(与DOM中的stopPropagation()方法的作用相同) |
returnValue | Boolean | 读/写 | 默认值为true,但将其设置为false就可以取消事件的默认行为(与DOM中的preventDefault()方法的作用相同) |
srcElement | Element | 只读 | 事件的目标(与DOM中的target属性相同) |
type | String | 只读 | 被触发的事件的类型 |
06|跨浏览器的事件对象
const EventUtil = {
addHandler(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if(element.attchEvent){
element.attchEvent(`on${type}`,handler);
}else{
element[`on${type}`] = handler;
}
},removeHandler(element,type,handler){
if(element.addEventListener){
element.removeEventListener(type,handler,false);
}else if(element.attchEvent){
element.detachEvent(`on${type}`,handler);
}else{
element[`on${type}`] = handler;
}
}
}
添加的部分如下所示:
const EventUtil = {
getEvent(event){
return event?event:window.event;
},getTarget(event){
return event.target || event.srcElement;
},preventDefault(event){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue = false;
}
},stopPropagation(event){
if(event.stopPropgation){
event.stopPropagation()
}else{
event.cancelBubble = true;
}
}
}
07|事件类型
其中JavaScript高程中讲到的事件类型如下所示:
- UI事件
- 焦点事件
- 鼠标与滚轮事件
- 键盘与文本事件
- 复合事件
- 变动事件
- HTML5事件
- 设备事件
- 触摸与手势事件
/*
1.鼠标和滚轮事件
1.客户区坐标位置clientX/clientY //表示事件发生时鼠标指针在视口中的水平和垂直位置
2.页面坐标位置 pageX/pageY //表示事件在页面中发生的位置
3.屏幕坐标位置 //获取事件发生时在屏幕中的位置
*/
/*
2.修改键(如果用户在触发事件时按下了shift/ctrl/alt/Meta,则返回true)
event.shiftkey | event.altKey | event.metaKey | event.ctrlKey
*/
/*
3.鼠标按钮(event.button)
对于mousedown和mouseup,其event中存在一个button属性,值为0表示主鼠标按钮,1表示中间鼠标按钮,2表示次鼠标按钮
*/
/*
4.鼠标滚轮事件(mousewheel)
1.兼容方案:
*/
let getWheelDelta = function(event){
let wheelDelta = event.wheelDelta ? event.wheelDelta : (-event.detail * 40);
return wheelDelta;
}
//注:document在普通浏览器中通过mousewheel监听鼠标滚轮事件,在火狐中使用DOMMouseScroll监听
5.键盘与文本事件
6.变动事件
1.DOMSubtreeModified | DOMNodeInserted | DOMNodeRemoved
例子
el.addEvent("DOMSubtreeModified", fn1)
7.HTML5事件
1.contextmenu事件(自定义上下文菜单)
2.DOMContentLoaded事件(在形成完整dom树之后就触发,不理会图像,js文件,css文件等资源是否下载完成)
3.hashchange事件(在URL的参数列表发生变化【即#号后面的所有字符串】时触发)
注:必须要把hashchange添加给window对象,event对象包含两个属性oldURL和newURL,分别保存着参数列表变化前后的完整URL
window.addEvent("hashchange", function(event){
// oldURL和newURL存在兼容问题,最好用location.hash代替
console.log(event.oldURL, event.newURL);
})
08|内存与性能
如果在页面写在之前没有清理干净事件处理程序,那他们就会滞留在内存中,每次加载完页面再卸载时,内存中滞留的对象就会增加,因为事件处理程序占用的内存并没有被释放。
【解决方案】再页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。但是使用onunload时页面不会被缓存bfcache(即往返缓存)中。
- 事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
通过对应的代码讲解:
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
<script>
let item1 = document.getElementById("goSomewhere");
let item2 = document.getElementById("doSomething");
let item3 = document.getElementById("sayHi");
EventUtil.addHandler(item1, "click", function(event){
location.href = "http://www.wrox.com";
});
EventUtil.addHandler(item2, "click", function(event){
document.title = "I changed the document's title";
});
EventUtil.addHandler(item3, "click", function(event){
alert("hi");
});
</script>
其中关于添加事件处理器的代码重复了,到时候如果需要卸载的话卸载起来比较麻烦!
我们接下来使用事件委托的方式进行解决!
let list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id){
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.href = "http://www.wrox.com";
break;
case "sayHi":
alert("hi");
break;
}
});
- 移除事件处理程序
在编写对应的事件处理程序的过程中,讲函数引用改为空!
let button = document.getElementById("mybutton");
button.onclick = ()=>{
//TODO
button.onclick = null;//移出事件处理程序
}
网友评论