事件
事件流
三个阶段
- 事件捕获;
- 到达目标;
- 事件冒泡;
事件捕获
- 事件流从 document 触发;
- 依次向下传播至触发节点;
到达目标
- 事件流到达触发节点;
- 从事件捕获转向事件冒泡;
事件冒泡
- 事件流从最深的节点触发;
- 依次向上传播;
事件处理程序
HTML 事件处理程序
语法格式
<input type="button" value="Click Me" onclick="console.log('Clicked')" />
调用脚本
- 全局作用域;
- this 指向元素本身;
<input type="button" value="Click Me" onclick="showMessage()"/>
<input type="button" value="Click Me" onclick="console.log(this.value)"> // Click me
触发时机
- 在冒泡阶段处理;
DOM0 事件处理程序
语法格式
element.event = ()=>{...}
;- 作用域为元素所在作用域;
let btn = document.getElementById("myBtn");
btn.onclick = function () {
console.log(this.id); // "myBtn"
};
btn.onclick = null;
触发时机
- 在冒泡阶段处理;
DOM2 事件处理程序
创建事件处理程序
- element.addEventListener(event,fn,useCapture);
- useCapture:false 为默认值,表示冒泡阶段处理事件,true 则为捕获阶段;
- 一个事件可以添加多个事件处理程序;
- 根据添加顺序触发;
let btn = document.getElementById("myBtn");
btn.addEventListener(
"click",
() => {
console.log(this.id);
},
false
);
let btn = document.getElementById("myBtn");
btn.addEventListener(
"click",
() => {
console.log(this.id);
},
false
);
btn.addEventListener(
"click",
() => {
console.log("Hello world!");
},
false
);
移除事件处理程序
- element.removeEventListener(event,fn,useCapture);
- addEventListener 添加的事件只能通过 removeEventListener 移除,且参数相同,意味着匿名函数无法移除;
let btn = document.getElementById("myBtn");
let handler = function () {
console.log(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false); // 有效果!
触发时机
- 默认在冒泡阶段触发;
- 可通过过 useCapture 参数设置捕获阶段触发;
触发一次
- 设置 once 为 true;
button.addEventListener(
"click",
() => {
console.log("clicked!");
},
{ once: true }
);
事件对象
event
let btn = document.getElementById("myBtn");
btn.onclick = function (event) {
console.log(event.type); // "click"
};
btn.addEventListener(
"click",
(event) => {
console.log(event.type); // "click"
},
false
);
target 和 currentTarget 属性
- target 为引起事件的元素;
- currentTarget 为处理事件时所在元素;
- this 对象始终等于 currentTarget;
document.body.onclick = function (event) {
// body 为处理事件时所在元素
console.log(event.currentTarget === document.body); // true
console.log(this === document.body); // true
// button 引起的 click 事件, 但 button 无 click 事件处理程序, 冒泡到 body 上处理
console.log(event.target === document.getElementById("myBtn")); // true
};
type 属性
- 标记触发事件类型;;
let btn = document.getElementById("myBtn");
let handler = function (event) {
switch (event.type) {
case "click":
console.log("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
preventDefault() 方法
- 阻止特定事件的默认动作;
let link = document.getElementById("myLink");
link.onclick = function (event) {
event.preventDefault();
};
stopPropagation() 方法
- 阻止事件流在 DOM 结构中传播,取消后续捕获或冒泡;
let btn = document.getElementById("myBtn");
btn.onclick = function (event) {
console.log("Clicked");
event.stopPropagation();
};
document.body.onclick = function (event) {
console.log("Body clicked");
};
eventPhase 属性
- 确定事件流当前所处的阶段;
let btn = document.getElementById("myBtn");
// 到达目标
btn.onclick = function (event) {
console.log(event.eventPhase); // 2
};
// 捕获阶段
document.body.addEventListener(
"click",
(event) => {
console.log(event.eventPhase); // 1
},
true
);
// 冒泡阶段
document.body.onclick = (event) => {
console.log(event.eventPhase); // 3
};
事件类型
用户界面事件
- load 事件:整个页面加载完成后于 windows 对象触发,对应 body 标签 onload 属性;
- unload 事件:文档卸载后于 windows 对象触发,对应 body 标签 onunload 属性;
- resize 事件:浏览器缩放于 windows 对象触发,对应 body 标签 onresize 属性;
- scroll 事件:元素滚动于 windows 对象触发,对应 body 标签 onscroll 属性;
焦点事件
- focus 事件:元素获得焦点触发,不冒泡;
- focusin 事件:元素获得焦点触发,冒泡;
- blur 事件:元素失去焦点触发,不冒泡;
- focusout 事件:元素失去焦点触发,冒泡;
html5 事件
- contextmenu 事件:显示菜单栏触发,冒泡;
- beforeunload 事件:在页面卸载前于 window 对象触发;
- DOMContentLoaded 事件:在 DOM 树构建完成后于 window/document 对象触发;
- pageshow 事件:load 事件之后,页面显示时于 window 触发;
- pagehide 事件:页面卸载后,unload 前于 window 触发;
- hashchange 事件:URL hash 值改变后于 window 触发;
鼠标事件
常见事件
- click 事件:点击鼠标左键或回车键触发,冒泡,对应 onclick 属性;
- dblclick 事件:双击鼠标左键触发,冒泡;
- mousedown 事件:按下任意鼠标键触发,冒泡;
- mouseenter 事件:鼠标从元素外到元素内触发,不冒泡;
- mouseleave 事件:鼠标从元素内到元素外触发,不冒泡;
- mousemove 事件:鼠标从元素删移动触发,不冒泡;
- mouseover 事件:鼠标从元素外到元素内触发,冒泡;
- mouseout 事件:鼠标从元素内到元素外触发,冒泡;
- mouseup 事件:释放鼠标键触发,冒泡;
- mousewheel 事件:鼠标滚动触发;
- wheelDelta 表示鼠标滚动数值;
客户端坐标
- clientXY:相对于视口的坐标;
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`Client coordinates: ${event.clientX}, ${event.clientY}`);
});
页面坐标
- pageXY:相对于页面的坐标;
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`Page coordinates: ${event.pageX}, ${event.pageY}`);
});
客户端坐标和页面坐标的关系
- 未滚动时,两者相同;
- 滚动时,客户端坐标 + body.scrollXXX = 页面坐标;
屏幕坐标
- screenXY:相对于屏幕的坐标;
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`Screen coordinates: ${event.screenX}, ${event.screenY}`);
});
修饰键
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
let keys = new Array();
if (event.shiftKey) {
keys.push("shift");
}
if (event.ctrlKey) {
keys.push("ctrl");
}
if (event.altKey) {
keys.push("alt");
}
if (event.metaKey) {
keys.push("meta");
}
console.log("Keys: " + keys.join(","));
});
相关元素
- 针对 mouseover 和 mouseout 事件;
- event.relatedTarge 属性指向相关元素;
鼠标按键
- event.button 属性;
- 0 鼠标左键,1 鼠标中键,2 鼠标右键;
键盘事件
常见事件
- keydown 事件:按键触发,持续按键重复触发;
- keypress 事件:按键并产生字符触发,持续按键重复触发;
- keyup 事件:释放按键触发;
键码
- keyCode 属性存储键盘按键键码;
let textbox = document.getElementById("myText");
textbox.addEventListener("keyup", (event) => {
console.log(event.keyCode);
});
字符编码
- charCode 属性存储对应按键字符编码;
let textbox = document.getElementById("myText");
textbox.addEventListener("keypress", (event) => {
console.log(event.charCode);
});
textInput 事件
- 在可编辑区域且新字符插入时触发;
let textbox = document.getElementById("myText");
textbox.addEventListener("textInput", (event) => {
console.log(event.data);
});
内存与性能
事件委托
事件委托
- 利用事件冒泡;
- 基于 type 属性 和 switch/if 语句;
- 使用一个事件处理程序管理一类事件;
let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => {
let target = event.target;
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":
console.log("hi");
break;
}
});
适合事件
- click;
- mousedown;
- mouseup;
- keydown;
- keypress;
- keyup;
优点
- 减少页面所需内存;
- 动态绑定,减少代码量;
删除事件处理程序
原因
- 删除节点其事件处理程序不一定被垃圾回收;
- 页面卸载其事件处理程序不一定被垃圾回收;
删除方法
- 手工删除事件处理程序;
模拟事件
模拟鼠标事件
let btn = document.getElementById("myBtn");
// 创建 event 对象
let event = document.createEvent("MouseEvents");
// 初始化 event 对象
event.initMouseEvent(
"click",
true,
true,
document.defaultView,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
// 触发事件
btn.dispatchEvent(event);
模拟键盘事件
let textbox = document.getElementById("myTextbox"),
event;
// 按照 DOM3 的方式创建 event 对象
if (document.implementation.hasFeature("KeyboardEvents", "3.0")) {
event = document.createEvent("KeyboardEvent");
// 初始化 event 对象
event.initKeyboardEvent(
"keydown",
true,
true,
document.defaultView,
"a",
0,
"Shift",
0
);
}
// 触发事件
textbox.dispatchEvent(event);
模拟其他事件
let event = document.createEvent("HTMLEvents");
event.initEvent("focus", true, false);
target.dispatchEvent(event);
自定义事件
// DOM3
let div = document.getElementById("myDiv"),
event;
div.addEventListener("myevent", (event) => {
console.log("DIV: " + event.detail);
});
document.addEventListener("myevent", (event) => {
console.log("DOCUMENT: " + event.detail);
});
if (document.implementation.hasFeature("CustomEvents", "3.0")) {
event = document.createEvent("CustomEvent");
event.initCustomEvent("myevent", true, false, "Hello world!");
div.dispatchEvent(event);
}
最佳实践
优化 scroll
- 通过 IntersectionObserve API 替换 scroll 事件;
节流和防抖
节流
- 固定时间段,最多只能运行一次;
const throttle = (fun, wait) => {
let timeoutID = null;
return function () {
const context = this;
const args = arguments;
if (timeoutID) return;
timeoutID = setTimeout(() => {
fun.apply(context, args);
timeoutID = null;
}, wait);
};
};
防抖
- 固定时间段内,只有最后一次操作被执行;
const debounce = (fun, wait) => {
let timeoutID = null;
return function () {
const context = this;
const args = arguments;
if (timeoutID) clearTimeout(timeoutID);
timeoutID = setTimeout(function () {
fun.apply(context, args);
}, wait);
};
};