JavaScript Event
基础知识
在文档、浏览器、标签元素等元素在特定状态下触发的行为即为事件,比如用户的单击行为、表单内容的改变行为即为事件,我们可以为不同的事件定义处理程序。Js
使用异步事件驱动的形式管理事件。
事件类型
Js
为不同的事件定义的类型,也可以称为事件名称。
事件目标
事件目标指产生事件的对象,比如<a>
标签被点击那么<a>
标签就是事件目标。元素是可以嵌套的,所以在进行一次点击行为时可能会触发多个事件目标。
处理程序
事件的目的是要执行一段代码,我们称这类代码为事件处理(监听)程序。当在对象上触发事件时就会执行定义的事件处理程序。
HTML绑定
可以在html
元素上设置事件处理程序,浏览器解析后会绑定到DOM
属性中
<button onclick="alert('弹窗')">点我出现弹窗</button>
往往事件处理程序业务比较复杂,所以绑定方法或函数会有常见
绑定函数或方法时需要加上括号
<body>
<button onclick="show()">点我出现弹窗</button>
</body>
<script>
function show() {
alert("弹窗");
}
</script>
绑定类的静态方法
<body>
<button onclick="User.show()">点我出现弹窗</button>
</body>
<script>
class User{
static show(){
alert("弹窗");
}
}
</script>
在绑定事件的HTML
的元素上,可以进行参数的传递
this:即事件目标本身
event:事件对象
<body>
<button onclick="show(this,event)">点我</button>
</body>
<script>
function show(self,event){
console.log(self); // <button onclick="show(this,event)">点我</button>
console.log(event); // 包含很多信息
}
</script>
DOM绑定
也可以将事件处理程序绑定到DOM
属性中
使用
setAttribute
方法设置事件处理程序无效属性名区分大小写
使用普通函数进行DOM
绑定时,this
即为事件目标本身
<body>
<button>点我</button>
</body>
<script>
undefined
let button = document.querySelector("button");
button.onclick = function () {
console.log(this) // <button>点我</button>
}
</script>
在使用DOM
绑定时不推荐使用箭头函数,因为这会使this
的指向为window
或undefined
,但是我们可以通过事件对象提取出事件目标本身
<body>
<button>点我</button>
</body>
<script>
let button = document.querySelector("button");
button.onclick = (event)=>{
console.log(event.target) // <button>点我</button>
}
</script>
使用DOM
绑定时不允许对同一事件进行多次处理,只会依照最后的处理程序为准
<body>
<button>点我</button>
</body>
<script>
let button = document.querySelector("button");
button.onclick = function () {
console.log(this); // 不打印
}
button.onclick = function () {
alert("弹窗"); // 打印
}
</script>
事件监听
使用HTML
与DOM
绑定都有缺陷,建议使用新的事件监听绑定方式
方法 | 说明 |
---|---|
addEventListener | 添加事件处理程序 |
removeEventListener | 移除事件处理程序 |
addEventListener
使用addEventListener
添加事件处理程序
transtionend / DOMContentLoaded
等事件类型只能使用addEventListener
处理同一事件类型设置多个事件处理程序,按设置的顺序先后执行
也可以对未来添加的元素绑定事件
参数说明如下
参数 | 说明 |
---|---|
参数1 | 事件类型 |
参数2 | 事件处理程序 |
参数3 | 定制选项 |
参数3的定制项
once:true
:只执行一次事件
capture:true/false
:捕获阶段传播到该EventTarget
时触发
passive:true
:listener
永远不会调用preventDefault()
使用addEventListener
可对同一事件进行多次监听
<body>
<button>点我</button>
</body>
<script>
let button = document.querySelector("button");
button.addEventListener("click", function () {
console.log(this); // 打印
})
button.addEventListener("click",function () {
alert("弹窗"); // 打印
})
</script>
对象绑定
如果事件处理程序可以是对象,对象的 handleEvent
方法会做为事件处理程序执行。下面将元素的事件统一交由对象处理
<body>
<div style="width: 300px;height: 300px;background: red;"></div>
</body>
<script>
class DivEvent {
handleEvent(e){
this[e.type](e.target); // e是事件对象
}
click(self) {
console.log("鼠标点击事件",self); // self即为事件目标 div标签
}
mouseover(self) {
console.log("鼠标移动事件",self);
}
}
let div = document.querySelector("div");
let divEvent = new DivEvent();
div.addEventListener("click",divEvent);
div.addEventListener("mouseover",divEvent);
</script>
removeEventListener
使用removeEventListener
删除绑定的事件处理程序
事件处理程序单独定义函数或方法,这可以保证事件处理程序是同一个
以下示例中,每次点击<div>
都会令其内容+1,当点击删除事件按钮后点击无效。
<body>
<div style="width: 100px;height: 100px;background: red;color: #fff;text-align: center;line-height: 100px;">
</div>
<button>删除事件</button>
</body>
<script>
function add(event) {
if (!event.target.innerText) {
event.target.innerText = 1;
} else {
event.target.innerText++;
}
}
document.querySelector("div").addEventListener("click", add);
document.querySelector("button").addEventListener("click", (event) => {
document.querySelector("div").removeEventListener("click",add)
});
</script>
注意事项
通过HTML
与DOM
进行事件处理程序的绑定,需要在事件名前加入on
,比如click
就变为onclick
,copy
变为oncopy
。
而使用事件监听的方式则不需要加上on
,这与jQuery
的做法如出一辙。
事件对象
执行事件处理程序时,会产生当前事件相关信息的对象,即为事件对象。
系统会自动做为参数传递给事件处理程序。
大部分浏览器将事件对象保存到
window.even
t中有些浏览器会将事件对象做为事件处理程序的参数传递
事件对象常用属性如下:
属性 | 说明 |
---|---|
type | 事件类型 |
target | 事件目标对象,冒泡的父级通过该属性可以找到在哪个元素上执行了事件 |
currentTarget | 当前执行事件的对象 |
timeStamp | 事件发生时间 |
x | 相对窗口的X坐标 |
y | 相对窗口的Y坐标 |
clientX | 相对窗口的X坐标 |
clientY | 相对窗口的Y坐标 |
screenX | 相对计算机屏幕的X坐标 |
screenY | 相对计算机屏幕的Y坐标 |
pageX | 相对于文档的X坐标 |
pageY | 相对于文档的Y坐标 |
offsetX | 相对于事件对象的X坐标 |
offsetY | 相对于事件对象的Y坐标 |
layerX | 相对于父级定位的X坐标 |
layerY | 相对于父级定位的Y坐标 |
path | 冒泡的路径 |
altKey | 是否按了alt键 |
shiftKey | 是否按了shift键 |
metaKey | 是否按了媒体键 |
window.pageXOffset | 文档参考窗口水平滚动的距离 |
window.pageYOffset | 文档参考窗口垂直滚动的距离 |
冒泡捕获
冒泡行为
标签元素是嵌套的,在一个元素上触发的事件,同时也会向上执行父级元素的事件处理程序,一直到HTML
标签元素。
大部分事件都会冒泡,但像
focus
事件则不会
event.target
可以在事件中(包括父级元素中)得到事件目标元素即最底层的产生事件的对象
event.currentTarget == this
即当前执行事件的对象
以下示例有标签的嵌套,并且父子标签都设置了事件,当在子标签上触发事件事会冒泡执行父级标签的事件
简而言之,如果嵌套的标签设置有相同的事件,会按照最外层的标签处理程序为准。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: blueviolet;
}
article {
width: 100px;
height: 100px;
background: deeppink;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.body.addEventListener("click", (evnet) => {
event.target.style.background = "green"; // 爷爷body是绿色
});
document.querySelector("section").addEventListener("click", (event) => {
event.target.style.background = "red"; // 爸爸section是红色
});
document.querySelector("article").addEventListener("click", (event) => {
event.target.style.background = "deepskyblue"; // 孙子deepskyblue是蓝色
});
</script>
</html>
阻止冒泡
冒泡过程中的任何事件处理程序中,都可以执行 event.stopPropagation()
方法阻止继续进行冒泡传递
event.stopPropagation()
用于阻止冒泡,如果同一类型事件绑定多个事件处理程序event.stopPropagation()
只阻止当前的事件处理程序
event.stopImmediatePropagation()
阻止事件冒泡并且阻止相同事件的其他事件处理程序被调用,也就是说不仅仅是当前的事件处理程序不会冒泡了,所有关于该标签的同类型事件处理程序都会阻止冒泡的发生。所以推荐使用
event.stopImmediatePropagation()
为上图添加阻止冒泡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: blueviolet;
}
article {
width: 100px;
height: 100px;
background: deeppink;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.body.addEventListener("click", (evnet) => {
event.target.style.background = "green"; // 爷爷body是绿色
});
document.querySelector("section").addEventListener("click", (event) => {
event.stopImmediatePropagation();
event.target.style.background = "red"; // 爸爸section是红色
});
document.querySelector("article").addEventListener("click", (event) => {
event.stopImmediatePropagation();
event.target.style.background = "deepskyblue"; // 孙子deepskyblue是蓝色
});
</script>
</html>
事件捕获
事件执行顺序为 捕获 > 事件目标 > 冒泡阶段执行,在向下传递到目标对象的过程即为事件捕获。事件捕获在实际使用中频率不高。
通过设置第三个参数为
true
或{ capture: true }
在捕获阶段执行事件处理程序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: blueviolet;
}
article {
width: 100px;
height: 100px;
background: deeppink;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.querySelector("section").addEventListener("click", (event) => {
event.target.style.background = "red"; // 爸爸section是红色
},{ capture: true }); // 捕获阶段执行事件处理程序,这与阻止冒泡并无关系
document.querySelector("article").addEventListener("click", (event) => {
event.target.style.background = "deepskyblue"; // 孙子deepskyblue是蓝色
},{ capture: true }); // 捕获阶段执行事件处理程序,这与阻止冒泡并无关系
</script>
</html>
事件代理
借助冒泡思路,我们可以不为子元素设置事件,而将事件设置在父级。然后通过父级事件对象的event.target
查找子元素,并对他做出处理。
在jQuery
中对事件代理的操作极其简单,但是在原生的JavaScript
中还是有一些复杂的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.show{
list-style: none;
border: 1px solid #ddd;
margin:20px;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
<script>
document.querySelector("ul").addEventListener("click", () => {
if(event.target.tagName === "LI"){ // 如果事件目标是li,则添加样式
event.target.classList.toggle("show");
}
})
</script>
</html>
我们可以将事件代理做成jQuery
那样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
section {
width: 200px;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
outline: 1px solid #ddd;
}
div {
display: flex;
justify-content: center;
align-items: center;
width: 180px;
margin: 10px;
background-color: darkviolet;
color: #fff;
}
</style>
</head>
<body>
<section>
<div>1</div>
<div>2</div>
<div>3</div>
</section>
</body>
<script>
function EventProxy(ProxyElement, ElementType, EventElement, func) {
ProxyElement.addEventListener(ElementType, () => {
if (event.target.tagName == EventElement.toUpperCase()) {
func(event);
}
});
}
let section = document.querySelector("section");
// 委托的DOM 事件类型 事件目标 ele即为事件对象
EventProxy(section, "click", "div", (ele) => {
ele.target.style.backgroundColor = "red";
});
</script>
</html>
模拟出jQuery
的事件代理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
section {
width: 200px;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
outline: 1px solid #ddd;
}
div {
display: flex;
justify-content: center;
align-items: center;
width: 180px;
margin: 10px;
background-color: darkviolet;
color: #fff;
}
</style>
</head>
<body>
<section>
<div>1</div>
<div>2</div>
<div>3</div>
</section>
</body>
<script>
Element.prototype.on = function (ElementType, EventElement, func) {
this.addEventListener(ElementType, () => {
if (event.target.tagName == EventElement.toUpperCase()) {
func(event);
}
});
}
let section = document.querySelector("section");
// 委托的DOM 事件类型 事件目标 ele即为事件对象
section.on("click", "div", (ele) => {
ele.target.style.backgroundColor = "red";
});
</script>
</html>
未来元素
未来元素是指后期通过Js
新增的元素,对于这些元素而言我们可以通过父级事件代理的形式让它们也能进行一些事件的处理,而不用一个一个再去进行事件绑定。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
section {
width: 200px;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
outline: 1px solid #ddd;
}
div {
display: flex;
justify-content: center;
align-items: center;
width: 180px;
margin: 10px;
background-color: darkviolet;
color: #fff;
}
</style>
</head>
<body>
<section>
</section>
</body>
<script>
Element.prototype.on = function (ElementType, EventElement, func) {
this.addEventListener(ElementType, () => {
if (event.target.tagName == EventElement.toUpperCase()) {
func(event);
}
});
}
let section = document.querySelector("section");
let div = document.createElement("div"); // 未来元素,不用绑定任何事件,其父级元素进行事件代理即可
div.innerText = "newElement";
section.append(div); // 添加未来元素
// 委托的DOM 事件类型 事件目标 ele即为事件对象
section.on("click", "div", (ele) => {
ele.target.style.backgroundColor = "red";
});
</script>
</html>
默认行为
Js
会有些对象会设置默认事件处理程序,比如<a>
链接在点击时会进行跳转。一般默认处理程序会在用户定义的处理程序后执行,所以我们可以在我们定义的事件处理程序员取消默认事件处理程序的执行。
使用
onclick
绑定的事件处理程序,return false
可以阻止默认行为推荐使用
event.preventDefault()
阻止默认行为
以下示例将展示使用event.preventDefault()
阻止submit
的默认行为。
<body>
<form action="http://www.google.com">
<p><input type="text" name="username"></p>
<button type="submit">提交</button>
</form>
</body>
<script>
let button = document.querySelector("button");
button.addEventListener("click",(event)=>{
event.preventDefault();
console.log("已阻止默认行为...");
})
</script>
窗口文档
事件类型
事件名 | 说明 |
---|---|
window.onload | 文档解析及外部资源加载后 |
DOMContentLoaded | 文档解析后不需要外部资源加载,只能使用addEventListener 设置 |
window.beforeunload | 文档刷新或关闭时 |
window.unload | 文档卸载时 |
scroll | 页面滚动时 |
实例操作
window.onload
事件在文档解析后及图片、外部样式文件等资源加载完后执行。
推荐代码书写至该事件处理函数中
<script>
window.onload = () => {
console.log("逻辑代码...")
}
</script>
DOMContentLoaded
事件在文档标签解析后执行,不需要等外部图片、样式文件、Js
文件等资源加载
需要等待前面引入的
CSS
样式文件加载解析后才执行只能使用
addEventListener
设置
<script>
window.addEventListener("DOMContentLoaded", () => {
console.log("逻辑代码...")
});
</script>
当浏览器窗口关闭或者刷新时,会触发beforeunload
事件,可以取消关闭或刷新页面。
返回值为非空字符串时,有些浏览器会做为弹出的提示信息内容
部分浏览器使用
addEventListener
无法绑定事件
<script>
window.onbeforeunload = function(e){
return "不要走,10000元宝等您领取!"
};
</script>
window.unload
事件在文档资源被卸载时执行,在beforeunload
后执行
不能执行
alert
、confirm
等交互指令发生错误也不会阻止页面关闭或刷新
<script>
window.addEventListener('unload', function (e) {
localStorage.setItem('name', 'yunya');
});
</script>
鼠标事件
事件类型
针对鼠标操作的行为有多种事件类型
鼠标事件会触发在Z-INDEX最高的那个元素上
事件名 | 说明 |
---|---|
click | 鼠标单击事件,同时触发 mousedown/mouseup |
dblclick | 鼠标双击事件 |
contextmenu | 点击右键后显示的所在环境的菜单 |
mousedown | 鼠标按下(长按) |
mouseup | 鼠标抬起时 |
mousemove | 鼠标移动时 |
mouseover | 鼠标移动时 |
mouseout | 鼠标从元素上离开时 |
mouseup | 鼠标抬起时 |
mouseenter | 鼠标移入时触发,不产生冒泡行为 |
mouseleave | 鼠标移出时触发,不产生冒泡行为 |
copy | 复制内容时触发 |
某一段文本被选中时触发 | |
scroll | 元素滚动时,可以为元素设置overflow:auto; 产生滚动条来测试 |
事件对象
鼠标事件产生的事件对象包含相对应的属性
属性 | 说明 |
---|---|
which | 执行mousedown/mouseup时,显示所按的键 1左键,2中键,3右键 |
clientX | 相对窗口X坐标 |
clientY | 相对窗口Y坐标 |
pageX | 相对于文档的X坐标 |
pageY | 相对于文档的Y坐标 |
offsetX | 目标元素内部的X坐标 |
offsetY | 目标元素内部的Y坐标 |
altKey | 是否按了alt键 |
ctrlKey | 是否按了ctlr键 |
shiftKey | 是否按了shift键 |
metaKey | 是否按了媒体键 |
relatedTarget | mouseover事件时从哪个元素来的,mouseout事件时指要移动到的元素。当无来源(在自身上移动)或移动到窗口外时值为null |
实例操作
禁止复制内容
<body>
abcdefg
</body>
<script>
document.body.addEventListener("copy", event => {
event.preventDefault();
alert("禁止复制");
});
</script>
relatedTarget
是控制鼠标移动事件的来源和目标对象的
如果移动过快会跳转中间对象
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
</body>
<script>
document.body.addEventListener("mouseout", () => {
console.log(event.target); // 显示鼠标在哪个位置上
console.log(event.relatedTarget);
});
</script>
mouseenter
与mouseleave
不会产生冒泡,即子元素和父元素(当前事件对象)来回移动时不产生事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
}
section {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
height: 200px;
border: 1px solid #ddd;
}
article {
width: 100px;
height: 100px;
background-color: darkviolet;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.querySelector("section").addEventListener("mouseenter", (event) => {
event.target.style.backgroundColor = "red";
});
document.querySelector("article").addEventListener("mouseenter", (event) => {
event.target.style.backgroundColor = "yellow";
});
</script>
</html>
键盘事件
事件类型
针对键盘输入操作的行为有多种事件类型
事件名 | 说明 |
---|---|
Keydown | 键盘按下时,一直按键不松开时keydown事件会重复触发 |
keypress | 某个键盘按键被按下并且松开 |
keyup | 按键抬起时 |
事件对象
键盘事件产生的事件对象包含相对应的属性
属性 | 说明 |
---|---|
keyCode | 返回键盘的ASCII字符数字 |
code | 按键码,字符以Key开始,数字以Digit开始,特殊字符有专属名子。左右ALT键字符不同。 不同布局的键盘值会不同 |
key | 按键的字符含义表示,大小写不同。不能区分左右ALT等。不同语言操作系统下值会不同 |
altKey | 是否按了alt键 |
ctrlKey | 是否按了ctlr键 |
shiftKey | 是否按了shift键 |
metaKey | 是否按了媒体键 |
表单事件
对于表单元素来说,可有以下事件提供处理
事件类型 | 说明 |
---|---|
focus | 获取焦点事件 |
blur | 失去焦点事件 |
element.focus() | 让元素强制获取焦点 |
element.blur() | 让元素失去焦点 |
change | 文本框在内容发生改变并失去焦点时触发,select/checkbox/radio选项改变时触发事件 |
input | 内容改变时触发,包括粘贴内容或语音输入内容都会触发事件 |
submit | 确认按钮被点击,提交表单时触发 |