1.解釋以下概念:事件傳播機制、阻止傳播、取消默認事件、事件代理
-
基礎
什么是事件?
JavaScript和HTML的交互是通過事件實現(xiàn)的。事件是某個行為或者觸發(fā),比如點擊、鼠標移動,圖片加載等。
什么是事件流?
事件流描述的是從頁面中接收事件的順序。當用戶點擊了一個有嵌套關系的元素時,那是先點擊的是用戶本身想要點擊的被嵌套的元素,還是嵌套元素的父元素?這里有三種事件傳播的模型:事件冒泡,事件捕獲,DOM事件流。
1.事件冒泡:事件開始時由最具體的元素接收,然后逐級向上傳播到較為不具體的元素
事件冒泡
2.事件捕獲:不太具體的節(jié)點更早接收事件,而最具體的元素最后接收事件,和事件冒泡相反(ie低版本沒有捕獲)
事件捕獲
3.DOM事件流:DOM2級事件規(guī)定事件流包括三個階段,事件捕獲階段,處于目標階段,事件冒泡階段,首先發(fā)生的是事件捕獲,為截取事件提供機會,然后是實際目標接收事件,最后是冒泡階段
DOM事件流 -
事件傳播機制
當一個事件發(fā)生以后,它會在不同的DOM節(jié)點之間傳播。這種傳播分為三個階段:

