什么是事件
- JavaScript和HTML之间的交互是通过事件实现的。
- 事件:就是文档或浏览器窗口发生的一些特定的交互瞬间。
- 监听器(或事件处理程序):预定事件,以便事件发生时执行相应的代码。
- 通俗的说,这种模型其实就是一个观察者模式。(事件是对象主题,而这一个个的监听器就是一个个观察者)
什么是事件流
- 事件流描述的就是从页面中接受事件的顺序。
- 而早起的IE和Netscape提出了完全相反的事件流概念,IE事件流是事件冒泡,而Natscape的事件流就是事件捕获。
事件冒泡事件捕获
IE提出的事件流是事件冒泡,即从下至上,从目标触发的元素逐级向上传播,知道window对象。
而Netscape的事件流就是事件捕获,即从document逐级向下传播到目标元素。
由于IE低版本浏览器不支持,所以很少使用事件捕获
后来ECMAScript在DOM2中对事件流进行了进一步规范,DOM2级事件规定的事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
DOM事件处理
DOM节点中有了事件,那我们就需要对事件进行处理,而DOM事件处理分为4个级别:DOM0级事件处理、DOM1级事件处理、DOM2级事件处理、DOM3级事件处理。
DOM0:只支持事件冒泡
dom0级事件具有几号的夸浏览器优势,会以最快的速度绑定。
第一种方式是内联模型(行内绑定),将函数名直接作为html标签中属性的属性值。
<div onclick="btnClick()">click</div>
<script>
function btnClick(){
console.log("hello");
}
</script>
缺点:不符合w3c中关于内容与行为分离的基本规范
第二种方式是脚本模型(动态绑定),通过在js中选中某个节点,然后给节点添加onclick属性。
直接给onclick赋值一个function
<div id="btn">点击</div>
<script>
var btn = document.getElementById("btn");
btn.onclick = function(){
console.log("hello");
}
// 会覆盖上一个
btn.onclick = function(){
console.log("hello again");
}
</script>
缺点:如果添加两个handler,后面一个会覆盖前面一个。(因为是属性赋值)
嵌套的元素事件
<div id="btn3">
btn3
<div id="btn2">
btn2
<div id="btn1">
btn1
</div>
</div>
</div>
<script>
let btn1 = document.getElementById("btn1");
let btn2 = document.getElementById("btn2");
let btn3 = document.getElementById("btn3");
btn1.onclick=function(){
console.log(1)
}
btn2.onclick=function(){
console.log(2)
}
btn3.onclick=function(){
console.log(3)
}
</script>
由于事件冒泡:从下往上,依次触发事件。
DOM2
进一步规范之后,有了DOM2级事件处理程序。可以为一个节点添加多个事件处理。
其中定义了两个方法:
1、addEventlistener() 添加时间侦听器
- 添加的函数不可以重名,否则会被覆盖
2、removerEventListener() 删除事件侦听器
- 无法删除匿名函数
函数均有3个参数:
1、要处理的事件名
2、事件处理函数
3、boolean值,默认false 表示使用冒泡机制,true表示捕获机制。
<div id="btn">点击</div>
<script>
var btn=document.getElementById("btn");
btn.addEventListener("click",hello,false);
btn.addEventListener("click",helloagain,false);
// 【第一个处理函数】
function hello(){
console.log("hello");
}
// 【第二个处理函数】
function helloagain(){
console.log("hello again");
}
</script>
添加多个handler,则按照添加顺序执行。
那么既有冒泡,也有捕获的时候。谁先谁后呢?
btn1.addEventListener('click',function(){
console.log('btn1捕获')
}, true)
btn1.addEventListener('click',function(){
console.log('btn1冒泡')
}, false)
btn2.addEventListener('click',function(){
console.log('btn2捕获')
}, true)
btn2.addEventListener('click',function(){
console.log('btn2冒泡')
}, false)
btn3.addEventListener('click',function(){
console.log('btn3捕获')
}, true)
btn3.addEventListener('click',function(){
console.log('btn2冒泡')
}, false)
// 调换所有顺序
btn1.addEventListener('click',function(){
console.log('btn1冒泡')
}, false)
btn1.addEventListener('click',function(){
console.log('btn1捕获')
}, true)
btn2.addEventListener('click',function(){
console.log('btn2捕获')
}, true)
btn2.addEventListener('click',function(){
console.log('btn2冒泡')
}, false)
btn3.addEventListener('click',function(){
console.log('btn3捕获')
}, true)
btn3.addEventListener('click',function(){
console.log('btn2冒泡')
}, false)
但是最sub的元素会按照添加的顺序来执行。父元素们的捕获和冒泡顺序照旧。
阻止后序事件
有时候我们需要点击事件不再继续传递,我们在btn2上加上stopPropagation函数,阻止程序冒泡。
btn1.addEventListener('click',function(){
console.log('btn1冒泡')
}, false)
btn1.addEventListener('click',function(){
console.log('btn1捕获')
}, true)
// 阻止冒泡
btn2.addEventListener('click',function(){
console.log('btn2冒泡')
}, false)
btn2.addEventListener('click',function(ev){
ev.stopPropagation();
console.log('btn2捕获')
}, true)
btn3.addEventListener('click',function(){
console.log('btn3冒泡')
}, false)
btn3.addEventListener('click',function(e){
console.log('btn3捕获')
}, true)
- 所有后续事件全部不会执行,包括btn1自身
- 如果添加在btn1,不管添加在哪一个,都会执行完所有的btn1处理。但是不会执行执行父级冒泡的内容。
1.preventDefault:阻止标签默认行为,有些标签有默认行为,例如a标签的跳转链接属性href等。
- form中的submi点击默认行为是提交表单,这里并不需要它提交,只需要执行register方法,故阻止为秒。
2. stopPropagation:阻止事件冒泡,点击那个元素,就只响应这个元素,父级就不会响应了
事件委托
如果有多个DOM节点需要监听事件的情况下,给每个DOM绑定监听函数,会极大的影响页面的性能,因为我们通过父元素事件委托来进行优化,事件委托利用的就是冒泡的原理。
正常情况我们给每一个li都会绑定一个事件,但是如果这时候li是动态渲染的,数据又特别大的时候,每次渲染后(有新增的情况)我们还需要重新来绑定,又繁琐又耗性能;这时候我们可以将绑定事件委托到li的父级元素,即ul。
修改前:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var li_list = document.getElementsByTagName('li')
for(let index = 0;index<li_list.length;index++){
li_list[index].addEventListener('click', function(ev){
console.log(ev.currentTarget.innerHTML)
})
}
</script>
修改后
var ul_dom = document.getElementsByTagName('ul')
ul_dom[0].addEventListener('click', function(ev){
console.log(ev.target.innerHTML)
})
给ul添加事件
- target返回触发事件的元素,不一定是绑定事件的元素
- currentTarget返回的是绑定事件的元素
总结
冒泡
概念: 从子元素向上触发
触发方式:
dom0方式添加时间
1、内联方式: 在标签内添加onclick事件
2、dom元素事件属性:document.getElementById('id').onclick = btnclick
dom2方式
1、通过addEventLisenter(或者+false,默认是false): btn.addEventListener("click",btnClick,false);
2、为啥false是冒泡?可能是因为其他的方式都会产生冒泡,因此addEventLisenter默认是为了捕获而出现的吧,但是捕获的顺序比较特殊,因此false冒泡是默认
阻止方式
btn1是btn的父元素,因此理论上会:子捕获 =》父冒泡
然而,子元素阻止了冒泡:子捕获
btn1.addEventListener('click',function(){ //父
console.log('btn1冒泡')
}, false)
btn.addEventListener('click',function(ev){ //子
ev.stopPropagation();
console.log('btn捕获')
}, true)
捕获
概念:元素从上往下触发
触发方式
dom2方式
通过addEventLisenter+true:btn.addEventListener("click",btnClick,true);
阻止方式
和冒泡一样
event.stopPropagation():阻止之后发生的所有事件,如果父元素设置了阻止冒泡,那么子元素所有的事件都不会发生。但其实父元素先发生的事件对于子元素来说属于事件捕获。
事件委托
1、 某个元素中的每个子元素都需要添加一个事件,但逐个添加太浪费了
处理事件时,可以通过 currentTarget 操作事件绑定元素 =》子元素
2、 直接在父元素添加事件
子元素全部不需要添加事件
处理事件时,用target 操作真正触发的元素 =》 子元素。因此 currentTarget获取的绑定元素是父元素
currentTarget 和 target
- target 是事件触发的真是元素
- currentTarget 是事件绑定的元素
https://www.wolai.com/mary/iPeoDYW5mvo2Zazb71cGv9
https://zhuanlan.zhihu.com/p/114276880