事件
JavaScript和HTML的交互是通過事件實(shí)現(xiàn)的。JavaScript采用異步事件驅(qū)動(dòng)編程模型,當(dāng)文檔、瀏覽器、元素或與之相關(guān)對(duì)象發(fā)生特定事情時(shí),瀏覽器會(huì)產(chǎn)生事件。如果JavaScript關(guān)注特定類型事件,那么它可以注冊(cè)當(dāng)這類事件發(fā)生時(shí)要調(diào)用的句柄。事件是某個(gè)行為或者觸發(fā),比如點(diǎn)擊、鼠標(biāo)移動(dòng).....
- 當(dāng)用戶點(diǎn)擊鼠標(biāo)時(shí)
- 當(dāng)網(wǎng)頁已加載時(shí)
- 當(dāng)圖像已加載時(shí)
- 當(dāng)鼠標(biāo)移動(dòng)到元素上時(shí)
- 當(dāng)用戶觸發(fā)按鍵時(shí)...
事件流
事件流描述的是從頁面中接收事件的順序,比如有兩個(gè)嵌套的div,點(diǎn)擊了內(nèi)層的div,這時(shí)候是內(nèi)層的div先觸發(fā)click事件還是外層先觸發(fā)?目前主要有三種模型:
事件冒泡:事件開始時(shí)由最具體的元素接收,然后逐級(jí)向上傳播到較為不具體的元素
事件捕獲:不太具體的節(jié)點(diǎn)更早接收事件,而最具體的元素最后接收事件,和事件冒泡相反
DOM事件流:DOM2級(jí)事件規(guī)定事件流包括三個(gè)階段,事件捕獲階段,處于目標(biāo)階段,事件冒泡階段,首先發(fā)生的是事件捕獲,為截取事件提供機(jī)會(huì),然后是實(shí)際目標(biāo)接收事件,最后是冒泡階段
事件處理程序
我們也稱之為事件偵聽器(listener),事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作。比如click、load、mouseover等,都是事件類型(俗稱事件名稱),而響應(yīng)某個(gè)事件的方法就叫做事件處理程序或者事件監(jiān)聽器。
也就是我們需要提前定義好某些事件發(fā)生了該怎么處理,這個(gè)過程叫做綁定事件處理程序。
給元素添加事件處理程序
方法一:HTML內(nèi)聯(lián)方式
元素支持的每個(gè)事件都可以使用一個(gè)相應(yīng)事件處理程序同名的HTML屬性指定。這個(gè)屬性的值應(yīng)該是可以執(zhí)行的JavaScript代碼,我們可以為一個(gè)button添加click事件處理程序
<input type="button" value="Click Here" onclick="alert('Clicked!');" />
在HTML事件處理程序中可以包含要執(zhí)行的具體動(dòng)作,也可以調(diào)用在頁面其它地方定義的腳本,剛才的例子可以寫成這樣
<input type="button" value="Click Here" onclick="showMessage();" />
在HTML中指定事件處理程序書寫很方便,但是有兩個(gè)缺點(diǎn):
存在加載順序問題,如果事件處理程序在html代碼之后加載,用戶可能在事件處理程序還未加載完成時(shí)就點(diǎn)擊按鈕之類的觸發(fā)事件,存在時(shí)間差問題
這樣書寫html代碼和JavaScript代碼緊密耦合,維護(hù)不方便
方法二:JavaScript指定事件處理程序
通過JavaScript指定事件處理程序就是把一個(gè)方法賦值給一個(gè)元素的事件處理程序?qū)傩浴?/p>
每個(gè)元素都有自己的事件處理程序?qū)傩?,這些屬性名稱通常為小寫,如onclick等,將這些屬性的值設(shè)置為一個(gè)函數(shù),就可以指定事件處理程序,如下
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.onclick = function showMessage() {
alert(this.id);
};
</script>
這樣處理,事件處理程序被認(rèn)為是元素的方法,事件處理程序在元素的作用域下運(yùn)行,this就是當(dāng)前元素,所以點(diǎn)擊button結(jié)果是:btnClick
這樣還有一個(gè)好處,我們可以刪除事件處理程序,只需把元素的onclick屬性賦為null即可。
事件對(duì)象
在觸發(fā)DOM上的某個(gè)事件的時(shí)候會(huì)產(chǎn)生一個(gè)事件對(duì)象event,這個(gè)對(duì)象包含著所有與事件有關(guān)的信息,包括產(chǎn)生事件的元素、事件類型等相關(guān)信息。所有瀏覽器都支持event對(duì)象,但支持方式不同。
DOM中的事件對(duì)象
兼容DOM的瀏覽器會(huì)產(chǎn)生一個(gè)event對(duì)象傳入事件處理程序中。event對(duì)象包含與創(chuàng)建它的特定事件有關(guān)的屬性和方法,觸發(fā)事件的類型不同,可用的屬性和方法也不同,但是所有事件都會(huì)包含
在事件處理程序內(nèi)部,this始終等同于currentTarget,而target是事件的實(shí)際目標(biāo)。
要阻止事件的默認(rèn)行為,可以使用preventDefault()方法,前提是cancelable值為true,比如我們可以阻止鏈接導(dǎo)航這一默認(rèn)行為
document.querySelector('#btn').onclick = function (e) {
e.preventDefault();
}
stopPropagation()方法可以停止事件在DOM層次的傳播,即取消進(jìn)一步的事件捕獲或冒泡。我們可以在button的事件處理程序中調(diào)用stopPropagation()從而避免注冊(cè)在body上的事件發(fā)生
var handler = function (e) {
alert(e.type);
e.stopPropagation();
}
addEvent(document.body, 'click', function () { alert('Clicked body')});
var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);
若是注釋掉e.stopPropagation(); 在點(diǎn)擊button的時(shí)候,由于事件冒泡,body的click事件也會(huì)觸發(fā),但是調(diào)用這句后,事件會(huì)停止傳播
IE中的事件對(duì)象
訪問IE中的event對(duì)象有幾種不同的方式,取決于指定事件處理程序的方法。直接為DOM元素添加事件處理程序時(shí),event對(duì)象作為window對(duì)象的一個(gè)屬性存在
var handler = function () {
var e = window.event;
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
btnClick.onclick = handler;
我們通過window.event取得了event對(duì)象,并檢測(cè)到了其類型,可是如果事件處理程序是通過attachEvent添加的,那么就會(huì)有一個(gè)event對(duì)象被傳入事件處理程序中
var handler = function (e) {
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
attachEvent(btnClick, handler);
當(dāng)然這時(shí)候也可以通過window對(duì)象訪問event,方便起見,我們一般會(huì)傳入event對(duì)象,IE中所有的事件都包含以下屬性方法
跨瀏覽器的事件對(duì)象
雖然DOM和IE的event對(duì)象不同,但基于它們的相似性,我們還是可以寫出跨瀏覽器的事件對(duì)象方案
function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
return e.target || e.scrElement;
}
function preventDefault(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
1: DOM0 事件和DOM2級(jí)在事件監(jiān)聽使用方式上有什么區(qū)別?
在W3C對(duì)DOM事件進(jìn)行規(guī)范化之前的事件處理,一般稱為DOM0級(jí)事件處理程序。W3C在DOM2級(jí)文檔規(guī)范中,包含了DOM 事件(DOM Events)規(guī)范,也就是我們通常所說的DOM2級(jí)事件處理程序。
DOM0級(jí)事件處理方式:
- Dom0級(jí)事件處理程序是將一個(gè)函數(shù)賦值給一個(gè)事件處理程序?qū)傩裕ㄟ^將事件處理程序設(shè)置為null刪除綁定在元素上的事件處理程序。這種方法無法給一個(gè)事件添加多個(gè)事件處理程序,一個(gè)事件只能綁定一次,后面的程序會(huì)覆蓋前面的程序。
// 添加事件處理程序
var btn=document.querySelector("#btn");
btn.onclick=function () {
alert(this.id);
}
// 刪除事件處理程序
btn.onclick=null;
優(yōu)點(diǎn): 簡(jiǎn)單,兼容性好。
缺點(diǎn): 一個(gè)事件處理程序只能對(duì)應(yīng)一個(gè)處理函數(shù),設(shè)置第二個(gè)事件時(shí)候,因?yàn)槭琴x值,所以第二個(gè)事件會(huì)覆蓋第一個(gè)事件。
DOM2級(jí)事件處理方式:
- DOM2級(jí)事件處理方式指定了,添加事件處理程序和刪除事件處理程序的方法。DOM2級(jí)定義了addEventListener()和removeEventListener()用于處理指定和刪除事件處理程序。所有Dom節(jié)點(diǎn)都包含這兩個(gè)方法,并且它們都接受3個(gè)參數(shù),要處理的事件名、作為事件處理程序的函數(shù)和一個(gè)布爾值。最后這個(gè)布爾值參數(shù)如果是true,表示在捕獲階段調(diào)用事件處理程序;如果是false,表示在冒泡階段調(diào)用事件處理程序。
addEventListener(eventName,func,isPuhuo); //添加事件
removeEventListener(eventName,func,isPuhuo); //刪除事件
/*參數(shù)分別是,事件處理屬性名稱,處理函數(shù),是否在捕獲時(shí)執(zhí)行事件處理函數(shù),
默認(rèn)是false,即在冒泡階段執(zhí)行
*/
應(yīng)用舉例
var btn = document.getElementById("btn");
var cancel = document.getElementById("cancel")
function fn(){
console.log("I am clicked 123");
}
btn.addEventListener("click",fn) //點(diǎn)擊btn事件,執(zhí)行函數(shù)fn
cancel.addEventListener("click",function(){
btn.removeEventListener("click",fn)
}) //點(diǎn)擊cancel事件,執(zhí)行刪除btn的綁定事件
通過removeEventListener()函數(shù)來移除事件處理程序時(shí),移除時(shí)的參數(shù)必須與添加處理程序時(shí)使用的參數(shù)相同,這也意味著通過addEventListener()添加的匿名函數(shù)將無法移除。如:
var btn=document.querySelector("#btn");
// 指定事件處理程序
btn.addEventListener("click",function(){
alert(this.id);
},false);
// 刪除事件處理程序,不會(huì)生效
btn.removeEventListener("click",function(){
alert(this.id);
},false);
注:
(a) eventName
的值均不含on,例如注冊(cè)鼠標(biāo)點(diǎn)擊事件eventName
為“click”
(b) 處理函數(shù)中的this依然指的是指當(dāng)前dom元素
(c) 通過addEventListener
添加的事件處理程序,只能通過removeEventListener
來刪除,也就是說通過addEventListener
添加的匿名函數(shù)將無法被刪除。
二者區(qū)別:使用Dom2級(jí)方法添加事件處理程序的主要好處是可以添加多個(gè)事件處理程序,而Dom0級(jí)為一個(gè)事件添加多個(gè)事件處理程序時(shí),后面的程序會(huì)覆蓋前面的。
2: attachEvent與addEventListener的區(qū)別?
參數(shù)個(gè)數(shù)不相同,這個(gè)最直觀,addEventListener有三個(gè)參數(shù),attachEvent只有兩個(gè),attachEvent添加的事件處理程序只能發(fā)生在冒泡階段,addEventListener第三個(gè)參數(shù)可以決定添加的事件處理程序是在捕獲階段還是冒泡階段處理(我們一般為了瀏覽器兼容性都設(shè)置為冒泡階段)
第一個(gè)參數(shù)意義不同,addEventListener第一個(gè)參數(shù)是事件類型(比如click,load),而attachEvent第一個(gè)參數(shù)指明的是事件處理函數(shù)名稱(onclick,onload)
事件處理程序的作用域不相同,addEventListener的作用域是元素本身,this是指的觸發(fā)元素,而attachEvent事件處理程序會(huì)在全局變量?jī)?nèi)運(yùn)行,this是window,所以剛才例子才會(huì)返回undefined,而不是元素id
為一個(gè)事件添加多個(gè)事件處理程序時(shí),執(zhí)行順序不同,addEventListener添加會(huì)按照添加順序執(zhí)行,而attachEvent添加多個(gè)事件處理程序時(shí)順序無規(guī)律(添加的方法少的時(shí)候大多是按添加順序的反順序執(zhí)行的,但是添加的多了就無規(guī)律了),所以添加多個(gè)的時(shí)候,不依賴執(zhí)行順序的還好,若是依賴于函數(shù)執(zhí)行順序,最好自己處理,不要指望瀏覽器。
3: 解釋IE事件冒泡和DOM2事件傳播機(jī)制?
IE事件冒泡:
- 事件從目標(biāo)元素向父級(jí)元素傳遞,直到傳遞到 window ( document) 停止。
DOM2級(jí)事件傳播:有3個(gè)階段:
- 捕獲階段:事件從document向子元素一層層傳遞,直到目標(biāo)元素
處于目標(biāo)階段:此時(shí)事件發(fā)生在目標(biāo)元素上,被看做冒泡的一部分
冒泡階段:和IE冒泡一樣,從目標(biāo)元素向父級(jí)元素傳遞,直到document停止。
4:如何阻止事件冒泡? 如何阻止默認(rèn)事件?
兼容DOM的瀏覽器:
- 阻止默認(rèn)事件:
e.preventDefault();
event.preventDefault():如果事件可取消,則取消該事件,而不停止事件的進(jìn)一步傳播。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>event.preventDefault()</title>
</head>
<body>
<p>請(qǐng)點(diǎn)擊復(fù)選框控件</p>
<form>
<label for="id-checkbox">Checkbox</label>
<input type="checkbox" id="id-checkbox" name="checkbox" />
</form>
<script>
document.querySelector("#id-checkbox").addEventListener("click", function(event){
alert("preventDefault會(huì)阻止該復(fù)選框被勾選.")
event.preventDefault();
//阻止該復(fù)選框被勾選
}, false);
</script>
</body>
</html>
- 阻止事件冒泡:
e.stopPropagation();
event.stopPropagation(): 阻止捕獲和冒泡階段中當(dāng)前事件的進(jìn)一步傳播。
<!DOCTYPE html>
<html>
<head>
<title>Event Propagation</title>
<style type="text/css">
#t-daddy { border: 1px solid red }
#c1 { background-color: pink; }
</style>
<script type="text/javascript">
function stopEvent(ev) {
c2 = document.getElementById("c2");
c2.innerHTML = "hello";
// this ought to keep t-daddy from getting the click.
ev.stopPropagation();
alert("event propagation halted.");
}
function load() {
elem = document.getElementById("tbl1");
elem.addEventListener("click", stopEvent, false);
}
</script>
</head>
<body onload="load();">
<table id="t-daddy" onclick="alert('hi');">
<tr id="tbl1">
<td id="c1">one</td>
</tr>
<tr>
<td id="c2">two</td>
</tr>
</table>
</body>
</html>
IE9 之前的IE瀏覽器:
阻止默認(rèn)冒泡:
e.cancelBubble = true;
阻止默認(rèn)事件:
event.returnValue = false;
5:有如下代碼,要求當(dāng)點(diǎn)擊每一個(gè)元素li時(shí)控制臺(tái)展示該元素的文本內(nèi)容。不考慮兼容
<ul class="ct">
<li>這里是</li>
<li>秦皇島</li>
<li>東北大學(xué)秦皇島分校</li>
</ul>
<script>
//todo ...
</script>
----------------------------------------------------------
<ul class="ct">
<li>這里是</li>
<li>秦皇島</li>
<li>東北大學(xué)秦皇島分校</li>
</ul>
<script>
var ct = document.querySelector('.ct');
ct.addEventListener('click',function(e){
console.log(e.target.innerText)
});
</script>
6: 補(bǔ)全代碼要求:(1)當(dāng)點(diǎn)擊按鈕開頭添加時(shí)在<li>這里是</li>元素前添加一個(gè)新元素,內(nèi)容為用戶輸入的非空字符串;當(dāng)點(diǎn)擊結(jié)尾添加時(shí)在最后一個(gè) li 元素后添加用戶輸入的非空字符串;(2)當(dāng)點(diǎn)擊每一個(gè)元素li時(shí)控制臺(tái)展示該元素的文本內(nèi)容。
<ul class="ct">
<li>這里是</li>
<li>秦皇島</li>
<li>東北大學(xué)秦皇島分校</li>
</ul>
<input class="ipt-add-content" placeholder="添加內(nèi)容"/>
<button id="btn-add-start">開頭添加</button>
<button id="btn-add-end">結(jié)尾添加</button>
<script>
//你的代碼
</script>
---------------------------------------------------------------------
<ul class="ct">
<li>這里是</li>
<li>秦皇島</li>
<li>東北大學(xué)秦皇島分校</li>
</ul>
<input class="ipt-add-content" placeholder="添加內(nèi)容"/>
<button id="btn-add-start">開頭添加</button>
<button id="btn-add-end">結(jié)尾添加</button>
<script>
var ct = document.querySelector('.ct'),
ipt = document.querySelector('.ipt-add-content'),
addStart = document.querySelector('#btn-add-start'),
addEnd = document.querySelector('#btn-add-end');
ct.addEventListener('click',function(e)){
if(e.target.tagName.toLowerCase() === 'li'){
console.log(e.target.innerText);
}
});
addStart.addEventListener('click',function(){
var li = document.createElement('li');
li.innerText = ipt.value;
if (li.innerText === "") {
alert("please input content");
} else {
ct.inserBefore(li,ct.firstChild);
}
});
addEnd.addEventListener('click',function(){
var li = document.createElement('li');
li.innerText = ipt.value;
if (li.innerText === "") {
alert("please input content")
} else {
ct.appendChild(li);
}
})
</script>
7: 補(bǔ)全代碼,要求:當(dāng)鼠標(biāo)放置在li元素上,會(huì)在img-preview里展示當(dāng)前l(fā)i元素的data-img對(duì)應(yīng)的圖片。
<ul class="ct">
<li data-img="1.png">鼠標(biāo)放置查看圖片1</li>
<li data-img="2.png">鼠標(biāo)放置查看圖片2</li>
<li data-img="3.png">鼠標(biāo)放置查看圖片3</li>
</ul>
<div class="img-preview"></div>
<script>
//你的代碼
</script>
-------------------------------------------------------------
<body>
<ul class="ct">
<li data-img="http://cdn.jirengu.com/book.jirengu.com/img/11.jpg">鼠標(biāo)放置查看圖片1</li>
<li data-img="http://cdn.jirengu.com/book.jirengu.com/img/13.jpg">鼠標(biāo)放置查看圖片2</li>
<li data-img="http://cdn.jirengu.com/book.jirengu.com/img/14.jpg">鼠標(biāo)放置查看圖片3</li>
</ul>
<div class="img-preview"></div>
<script>
function $(id) {
return document.querySelector(id);
}
function $$(cls) {
return document.querySelectorAll(cls);
}
var ct = $('.ct')
var childs = $$('li')
var preview = $('.img-preview')
//直接在每一個(gè)元素上監(jiān)聽
for (var i = 0; i < childs.length; i++) {
childs[i].addEventListener('mouseenter', function () {
var dataImg = this.getAttribute('data-img');
preview.innerHTML = ''
})
}
//監(jiān)聽父元素
ct.addEventListener('mouseover', function (e) {
if (e.target.tagName.toLowerCase() === 'li') {
var img = document.createElement('img');
img.src = e.target.getAttribute('data-img');
preview.appendChild(img);
}
})
ct.addEventListener('mouseout', function (e) {
if (e.target.tagName.toLowerCase() === 'li') {
preview.innerHTML = '';
}
});
</script>
</body>