- 第一階段,“捕獲階段”。:事件從document開始向下傳,一直傳到觸發(fā)的目標節(jié)點上,在這個過程中會依次檢測是否有節(jié)點綁定了事件的監(jiān)聽函數(shù),如果有就會執(zhí)行。
- 第二階段,目標階段:事件到達目標節(jié)點,并且觸發(fā)監(jiān)聽函數(shù)。
- 第三階段,冒泡階段:從目標節(jié)點依次上傳,直到document,再依次判斷是否有節(jié)點綁定了監(jiān)聽函數(shù)。
這種三階段的傳播模型,會使得一個事件在多個節(jié)點上觸發(fā),比如會在捕獲階段和冒泡階段在一個節(jié)點上執(zhí)行兩次。
-
阻止傳播
如果有特殊情況,需要事件在某個節(jié)點上停止傳播,就可以使用stopPropagation()取消事件進一步捕獲或冒
泡,防止再觸發(fā)定義在別的節(jié)點上的監(jiān)聽函數(shù),但是不包括在當前節(jié)點上新定義的事件監(jiān)聽函數(shù)
document.querySelector('#box') = function(e){
e.stopPropagation() //阻止傳播
}
-
取消默認事件
取消瀏覽器對當前事件的默認行為,像有些元素時有默認的事件的,比如<a>標簽,在點擊的時候默認會打開所含的超鏈接。如果想要自定義點擊<a>鏈接的操作,就可以通過preventDefault()取消事件默認行為
document.querySelector('a') = function(e){
e.preventDefault() //阻止默認事件
}
-
事件代理
由于事件會在冒泡階段向上傳播到父節(jié)點,因此可以把子節(jié)點的監(jiān)聽函數(shù)統(tǒng)一處理。指定一個事件處理程序,就可以管理某一類型的所有事件
document.querySelector('ul').addEventListener('click', function(event){
if(event.target.tagName.toLowerCase() === 'li'){ //或者if(e.target.classList.contains('box'))用class來選擇
//監(jiān)聽ul下面的所有l(wèi)i
}
})
如果之后再在ul里面添加li,監(jiān)聽函數(shù)也可以監(jiān)聽新增的li
2.寫一個 Demo,演示事件傳播的過程,演示阻止傳播的效果
捕獲階段還是冒泡階段,可以通過addEventListener第三個參數(shù)cancelable屬性來控制。默認是falsse為冒泡階段,可以設置為true變成捕獲階段
html+css
<style>
.container,
.box,
.target{
border: 1px solid;
padding: 10px;
}
</style>
<button id="btn">click</button>
<div class="container">
container
<div class="box">
box
<div class="target">target</div>
</div>
</div>
js
<script>
var $ = function(e){
return document.querySelector(e)
}
$('.container').addEventListener('click',function(){
console.log('container 捕獲階段')
},true)
$('.box').addEventListener('click',function(){
console.log('box 捕獲階段')
},true)
$('.target').addEventListener('click',function(){
console.log('target 捕獲階段')
},true)
$('.target').addEventListener('click',function(){
console.log('target 冒泡階段')
},false)
$('.box').addEventListener('click',function(){
console.log('box 冒泡階段')
},false)
$('.container').addEventListener('click',function(){
console.log('container 冒泡階段')
},false)
//正常點擊target 輸出
//container 捕獲階段
//box 捕獲階段
//target 捕獲階段
//target 冒泡階段
//box 冒泡階段
//container 冒泡階段
</script>
如果想讓事件在 box冒泡階段 停止
<script>
var $ = function(e){
return document.querySelector(e)
}
$('.container').addEventListener('click',function(){
console.log('container 捕獲階段')
},true)
$('.box').addEventListener('click',function(){
console.log('box 捕獲階段')
},true)
$('.target').addEventListener('click',function(){
console.log('target 捕獲階段')
},true)
$('.target').addEventListener('click',function(){
console.log('target 冒泡階段')
},false)
$('.box').addEventListener('click',function(e){
e.stopPropagations() //這里不能用this
console.log('box 冒泡階段')
},false)
$('.container').addEventListener('click',function(){
console.log('container 冒泡階段')
},false)
//container 捕獲階段
//box 捕獲階段
//target 捕獲階段
//target 冒泡階段
//box 冒泡階段
</script>
3.解釋DOM2事件傳播機制
如果用單獨的事件綁定,比如
document.querySelector('.button').onclick = function(){
console.log('1')
}
document.querySelector('.button').onclick = function(){
console.log('2')
}
//2
想讓.button在被點擊的時候觸發(fā)兩個事件,此時第二個監(jiān)聽函數(shù)會覆蓋第一個,最后的輸出 結果只有2
這個時候不要把onclick當成一個觸發(fā)時間,要把它看成一個“屬性”,那么后面的“屬性”會覆蓋掉前面的“屬性”
<ul>
<li class="box">box1</li>
<li class="box">box2</li>
</ul>
document.querySelectorAll('.box').onclick = function(){
console.log(this.innerText)
}
//一個都輸出不了
因為選擇的是一個數(shù)組,沒有onclick,除非用數(shù)組的forEach去遍歷每一個數(shù)組,但是寫起來又特別的麻煩
DOM2級事件定義了兩個方法用于處理指定和刪除事件處理程序的操作:
addEventListener
removeEventListener
所有的DOM節(jié)點都包含這兩個方法,并且它們都接受三個參數(shù):
1.事件類型
2.事件處理方法
3.布爾參數(shù),如果是true表示在捕獲階段調用事件處理程序,如果是false,則是在事件冒泡階段處理
這時,想要實現(xiàn)上面的效果就很容易了
document.querySelector('.button').addEventListener('click', function(){
console.log('1')
},false)
document.querySelector('.button').addEventListener('click', function(){
console.log('2')
},false)
//1
//2
4.補全代碼
要求:
- 當點擊按鈕開頭添加時在<li>這里是</li>元素前添加一個新元素,內容為用戶輸入的非空字符串;當點擊結尾添加時在最后一個 li 元素后添加用戶輸入的非空字符串.
- 當點擊每一個元素li時控制臺展示該元素的文本內容。
<ul class="ct">
<li>這里是</li>
<li>饑人谷</li>
<li>任務班</li>
</ul>
<input class="ipt-add-content" placeholder="添加內容"/>
<button id="btn-add-start">開頭添加</button>
<button id="btn-add-end">結尾添加</button>
<script>
//你的代碼
</script>
<script>
var $ = function(e){
return document.querySelector(e)
}
var ct = $('.ct')
var addstr = $('#btn-add-start')
var addend = $('#btn-add-end')
var input = $('.ipt-add-content')
addstr.onclick = function(){
if(input.value !== ''){
var box = document.createElement('li')
box.innerText = input.value
ct.insertBefore(box, ct.firstChild)
}
}
addend.onclick = function(){
if(input.value !== ''){
var box = document.createElement('li')
box.innerText = input.value
ct.appendChild(box)
}
}
$('.ct').addEventListener('click', function(s){ //監(jiān)聽點擊
if(s.target.tagName.toLowerCase() === 'li'){
console.log(s.target.innerText)
}
})
</script>
5.onlick與addEventListener的區(qū)別
- onclick事件在同一時間只能指向唯一對象
- addEventListener給一個事件注冊多個listener
- addEventListener對任何DOM都是有效的,而onclick僅限于HTML
- addEventListener可以控制listener的觸發(fā)階段(捕獲/冒泡)。對于多個相同的事件處理器,不會重復觸發(fā),不需要手動使用removeEventListener清除
- IE9使用attachEvent和detachEvent


