JS-Web-API(二)- 事件
# 1.几道面试题(以点带面)
- 编写一个通用的事件监听函数
- 描述事件冒泡的流程
- 无限下拉的图片列表,如何监听每个图片的点击?
# 2.涉及知识点
- 事件绑定
- 事件冒泡
- 事件代理(又称事件委托)
# 2.1 事件绑定
# JavaScript绑定事件的三种方式
- 使用内联
- 使用.onclick的方式
- 使用事件监听addEventListener的方式
示例demo
<!-- 内联方式。就是在一个元素上面直接绑定了一个点击onclick事件,此事件为DOM 0级标准 -->
<!-- 同时,这个事件的优先级是最高的 -->
<input type="button" value="按钮" onclick="alert(1);">
<!--.onclick形式。也可以给一个DOM元素添加上一个事件。这个也是DOM 0级标准-->
<input type="button" value="按钮">
<script type="text/javascript">
var bt = document.getElementsBytagname("input")[0];
bt.onclick = function(){
alert(2)
}
</script>
<!--上述两种方式都有个弊端:一个元素只能添加一个事件-->
<!--下面这种方式addEventListener可以给一个DOM对象绑定一个或者是多个事件,推荐使用-->
<!--1.事件类型,不需要添加上on。2.事件函数。3.是否捕获(布尔值),默认是false,即不捕获,那就是冒泡-->
<input type="button" value="按钮">
<script type="text/javascript">
var bt = document.getElementsBytagname("input")[0];
bt.addEventListener("click", function(){
alert(1)
})
bt.addEventListener("click", function(){
alert(2)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 通用的事件监听函数-初版
//通用的事件绑定函数
function bindEvent(elem,type,fn){
elem.addEventListener(type,fn)
}
//使用示例
const btn1= document.getElementById('btn1')
bindEvent(btn1,'click',event=>{
console.log(event.target)//获取触发的元素,注意event.target等下事件代理会用到
event.preventDefault()//阻止默认行为
alert('clicked') I
})
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 2.2 事件冒泡
冒泡:当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
<!--示例demo-->
<body>
<div id="div1">
<p id="p1">激活</p>
<p id="p2">取消</p>
<p id="p3">取消</p>
<p id="p4">取消</p>
</div>
<div id="div2">
<p id="p5">取消</p>
<p id="p6">取消</p>
</div>
</body>
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1,'click',e=>{
e.stopPropagation()//注释掉这一行,来体会事件冒泡
alert('激活')
})
bindEvent(body,'click',e=>{
alert('取消')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2.3 事件代理
事件代理,就是把一个元素响应事件(click
、keydown
......)的函数委托到另一个元素。事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素。当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
代理的好处:
- 代码简洁
- 减少浏览器内存占用
- 但是,不要滥用
有了事件代理,我们要对通用的事件监听函数做进一步优化
# 优化通用事件监听函数
<!--elem:要操作的DOM元素;type:事件;selector:要被代理的元素,elem是它的外层元素;fn:回调函数-->
function bindEvent(elem, type, selector, fn) {
//说明只传了三个参数,没有元素需要做代理,直接操作某个DOM元素绑定事件即可
if (fn == null) {
fn=selector
selector=null
}
//注意elem既可能是要直接加事件监听的DOM元素,也可能是帮助事件代理的外层元素
elem.addEventListener(type,event=>{
//拿到触发事件的元素
const target = event.target
if (selector) {
//selector不为null,其需要代理绑定
//matches方法判断DOM元素是否符合CSS选择器
//此时如果匹配上了触发事件的是被代理元素selector,直接由其执行调用方传入的回调
//因为要考虑selector还有兄弟元素,它们也可能触发冒泡的清空,所以要匹配筛选一下
if (target.matches(selector)){
fn.call(target,event)
}
}else {
//不需要代理,普通绑定
//要操作的DOM元素直接执行调用方传入的回调
//调用方传入的回调可能用到this,故必须用call把this指向当前触发事件的元素
fn.call(target, event)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//使用示例
<div id="div3">
<a href="#">a1</a><br>
<a href="#">a2</a><br>
<a href="#">a3</a><br>
<a href="#">a4</a><br>
<button>加载更多...</button>
</div>
<script>
//普通绑定
const btn1 = document.getElementById('btn1')
//注意传入的回调不能用箭头函数形式,否则this不对
bindEvent(btn1,'click', function (event){
// console.log(event.target)//获取触发的元素
event.preventDefault()//阻止默认行为
alert(this.innerHTML)
})
//代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3,'click','a',function (event){
event.preventDefault()
alert(this.innerHTML)
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 3.对前文问题的解答
# 3.1 编写一个通用的事件监听函数
<!--elem:要操作的DOM元素;type:事件;selector:要被代理的元素,elem是它的外层元素;fn:回调函数-->
function bindEvent(elem, type, selector, fn) {
//说明只传了三个参数,没有元素需要做代理,直接操作某个DOM元素绑定事件即可
if (fn == null) {
fn=selector
selector=null
}
//注意elem既可能是要直接加事件监听的DOM元素,也可能是帮助事件代理的外层元素
elem.addEventListener(type,event=>{
//拿到触发事件的元素
const target = event.target
if (selector) {
//selector不为null,其需要代理绑定
//matches方法判断DOM元素是否符合CSS选择器
//此时如果匹配上了触发事件的是被代理元素selector,直接由其执行调用方传入的回调
//因为要考虑selector还有兄弟元素,它们也可能触发冒泡的清空,所以要匹配筛选一下
if (target.matches(selector)){
fn.call(target,event)
}
}else {
//不需要代理,普通绑定
//要操作的DOM元素直接执行调用方传入的回调
//调用方传入的回调可能用到this,故必须用call把this指向当前触发事件的元素
fn.call(target, event)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 3.2 描述事件冒泡的流程
- 基于DOM树形结构
- 事件会顺着触发元素向上冒泡
- 应用场景:代理(事件代理没有冒泡机制是用不了的)
# 3.3 无限下拉的图片列表,如何监听每个图片的点击?
- 利用事件代理机制
- 用 e.target 获取触发元素
- 用matches来判断是否是触发元素