前言
例子就不兼容IE了,需要再去网上查阅即可。
事件流
直接一句话概括,事件的流向。
主要分为事件冒泡和事件捕获。
事件冒泡
直接上例子,首先我们写个视图看看:
<style>
ul {
border: 1px solid red;
width: 200px;
height: 100px;
position: relative;
}
li {
width: 50px;
height: 50px;
border: 1px solid black;
list-style: none;
display: inline-block;
position: absolute;
top: 110px;
}
li:nth-child(2) {
left: 100px;
}
li:nth-child(3) {
left: 160px;
}
</style>
<body>
<ul id="ul">
<li class="li" id="li1">一号</li>
<li class="li">二号</li>
<li class="li">三号</li>
</ul>
</body>
渲染出来如下:
可能会有朋友说为啥不把子元素放入父元素中显示。因为事件流机制看的不是视图而是html结构!很多初学者会有这个误解。
如果我们想给每个子元素绑定上点击事件,分别打印出自己的内容,一般可以这么写:
let Olis = document.getElementsByClassName("li");
for (let i in Olis) { // 通过循环给每个子元素上绑定事件
Olis[i].onclick = function () {
console.log(this.innerText);
};
}
好,点击下面每个子元素都能正常打印出对应innerText。接着,如果我们想给父级元素绑定上点击事件:
let oUl = document.getElementById("ul");
oUl.onclick = function () {
console.log(this.id);
};
每次点击上面父元素都能获取到id值ul。
诡异的来了!当我们点击下方的子元素,居然也能打印出父元素的id值ul!
这就是事件冒泡的体现!一般标准浏览器都会默认开启事件冒泡机制,也就是当我们点击一个元素,触发了事件,那么这个事件会顺着这个元素的html结构向父级一层一层传递出去。
被点击的这个元素被称为事件源。
怎么去捕获这个事件源呢:
let oUl = document.getElementById("ul");
oUl.onclick = function (event) {
// event默认参数
console.log(event.target); // 获取事件源
};
我们依次去点击父级和子元素,都能够打印出对应的html:
好,利用这个事件冒泡机制,我们可以改写最开始那个 “给每个子元素绑定上点击事件,分别打印出自己的内容” 的例子:
let oUl = document.getElementById("ul");
oUl.onclick = function (event) {
console.log(event.target.innerText);
};
但是,你会发现点击父元素也能一下子把所有子元素都打印出来了,所以还需要在函数内部做个判断过滤父元素:
let oUl = document.getElementById("ul");
oUl.onclick = function (event) {
if (event.target.nodeName === "LI") {
console.log(event.target.innerText);
}
};
这样就完美了。
接着,我们让一号子元素叛变,给它自己绑定个函数不和其他子元素玩:
let oLi1 = document.getElementById("li1");
oLi1.onclick = function () {
console.log("一号叛变了");
};
let oUl = document.getElementById("ul");
oUl.onclick = function (event) {
if (event.target.nodeName === "LI") {
console.log(event.target.innerText);
}
};
当我们点击一号元素后,会同时打印"一号叛变了"和“一号”,因为点击的时候不仅触发了绑在身上的事件,同时还通过冒泡机制触发了父元素上的方法。这时候我们只想让一号触发自己的方法怎么办呢?可以通过一个方法阻止事件冒泡:
let oLi1 = document.getElementById("li1");
oLi1.onclick = function (event) {
event.stopPropagation(); // 阻止这个事件源冒泡
console.log("一号叛变了");
};
这样再次点击只会触发该函数了。
我们来看看利用事件冒泡机制写代码有什么好处:
- 代码简洁。例如子级如果是动态的,会增删改,如果用原来的循环方法去写会非常麻烦,我们交给父元素处理就一劳永逸了,代码精简很多。
- 性能优化好。不用获取过多子元素dom,且事件只绑定在一个父级身上。
一般事件冒泡也被叫做冒泡委托、事件委托、事件代理
事件捕获
可以直接理解为方向相反的事件冒泡。也就是当我们点击一个元素,触发了事件,那么这个事件会顺着这个元素的html结构向子级一层一层传递进去。
还是上面的例子,这会我们使用事件捕获机制:
function show(event) {
console.log(event.target);
}
oUl.addEventListener("click", show, true); // 默认第三个参数为false ,表示冒泡,如果为true表示捕获
当我们点击子级时,只会打印出自己了,因为事件向内传递不会向外了。
简单封装代理函数
这个封装是我在慕课上看到的,贴在这里给大家看看(可看可不看,问题不大):
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}
// 不用代理
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
event.preventDefault() // 阻止默认行为
alert(this.innerHTML)
})
// 开启代理
const dad= document.getElementById('dad')
bindEvent(dad, 'click', 'a', function (event) {
event.preventDefault()
alert(this.innerHTML)
})
阻止冒泡和默认事件
这两个单独拿出来说是因为面试喜欢问,但是平时基本用不到,容易遗忘的
event.stopPropagation() // 阻止冒泡
event.preventDefault() // 阻止默认事件