javaScript 瀏覽器事件
1.事件基本概念
事件是指文檔或者瀏覽器中發(fā)生的一些特定交互瞬間,比如打開某一個(gè)網(wǎng)頁(yè)霜幼,瀏覽器加載完成后會(huì)觸發(fā)load事件,當(dāng)鼠標(biāo)浮于某一個(gè)元素上時(shí)會(huì)觸發(fā)hover事件洗搂,當(dāng)鼠標(biāo)點(diǎn)擊某一個(gè)元素時(shí)會(huì)觸發(fā)click事件等等热康。
事件處理就是當(dāng)事件被觸發(fā)吼沛申,瀏覽器響應(yīng)這個(gè)事件的行為,而這個(gè)行為所對(duì)應(yīng)的代碼即為事件處理程序姐军。
2.事件操作:監(jiān)聽與移除監(jiān)聽
2.1監(jiān)聽事件
瀏覽器會(huì)根據(jù)一些事件作出相對(duì)應(yīng)的事件處理铁材,事件處理的前提是需要監(jiān)聽事件,監(jiān)聽事件的方法主要有以下三種:
2.1.1 HTML內(nèi)聯(lián)屬性
即在HTML元素里直接填寫與事件相關(guān)的屬性奕锌,屬性值為事件處理程序著觉。示例如下:
<button onclick="console.log('You clicked me!');"></button>
onclick對(duì)應(yīng)著click事件,所以當(dāng)按鈕被點(diǎn)擊后歇攻,便會(huì)執(zhí)行事件處理程序固惯,即控制臺(tái)輸出“You clicked me!”缴守。
不過我們需要指出的是葬毫,這種方式將HTML代碼與JavaScript代碼耦合在一起,不利于代碼的維護(hù)屡穗,所以應(yīng)該盡量避免使用這樣的方式贴捡。
2.1.2 DOM屬性綁定
通過直接設(shè)置某個(gè)DOM節(jié)點(diǎn)的屬性來指定事件和事件處理程序,上代碼
const btn = document.getElementById("btn");
btn.onclick = function(e) {
console.log("You clicked me!")
}
上面示例中村砂,首先獲得btn這個(gè)對(duì)象烂斋,通過給這個(gè)對(duì)象添加onclick
屬性的方式來監(jiān)聽click
事件,這個(gè)屬性值對(duì)應(yīng)的就是事件處理程序础废。這段程序也被成為DOM 0級(jí)事件處理程序汛骂。
2.1.3 事件監(jiān)聽函數(shù)
標(biāo)準(zhǔn)的事件監(jiān)聽函數(shù)如下:
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
console.log("You click me!");
}, false);
上面的示例表示先獲得表示節(jié)點(diǎn)的btn對(duì)象,然后在這個(gè)對(duì)象上面添加了一個(gè)事件監(jiān)聽器评腺,當(dāng)監(jiān)聽到click事件發(fā)生時(shí)帘瞭,則調(diào)用回調(diào)函數(shù),即在控制臺(tái)輸出“You clicked me!”
蒿讥。addEventListener
函數(shù)包含了第三個(gè)參數(shù)false
蝶念,第三個(gè)參數(shù)的含義在后面的事件觸發(fā)三個(gè)階段之后再講解。這段程序也被稱作DOM 2級(jí)事件處理程序芋绸。IE9+媒殉、FireFox、Safari摔敛、Chrome 和 Opera 都是支持 DOM 2 級(jí)事件處理程序的廷蓉,對(duì)于 IE8 及以下版本,則用 attacEvent() 函數(shù)綁定事件马昙。
所以我們得寫一段具有兼容性的代碼:
function addEventHandler(obj, eventName, handler) {
if (document.addEventListener) {
obj.addEventListener(eventName, handler, false);
} else if (document.attachEvent) {
obj.attachEvent("on" + eventName, handler);
} else {
obj["on" + eventName] = handler;
}
}
2.2移除事件監(jiān)聽
在為某個(gè)元素綁定了一個(gè)事件后苦酱,如果想解除綁定售貌,則需要removeEventListener
方法。
const handler = function() {
// hanlder logic
}
const btn = document.getElementById("btn");
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);
需要注意的是疫萤,綁定事件的回調(diào)函數(shù)不能是匿名函數(shù),必須是一個(gè)已經(jīng)被聲明的函數(shù)敢伸,因?yàn)榻獬录壎〞r(shí)需要傳遞這個(gè)回調(diào)函數(shù)的引用扯饶。
同樣,IE8 及以下版本也不支持上面的方法池颈,而是用 detachEvent 代替尾序。
const handler = function() {
// hnalder logic
}
const btn = document.getElementById("btn");
btn.attachEvent("onclick", handler);
btn.detachEvent("onclick", handler);
同樣,可以寫一段具有兼容性的刪除事件函數(shù):
function removeEventHandler(obj, eventName, handler) {
if (document.removeEventListener) {
obj.removeEventListener(eventName, handler, false);
} else if (document.detachEvent) {
obj.detachEvent("on" + eventName, handler);
} else {
obj["on" + eventName] = null;
}
}
DOM事件級(jí)別
DOM級(jí)別一共可以分為四個(gè)級(jí)別:DOM0級(jí)
躯砰、DOM1級(jí)
每币、DOM2級(jí)
、DOM3級(jí)
琢歇。而DOM事件分為3個(gè)級(jí)別:DOM 0
級(jí)事件處理兰怠,DOM2
級(jí)事件處理和DOM3
級(jí)事件處理。由于DOM1
級(jí)中沒有事件的相關(guān)內(nèi)容李茫,所以沒有DOM1
級(jí)事件揭保。
DOM0級(jí)事件
el.onclick=function(){}
var btn = document.getElementById("btn");
btn.onclick = function() {
alert(this.innerHTML);
}
當(dāng)希望為同一個(gè)元素、標(biāo)簽綁定多個(gè)同類型事件的時(shí)候(如給上面的這個(gè)btn元素綁定3個(gè)點(diǎn)擊事件)魄宏,是不背允許的秸侣。DOM0事件綁定,給元素的事件行為綁定方法宠互,這些方法都是在當(dāng)前元素事件行為的冒泡階段(或者目標(biāo)階段)執(zhí)行的味榛。
DOM 2級(jí)事件
el.addEventListener(event-name,callback,useCapture)
- event-name: 事件名稱,可以是標(biāo)準(zhǔn)的DOM事件予跌。
- callback:回調(diào)函數(shù)搏色,當(dāng)事件觸發(fā)時(shí),函數(shù)會(huì)被注入一個(gè)參數(shù)為當(dāng)前事件對(duì)象event匕得。
- useCpature:默認(rèn)是false继榆,代表事件句柄在冒泡執(zhí)行階段執(zhí)行(或者說注冊(cè)的是冒泡事件),true表示事件句柄在捕獲階段執(zhí)行(或者說注冊(cè)的是捕獲事件)
var btn = document.getElementById('btn');
btn.addEventListener("click", test, false);
function test(e) {
e = e || window.event;
alert((e.target || e.srcElement).innerHTML);
btn.removeEventListner("click", test);
}
IE9以下的IE瀏覽器不支持 addEventListener()和removeEventListener()汁掠,使用 attachEvent()與detachEvent() 代替略吨,因?yàn)镮E9以下是不支持事件捕獲的,所以也沒有第三個(gè)參數(shù)考阱,第一個(gè)事件名稱前要加on翠忠。可以對(duì)此做個(gè)兼容性處理乞榨。
DOM 3級(jí)事件
在DOM 2級(jí)事件的基礎(chǔ)上添加了更多的事件類型秽之。
- UI事件当娱,當(dāng)用戶預(yù)頁(yè)面的元素交互時(shí)觸發(fā),如:load考榨、scroll
- 焦點(diǎn)事件跨细,當(dāng)元素獲得或失去焦點(diǎn)時(shí)觸發(fā),如:blur河质、focus
- 鼠標(biāo)事件冀惭,當(dāng)用戶通過誰(shuí)啊哦在頁(yè)面執(zhí)行操作時(shí)觸發(fā)如:dblclick、mouseup
- 滾輪事件掀鹅,當(dāng)使用鼠標(biāo)滾輪或類似設(shè)備時(shí)觸發(fā)散休,如:mousewheel
- 文本事件,當(dāng)在文檔中輸入文本時(shí)觸發(fā)乐尊,如:textinput
- 鍵盤事件戚丸,當(dāng)用戶通過鍵盤在頁(yè)面上執(zhí)行操作時(shí)觸發(fā),如:keydown扔嵌、keypress
- 合成事件限府,當(dāng)為IME(輸入法編輯器)輸入字符時(shí)觸發(fā),如:compositionstart
- 變動(dòng)事件对人,當(dāng)?shù)讓覦OM結(jié)構(gòu)發(fā)生變化時(shí)觸發(fā),如:DOMsubtreeModified谣殊。
- 同時(shí)DOM 3級(jí)事件也允許使用者自定義一些事件。
總結(jié):
- DOM2級(jí)的好處是可以添加多個(gè)事件處理程序牺弄;DOM0級(jí)對(duì)每個(gè)事件支持持一個(gè)事件處理程序姻几;
- 通過DOM2級(jí)添加的匿名函數(shù)無法移除,
addEventListener
和removeEventListener
的handler
必須同名 - 作用域:DOM 0的
handler
會(huì)在所屬元素的作用域內(nèi)運(yùn)行势告,IE的handler
會(huì)在全局作用域運(yùn)行蛇捌,this === window
- 觸發(fā)順序:添加多個(gè)事件時(shí),DOM2會(huì)按照添加順序執(zhí)行咱台,IE會(huì)以相反的順序執(zhí)行
3.事件觸發(fā)過程
事件流描述了頁(yè)面接收事件的順序÷绨瑁現(xiàn)代瀏覽器事件流包含三個(gè)過程,分別是捕獲階段回溺、目標(biāo)階段和冒泡階段春贸。
下面詳細(xì)地講解這三個(gè)過程。
3.1捕獲階段
當(dāng)我們對(duì) DOM元素進(jìn)行操作時(shí)遗遵,比如鼠標(biāo)點(diǎn)擊萍恕、懸浮等,就會(huì)有一個(gè)事件傳輸?shù)竭@個(gè)DOM元素车要,這個(gè)事件從Window開始允粤,依次經(jīng)過document、html、body类垫,再不斷經(jīng)過過子節(jié)點(diǎn)直到到達(dá)目標(biāo)元素司光,從 Window到達(dá)目標(biāo)元素父節(jié)點(diǎn)的過程稱為捕獲階段,注意此時(shí)還未到達(dá)目標(biāo)節(jié)點(diǎn)悉患。
3.2目標(biāo)階段
捕獲階段結(jié)束時(shí)残家,事件到達(dá)了目標(biāo)節(jié)點(diǎn)的父節(jié)點(diǎn),最終到達(dá)目標(biāo)節(jié)點(diǎn)购撼,并在目標(biāo)節(jié)點(diǎn)上觸發(fā)了這個(gè)時(shí)間跪削,這就是目標(biāo)階段。
需要注意的是迂求,事件觸發(fā)的目標(biāo)節(jié)點(diǎn)為最底層的節(jié)點(diǎn)。比如下面這個(gè)例子:
<div>
<p>
你猜晃跺,目標(biāo)在這里還是<span>哪里</span>
</p>
</div>
當(dāng)我們點(diǎn)擊“哪里“的時(shí)候揩局,目標(biāo)節(jié)點(diǎn)是<span></span>
,點(diǎn)擊這里的時(shí)候掀虎,目標(biāo)節(jié)點(diǎn)是<p></p>
凌盯,而當(dāng)我們點(diǎn)擊<p></p>
區(qū)域之外,<div></div>
區(qū)域之內(nèi)烹玉,目標(biāo)節(jié)點(diǎn)就是<div></div>
驰怎。
3.3 冒泡階段
當(dāng)事件到達(dá)目標(biāo)節(jié)點(diǎn)之后,就會(huì)沿著原路返回二打,這個(gè)過程有點(diǎn)類似于水泡從水底浮出水面的過程县忌,所以稱這個(gè)過程為冒泡階段。
現(xiàn)在再看addEventListener(eventName, handler, useCapture)
函數(shù)继效。第三個(gè)參數(shù)是useCapture
症杏,代表是否在捕獲階段進(jìn)行事件處理,如果是false瑞信,則在冒泡階段進(jìn)行事件處理厉颤,如果是true
則在捕獲階段進(jìn)行處理,默認(rèn)是false
凡简。
冒泡事件的流程剛好是事件捕獲的逆過程逼友。我們來看個(gè)事件冒泡的例子:
<div id="outer">
<div id="inner">
</div>
</div>
<script>
window.onclick = function() {
console.log('window');
};
document.onclick = function() {
console.log('document');
};
document.documentElement.onclick = function() {
console.log('html');
};
document.body.onclick = function() {
console.log('body');
};
outer.onclick = function(ev) {
console.log('outer');
};
inner.onclick = function(ev) {
console.log('inner');
}
</script>
// 輸出為 inner outter body html dcoument window
4.事件委托
JavaScript中,事件的委托表示給元素的父級(jí)或者祖級(jí)秤涩,甚至頁(yè)面帜乞,由他們來綁定事件,然后利用事件冒泡的基本原理溉仑,通過事件目標(biāo)對(duì)象進(jìn)行檢測(cè)挖函,然后執(zhí)行相關(guān)操作饥漫。
事件委托有兩個(gè)優(yōu)點(diǎn):
- 減少內(nèi)存消耗摊溶,提高性能
假設(shè)有一個(gè)列表,列表之中有大量的列表項(xiàng),我們需要在點(diǎn)擊每個(gè)列表項(xiàng)的時(shí)候響應(yīng)一個(gè)事件壕鹉。
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
如果給每個(gè)列表項(xiàng)都一一綁定函數(shù),那么對(duì)內(nèi)存的消耗是非常大的养涮,需要消耗更多性能鸭丛。借助事件委托,我們只需要給父容器ul
綁定方法即可梳庆,這樣不管點(diǎn)擊哪一個(gè)后代元素暖途,都會(huì)根據(jù)冒泡傳播的傳遞機(jī)制,把容器的click行為處罰膏执,然后把對(duì)應(yīng)的方法執(zhí)行驻售,根據(jù)事件源,我們可以知道點(diǎn)擊的是誰(shuí)更米,從而完成不同的事欺栗。
- 動(dòng)態(tài)綁定事件
在很多時(shí)候,我們需要通過用戶操作動(dòng)態(tài)的增刪列表項(xiàng)元素征峦,如果一開始給每個(gè)子元素綁定事件迟几,那么在列表發(fā)生變化時(shí),就需要從新給新增的元素綁定事件栏笆,給即將刪去的元素解綁事件类腮,如果用事件委托就會(huì)省去很多這樣的麻煩。
接下來我們來實(shí)現(xiàn)上例中父元素#list下的li元素的事件委托到他的父層元素上:
// 給父層元素綁定事件
document.getElementById('list').addEventListener('click', function(e) {
// 兼容性處理
var event = e || window.event;
var target = event.target || event.srcElement;
// 判斷是否匹配目標(biāo)元素
if (target.nodeName.toLocaleLowerCase === 'li') {
console.log('the content is:', target.innerHTML);
}
});
5.事件對(duì)象
DOM0
和DOM2
的事件處理程序都會(huì)自動(dòng)傳入event
對(duì)象蛉加,即觸發(fā)DOM
上的某個(gè)事件時(shí)蚜枢,會(huì)產(chǎn)生一個(gè)事件對(duì)象,里面包含著所有和事件有關(guān)的信息七婴。IE
中的event
對(duì)象取決于指定的事件處理程序的方法祟偷。
IE的
handler
會(huì)在全局作用域運(yùn)行,this === window
所以在IE中會(huì)有window.event
打厘、event
兩種情況修肠。
另外在IE
中,事件對(duì)象的屬性也不一樣户盯,對(duì)應(yīng)關(guān)系如下:
srcElement
=> target
returnValue
=> preventDefault()
cancelBubble
=> stopPropagation()
IE不支持事件捕獲嵌施,因而只能取消事件冒泡,但stopPropagtion
可以同時(shí)取消事件捕獲和冒泡莽鸭。
只有在事件處理程序期間吗伤,
event
對(duì)象才會(huì)存在,一旦事件處理程序執(zhí)行完成硫眨,event
對(duì)象就會(huì)被銷毀足淆。
1、event.preventDefault()
如果調(diào)用這個(gè)方法,默認(rèn)事件行為將不在觸發(fā)巧号。什么是默認(rèn)事件呢族奢?例如表單 - 點(diǎn)擊提交按鈕跳轉(zhuǎn)頁(yè)面、a標(biāo)簽?zāi)J(rèn)頁(yè)面跳轉(zhuǎn)或是錨點(diǎn)定位等丹鸿。
很多時(shí)候我們使用a標(biāo)簽僅僅是想當(dāng)做一個(gè)普通的按鈕越走,點(diǎn)擊實(shí)現(xiàn)一個(gè)功能,不想頁(yè)面跳轉(zhuǎn)靠欢,也不想錨點(diǎn)定位廊敌。
// 方法一
<a href="javascript:;">鏈接</a>
也可以通過JS方法來阻止,給其click事件綁定方法门怪,當(dāng)我們點(diǎn)擊A標(biāo)簽的時(shí)候骡澈,先觸發(fā)click事件,其次才會(huì)執(zhí)行自己的默認(rèn)行為
// 方法二
<a id="test" >鏈接</a>
<script>
test.onclick = function(e) {
e = e || window.event;
return false;
}
</script>
// 方法三
<a id="test" >鏈接</a>
<script>
test.onclick = function(e) {
e = e || window.event;
e.preventDefalut();
}
</script>
接下來我們看個(gè)例子:輸入框最多只能輸入六個(gè)字符掷空,如何實(shí)現(xiàn)秧廉?
// 例子5
<input type="text" id="tempInp">
<script>
tempInp.onkeydown = function(ev) {
ev = ev || window.event;
let val = this.value.trim();
let len = val.lenght;
if (len >= 6) {
this.value = val.substr(0, 6);
//阻止默認(rèn)行為去除特殊按鍵(DELETE\BACK-SPACE\方向鍵...)
let code = ev.which || ev.keyCode;
if (!/^(46|8|37|38|39|40)$/.test(code)) {
ev.preventDefault()
}
}
}
</script>
2.event.stopPropagation()
& event.stopImmediatePropagation()
event.stopPropagation()
方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執(zhí)行(一般我們認(rèn)為stopPropagation是用來阻止事件冒泡的拣帽,其實(shí)該函數(shù)也可以阻止捕獲事件)。上面提到事件冒泡階段是指事件從目標(biāo)節(jié)點(diǎn)紫霞而上的向window
對(duì)象傳播的階段嚼锄。我們?cè)谏厦娴睦又械?code>inner元素click
事件上减拭,添加event.stopPropagation()
這句話后,就阻止了父事件的執(zhí)行区丑,最后只打印了'inner'
拧粪。
inner.onclick = function(ev) {
console.log('inner');
ev.stopPropagation();
}
stopImmediatePropagation 既能阻止事件向父元素冒泡,也能阻止元素同事件類型的其它監(jiān)聽器被觸發(fā)沧侥。而 stopPropagation 只能實(shí)現(xiàn)前者的效果可霎。我們來看個(gè)例子:
<body>
<button id="btn">click me to stop propagation</button>
</body>
<script>
const btn = document.querySelector('#btn');
btn.addEventListener('click', event => {
console.log('btn click 1');
event.stopImmediatePropagation();
});
btn.addEventListener('click', event => {
console.log('btn click 2');
});
document.body.addEventListener('click', () => {
console.log('body click');
});
// btn click 1
</script>
如上所示,使用stopImmediatePropagation
后宴杀,點(diǎn)擊按鈕時(shí)癣朗,不僅body
綁定事件不會(huì)觸發(fā),與此同時(shí)按鈕的另一個(gè)點(diǎn)擊事件也不觸發(fā)旺罢。
event.target & event.currentTarget
event.target
指向引起觸發(fā)事件的元素旷余,而event.currentTarget
則是事件綁定的元素,只有被點(diǎn)擊的那個(gè)目標(biāo)元素的event.target
才會(huì)等于event.currentTarget
扁达。也就是說正卧,event.currentTarget始終是監(jiān)聽事件這,而event.target是事件的真正發(fā)出者跪解。
6.捕獲與冒泡的順序問題
當(dāng)有很多層交互嵌套時(shí)炉旷,事件捕獲和時(shí)間冒泡的先后順序看起來是不好確定的。下面分5種情況討論給它們的順序,以及如何規(guī)避意外情況的發(fā)生窘行。
1饥追、在外層div注冊(cè)事件,點(diǎn)擊內(nèi)層div來觸發(fā)事件時(shí)抽高,捕獲事件總是要比冒泡事件先觸發(fā)(與代碼順序無關(guān))
假設(shè)判耕,有這樣的html結(jié)構(gòu):
<div id="test" class="test">
<div id="testInner" class="test-inner">
</div>
</div>
然后,我們?cè)谕鈱觗iv上注冊(cè)兩個(gè)click事件翘骂,分別是捕獲事件和冒泡事件壁熄,代碼如下:
const btn = document.getElementById("test");
// 捕獲事件
btn.addEventListener("click", function(e) {
alert("capture is ok");
}, true);
// 冒泡事件
btn.addEventListener("click", function(e) {
alert("bubble is ok");
}, false)
點(diǎn)擊內(nèi)層的div,先彈出capture is ok碳竟,后彈出bubble is ok草丧。只有當(dāng)真正觸發(fā)事件的 DOM元素是內(nèi)層時(shí),外層DOM元素才有機(jī)會(huì)模擬捕獲事件和冒泡事件莹桅。
2昌执、當(dāng)在觸發(fā)事件的DOM元素上注冊(cè)事件時(shí),那個(gè)先注冊(cè)就先執(zhí)行那個(gè)
html 結(jié)構(gòu)同上诈泼,js代碼如下:
const btnInner = document.getElementById("testInner");
// 冒泡事件
btnInner.addEventListener("click", function(e) {
alert("bubble is ok");
}, false);
// 捕獲事件
btnInner.addEventListener("click", function(e) {
alert("caapture is ok");
}, true);
在本例中懂拾,冒泡事件先注冊(cè),所以先執(zhí)行铐达。所以岖赋,點(diǎn)擊內(nèi)層div,先彈出bubble is ok
,再?gòu)棾?code>caputre is ok瓮孙。
3唐断、當(dāng)外層div和內(nèi)層div同時(shí)注冊(cè)了捕獲事件時(shí),點(diǎn)擊內(nèi)層的div時(shí),外層div的事件一定會(huì)先觸發(fā)
const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");
btnInner.addEventListener("click", function(e) {
alert("inner capture is ok");
}, true);
btn.addEventListener("click", function(e) {
alert("outer capture is ok");
}, true)
雖然外層 div 的事件注冊(cè)在后面,但會(huì)先觸發(fā)杭抠。所以脸甘,結(jié)果是先彈出 outer capture is ok
,再?gòu)棾?inner capture is ok
偏灿。
4丹诀、同理,當(dāng)外層div和內(nèi)層div都同時(shí)注冊(cè)了冒泡事件菩混,點(diǎn)擊內(nèi)層div時(shí)忿墅,一定是內(nèi)層div事件先觸發(fā)。
const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");
btn.addEventLisntener("click", function(e) {
alert("outer bubble is ok");
}, false);
btnInner.addEventListener("click", function(e) {
alert("inner bubble is ok");
}, false)
先彈出inner bubble is ok
沮峡,再?gòu)棾?code>outer bubble is ok疚脐。
5、阻止事件的派發(fā)
通常情況下邢疙,我們都希望點(diǎn)擊某個(gè)div
時(shí)棍弄,就觸發(fā)自己的事件回調(diào)望薄。比如,明明點(diǎn)擊的是內(nèi)層div
呼畸,但是外層div
的事件也觸發(fā)了痕支,這就不是我們想要的了。這時(shí)蛮原,就需要阻止事件的派發(fā)卧须。
事件觸發(fā)時(shí),會(huì)默認(rèn)傳入一個(gè)event
對(duì)象儒陨,這個(gè)event
對(duì)象上有一個(gè)方法:stopPropagation
花嘶。MDN上的解釋是:阻止捕獲和冒泡階段中,當(dāng)前事件的進(jìn)一步傳播蹦漠。所以通過此方法椭员,讓外層div
接收不到事件,自然也就不會(huì)觸發(fā)了笛园。
btnInner.addEventListener("click", function(e) {
// 阻止冒泡
e.stopPropagation();
alert(”inner bubble is ok“);
}, false);