JS事件
事件最重要的概念就是:事件源,事件名,监听器,处理程序
01 事件流
就是事件传播,描述的是从页面中接收事件的顺序
如果单击了某个按钮,它们认为单击事件不仅仅发生在按钮上,甚至单击了整个页面
IE事件流是事件冒泡流
Netscape是事件捕获流
其实不同的就是事件的触发时机,是在捕获阶段触发事件,还是在冒泡阶段触发事件
02 事件冒泡和事件捕获的概念
事件冒泡:事件由当前最具体的元素接收,然后逐级向上传播到较为不具体的节点(文档document)
html
<div id="box"></div>
// 点击box,点击事件会向上传播,box的每一层父级都会触发它们身上的点击事件
<script>
const box = document.getElementById('box');
box.onclick = function () {
box.innerHTML += 'div<br>';
}
// 获取body
document.body.onclick = function () {
box.innerHTML += 'body<br>';
}
// 获取html
document.documentElement.onclick = function () {
box.innerHTML += 'html<br>';
}
// 获取document
document.onclick = function () {
box.innerHTML += 'document<br>';
}
// 获取window
window.onclick = function () {
box.innerHTML += 'window<br>';
}
</script>
事件捕获:事件由不太具体的节点,更早的接收事件,而最具体的节点应该最后接收到事件(逐级向下传播)
html
<div id="box"></div>
<script>
const box = document.getElementById('box')
box.addEventListener('click', function () {
box.innerHTML += 'box<br>'
},true)
// addEventListener的第三个参数用来控制事件触发时机,在捕获阶段(true)还是在冒泡阶段(false)这个参数还可以时一个对象
/*
{
capture: true, // 是否捕获阶段执行
once: true, // 是否只执行一次
passive: true // 是否阻止preventDefault
}
*/
document.body.addEventListener('click', function () {
box.innerHTML += 'body<br>'
},true)
document.documentElement.addEventListener('click', function () {
box.innerHTML += 'html<br>'
},true)
document.addEventListener('click', function () {
box.innerHTML += 'document<br>'
},true)
window.addEventListener('click', function () {
box.innerHTML += 'window<br>'
},true)
</script>
03 三种事件处理程序的绑定方式
关于三种方式的this指向问题
html
<body>
<button class="btn">Click Me</button>
<script>
const btn = document.querySelector('.btn')
// btn.onclick = function () {
// console.log(this) // 当前dom对象
// }
// btn.onclick = () => {
// console.log(this) // window
// }
// btn.addEventListener('click',function() {
// console.log(this) // 当前dom对象
// })
btn.addEventListener('click',() => {
console.log(this) // window
})
</script>
</body>
1、HTML事件处理程序
html
<!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>
#app{
width: 200px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="app" onclick="console.log(event)"></div>
<div id="app2" onclick="test(event)"></div>
<script>
function test(e){
console.log(e)
}
</script>
</body>
</html>
javascript
// 只要是html事件处理程序,浏览器都会对简单代码语句进行包装,因为这里面实际上都是字符串,是不能执行的,函数调用语句也是的,实际上也只是一个函数调用语句的字符串
// 下面是对两种形式的包装方式
element.onclick = function(event) {
test(event); // 把你写在 onclick 里的字符串代码直接塞到这里执行
};
element.onclick = function(event) {
console.log(event);
};
// 所以这种方式也可以看作是通过外层包装函数的第一个参数接收事件对象
2、DOM0级事件处理程序
html
<!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>
#app {
width: 200px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="app">
</div>
<script>
const box = document.getElementById('app')
box.onclick = function(e){
// 这种方式相当于是给执行的dom对象属性绑定方法,所以这里的this指向当前dom对象
console.log(this)
// 这里的全局事件对象event就是浏览器已经维护好的,可以直接使用,在window对象上
console.log(event)
// 当然这里也是可以通过事件处理程序的第一个参数,来接收事件对象
console.log(e)
}
</script>
</body>
</html>
3、DOM2级事件处理程序
html
<!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>
#app{
width: 200px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="app"></div>
<script>
const box = document.getElementById('app')
box.addEventListener('click',function(e){
console.log(this)
// 这里明显没有传递参数,所以这里的event还是window上的全局事件对象
console.log(event)
// 当然这里也是可以通过事件处理程序的第一个形参,来接收事件对象
console.log(e)
})
</script>
</body>
</html>
04 事件绑定的兼容写法
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">你过来呀!</button>
<script>
// DOM2级事件处理程序,addEventListener,ie不支持,需要使用attachEvent
const btn = document.getElementById('btn')
// btn.addEventListener('click',fn)
// btn.attachEvent('onclick',fn)
addEvent(btn,'click',function(){
console.log(this)
})
// 定义兼容性绑定函数,attachEvent中的this指向window,也需要做兼容处理
function addEvent(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false)
}else if(element.attachEvent){
element.attachEvent('on' + type,function(){
handler.call(element)
})
}
}
</script>
</body>
</html>
05事件调用顺序
html
<!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>
#box{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div id="box" onclick="this.innerHTML += 'html\n'"></div>
<script>
// 如果同时出现html级事件处理程序和DOM0级事件处理程序,html级会被覆盖掉
const box = document.getElementById('box')
box.onclick = function(){
this.innerHTML += 'DOM0\n'
}
box.addEventListener('click',function(){
this.innerHTML += 'DOM2\n'
})
</script>
</body>
</html>
06事件目标对象
html
<!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 type="text/css">
.item {
width: 100px;
height: 80px;
background-color: lightblue;
margin: 50px 10px;
}
</style>
</head>
<body>
<ul id="box">
<li class="item">1</li>
<li class="item">2</li>
</ul>
<script>
const box = document.getElementById('box')
box.onclick = function(e){
e = e || window.event
// 1、currentTarget 事件绑定的元素
console.log(e.currentTarget)
// 2、target 事件触发的元素
console.log(e.target)
// 3、srcElement 事件触发的元素,兼容性好
console.log(e.srcElement)
// this 指向事件绑定的元素
console.log(e.currentTarget === this)
console.log(e.target === this)
// 兼容性处理
let target = e.target || e.srcElement
}
</script>
</body>
</html>
07 事件代理(事件委托)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件代理</title>
<style>
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
overflow: hidden;
margin-top: 50px;
}
ul li {
float: left;
width: 100px;
height: 30px;
background-color: lightblue;
text-align: center;
line-height: 30px;
margin: 0 10px;
color: #fff;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
window.onload = function () {
const ul = document.querySelector('ul')
setTimeout(() => {
const item = document.createElement('li')
item.innerHTML = '6'
ul.appendChild(item)
}, 100)
// 获取所有li元素,这里只获取了原来的5个li元素,新增的li元素没有添加事件监听,所以不会触发事件
// const lis = document.querySelectorAll('li')
// lis.forEach(item => {
// item.addEventListener('mouseover',function () {
// this.style.backgroundColor = 'red'
// })
// item.addEventListener('mouseout',function () {
// this.style.backgroundColor = 'lightblue'
// })
// })
// 给未来的li添加事件监听,这里使用事件代理
ul.addEventListener('mouseover',function (e) {
if(e.target.tagName === 'LI'){
e.target.style.backgroundColor = 'red'
}
})
ul.addEventListener('mouseout',function (e) {
if(e.target.tagName === 'LI'){
e.target.style.backgroundColor = 'lightblue'
}
})
}
</script>
</body>
</html>
08 事件冒泡,阻止冒泡
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件冒泡</title>
</head>
<body>
<button id="btn" style="width: 200px; height: 30px;">点击我</button>
<input type="text" id="test">
<script>
// 两个属性,两个方法 bubbles cancelBubble stopPropagation() stopImmediatePropagation()
// 1、bubble 返回一个布尔值,表示当前事件是否冒泡,注意大部分事件都是冒泡事件,但是focus blur scroll事件不是冒泡事件
const btn = document.getElementById('btn')
const test = document.getElementById('test')
btn.addEventListener('click',function(e){
console.log(e.bubbles) // true
})
test.addEventListener('focus',function(e){
console.log(e.bubbles) // false
})
// 2、cancelBubble 取消事件冒泡,也不能阻止同一事件的其他监听函数执行
btn.addEventListener('click',function(e){
e.cancelBubble = true
})
// 3、stopPropagation() 取消事件冒泡,ie8不支持,但是它无法阻止同一事件的其他监听函数执行
// 4、stopImmediatePropagation() 取消事件冒泡,ie8不支持,但是它可以阻止同一事件的其他监听函数执行
btn.addEventListener('click',function(e){
e = e || window.event
// e.stopImmediatePropagation()
this.innerHTML = '阻止冒泡'
})
btn.addEventListener('click',function(e){
console.log('按钮的另一个监听函数执行了')
})
document.body.onclick = function(){
console.log('这里打印就是冒泡了,没打印就是没有冒泡')
}
</script>
</body>
</html>
09 取消默认事件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>取消默认事件</title>
</head>
<body>
<!-- 这两种方式都是让浏览器执行一段"返回空值"的代码,骗过浏览器让其不跳转 -->
<a href="javascript:;">返回空值取消默认事件</a>
<a href="javascript:void(0);">void(0)取消默认事件</a>
<a href="http://www.baidu.com" id="baidu">事件对象取消默认事件</a>
<script>
// 事件对象的两个方法来阻止默认行为
var a = document.getElementById('baidu');
a.onclick = function(e) {
e = e || window.event;
// 阻止默认行为 ie8不支持
// e.preventDefault();
// 阻止默认事件,兼容ie8以下的浏览器
// e.returnValue = false;
// 兼容写法
if(e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
// 这样也可以阻止默认行为,兼容了所有浏览器
return false
}
</script>
</body>
</html>
10事件对象中的坐标位置
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>鼠标事件对象属性</title>
<style>
#box{
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<div id="parent">
<div id="box"></div>
</div>
<script>
var box = document.getElementById('box');
box.onclick = function(event){
// 相对于浏览器有效窗口的坐标,也就是相对于body的坐标
console.log(event.clientX);
console.log(event.clientY);
// 相对于我电脑显示器的坐标
console.log(event.screenX);
console.log(event.screenY);
// 相对于浏览器页面的距离,包括滚动条
console.log(event.pageX);
console.log(event.pageY);
// 相对于当前事件源的偏移坐标
console.log(event.offsetX);
console.log(event.offsetY);
}
</script>
</body>
</html>