首先理解事件,我们需要理解事件的一些基本概念
事件流
描述的是页面接受事件的顺序
比如,一个div里面嵌套一个div,我们点击内层的div,这个时候是内层的div事件先触发呢,还是外层的事件先触发呢?目前有三种模型
首先我们看一下这个图了解一下
这个图可以看出
1.事件冒泡:从下而上,由最具体的元素接收,然后逐级向上传递到不具体的元素
2.事件捕获:从下而上,不太具体的节点更早的接受事件,而最具体的元素最后接收事件,和冒泡相反
我们可以用dom事件流来控制事件冒泡和事件捕获,DOM2事件规定事件流包括三个接待你,事件捕获,目标阶段,事件冒泡
DOM2级事件定义了两个方法用于处理指定和删除事件处理程序的操作:
addEventListener
removeEventListener
所有的DOM节点都包含这两个方法,并且它们都接受三个参数:
1.事件类型
2.事件处理方法
3.布尔参数,如果是true表示在捕获阶段调用事件处理程序,如果是false,则是在事件冒泡阶段处理
<div class="one">
<div class="two">
<div class="three">
<div class="four"></div>
</div>
</div>
</div>
<script>
function $$(node){
return document.querySelectorAll(node)
}
$$('.one')[0].addEventListener('click',function(){
console.log('one')
},false)
$$('.two')[0].addEventListener('click',function(){
console.log('two')
},false)
$$('.three')[0].addEventListener('click',function(){
console.log('three')
},false)
$$('.four')[0].addEventListener('click',function(e){
console.log('four')
},false)
//"four"
//"three"
//"two"
//"one"
</script>
可以看出事件是从下而上的执行,事件冒泡
$$('.one')[0].addEventListener('click',function(){
console.log('one')
},true)
$$('.two')[0].addEventListener('click',function(){
console.log('two')
},true)
$$('.three')[0].addEventListener('click',function(){
console.log('three')
},true)
$$('.four')[0].addEventListener('click',function(e){
console.log('four')
},true)
//"one"
//"two"
//"three"
//"four"
从上而下执行,事件捕获
有时候,我们并不需要事件冒泡,我们点击最小的div的时候并不需要浏览器进行事件冒泡往上传递,我们可以使用停止冒泡事件来解决这个问题4
e.stopPropagation();
效果如下
function $$(node){
return document.querySelectorAll(node)
}
$$('.one')[0].addEventListener('click',function(){
console.log('one')
},false)
$$('.two')[0].addEventListener('click',function(){
console.log('two')
},false)
$$('.three')[0].addEventListener('click',function(){
console.log('three')
},false)
$$('.four')[0].addEventListener('click',function(e){
console.log('four')
e.stopPropagation();
},false)
//four
这个时候我们可以看到控制台打印出了//four,并没有打印出//three two one
我们就可以知道e.stopPropagation()这条语句阻碍了事件往上传播, 使得包裹$$('.four')[0]这个元素的所有元素的点击事件没有触发
结合阻碍事件冒泡。我们还可以了解组织浏览器默认行为
比如说
<a href="https://www.baidu.com">百度</a>
上述代码,如果没有那一串的js代码,我们可以理解成我们点击页面中百度这个字符可以进入百度页面,但是我们添加了
<script>
var a = document.querySelectorAll('a')
a[0].addEventListener('click',function(e){
e.preventDefault();//阻止浏览器默认事件
})
</script>
这个时候,我们发现我们点击链接无法进入百度页面了,这个就是阻止浏览器的默认行为,浏览器对特定的标签会触发特定的事件,我们如果获取该标签节点,组织这个浏览器默认事件的触发,就被称为浏览器的默认行为组织
<form action="www.baidu.com">
<input type="text" name="username">
<input type="submit" value="提交">
</form>
<script>
var submit = document.querySelectorAll('[type=submit]')
submit[0].addEventListener('click',function(e){
e.preventDefault()
})
</script>
大家可以尝试一下上一段的代码,可以发现submit提交按钮被提交这个行为被禁止了
事件代理
我们页面渲染执行代码是从上到下的,一般情况下我们的js代码显示卸载最下面的,方便不为了阻碍html代码和css代码的渲染
我们有个需求,html代码块中ul下面所有的Li当点击的时候都会在控制台打印li里面当前的内容
<ul>
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
<script>
function $$(node){
return document.querySelectorAll('node')
}
for(var i = 0 ;i<$$('ul>li').length;i++){
$$('ul>li')[i].onclick = function(){
console.log(this.innerText)
}
}
</script>
这样书写当然没有错,是正确的写法,但是,当我们的html内容中使用js新增了li还能行的通吗?
在js中添加下面的代码尝试一下发现好像并没有什么用
var liNew = document.createElement('li');
liNew.innerText = 444;
$$('ul')[0].appendChild(liNew);
因为js代码是从上往下执行的,当你添加的新的li的时候,前面的for循环中并没有把你新加的这个Li给添加进去,所以就产生了这个尴尬的效果
当然,前面说的事件代理可以解决这个问题,那么问题来了,怎么理解事件代理呢?
简单来说事件代理就是可以理解成所有元素的事件都放在某一个元素上,使得那个元素成为代理。。。
好像不太好理解,我们以上面的例子为例,事件代理的实现思想就是,我不给每个li添加点击事件,我给ul下面所有的li添加事件,我不管你的li是不是从本来html就有的还是Js带来的妖艳货色,我都给你们添加
实现代码
$$('ul')[0].onclick = function(e){
var e = e || window.event;
var target = e.target || window.event;
if(target.nodeName.toLowerCase() == 'li'){
console.log(target.innerText);
}
}
这个时候无论怎么添加li元素都会使得点击出现当前Li的文本内容,我们通过点击li标签,前面我们说过事件捕获,他们的父亲元素捕获到了点击事件开始向下传递是否是li这个节点,如果是,就触发事件打印内容,很好理解吧!!!
为什么要使用事件捕获呢,我如果添加DomContentLoaded事件也可以然后在使用for循环给Li添加事件也可以呀,或者我们把给li添加事件的代码写在js代码的最下面也可以有作用呀
这个就不得不提到我们事件代理的优点了
1.管理的函数变少了。不需要为每个元素都添加监听函数。对于同一个父节点下面类似的子元素,可以通过委托给父元素的监听函数来处理事件
2.可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定
3.JavaScript和DOM节点之间的关联变少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率
网友评论