DOM事件模型 在0級DOM事件模型中,它只是簡單的執行你為它綁定的事件,比如你為某個元素添加了一個onclick事件,當事件觸發時,它只是去調用我們綁定的那個方法,不再做其他的操作。 在2級DOM事件模型中,就比較複雜一些,它將不再是單純的調用一下自身綁定的事件就完事了,它還擁有機會去處理它的祖先 ...
DOM事件模型
在0級DOM事件模型中,它只是簡單的執行你為它綁定的事件,比如你為某個元素添加了一個onclick事件,當事件觸發時,它只是去調用我們綁定的那個方法,不再做其他的操作。
在2級DOM事件模型中,就比較複雜一些,它將不再是單純的調用一下自身綁定的事件就完事了,它還擁有機會去處理它的祖先節點,在DOM2級事件模型中,它有一個事件傳播過程,分為3個階段,從“事件捕獲”Document開始來到“目標節點”再從“目標節點”冒泡回Document對象,舉段代碼
<div id="div">
<a href="javascript:;">DOM事件模型</a>
</div>
<script>
var div = document.getElementById("div");
var a = div.children[0];
document.onclick = function(){
console.log("document");
};
a.onclick = function(){
console.log("a");
};
div.onclick = function(){
console.log("div");
};
</script>
可以看到我只是點擊了a元素,但是div和document綁定的事件也被觸發了,這就是DOM2級和1級的區別,同時你也看到,它是先輸出的a,而不是div和document,雖然說它有3個階段,但瀏覽器預設是在冒泡階段才執行的,如果不這樣的話,我們點擊a元素就會執行多次啦。
如果你想讓瀏覽器在捕獲階段執行,那麼就不能直接使用onclick添加事件了,而是要使用addEventListener添加事件,它的第三個參數就是用來設置在哪個階段執行,具體可以看 http://www.runoob.com/jsref/met-element-addeventlistener.html
currentTarget
event.currentTarget獲取到的是當前綁定事件的那個對象,也因為此原因,他獲取到的常常和this一樣,下麵是一個示例:
<a href="javascript:;" id="a">evnet.currentTarget</a>
<script>
var a = document.getElementById("a");
a.onclick = function(event){
console.log("this:",this);
console.log("currentTarget:",event.currentTarget);
};
</script>
當我點擊a標簽時,this和currentTarget列印出來的都是a標簽本身,如下圖
既然如此這個currentTarget有啥用呢,這是一開始的想法,但隨後發現不對,想到這個currentTarget始終獲取到的是那個綁定事件的對象,但this卻有很大的不同,因為this並不關心是誰綁定的它,它只關心是誰執行的它,因此如果再將上面那段代碼改造改造,我們就會發現,它們真的是不一樣的,代碼如下:
var a = document.getElementById("a");
a.onclick = function(event){
(function(){
console.log("this:",this);
console.log("currentTarget:",event.currentTarget);
}());
};
效果如圖
這也就是說,某些時候如果不能通過this來獲取綁定事件的對象時,就可以使用event.currentTarget。
有些人認為event.currentTarget就是this,其實不然,容易把event.currentTarget當成this,主要原因就是,在事件處理器中,我們常常使用的是this,而不是event.currentTarget,至於為什麼,反正我是因為從接觸js開始,所看過的教程上都是那麼用的,時間長了,竟然忘了一件事,event.currentTarget才是真的屬於事件處理器的,而this不過是個冒牌貨。
target
event.target獲取到的是觸發事件的那個元素。
網上常說的事件委派,就是通過event.target來實現的,所謂的事件委派,就是我並不給某個具體的東西添加事件,而通過給它的父輩添加事件,當我點擊那個具體的元素時,父輩的事件會觸發(因為有事件冒泡),而這時就需要用到evnet.target了,因為在父輩的事件中this並不指向當前點擊的那個元素,但是event.target可以獲取到是誰觸發的當前事件。以下是一個示例
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
<script>
var ul = document.getElementById("ul");
ul.onclick = function(event){
console.log(event.target);
};
</script>
當我點擊第二個li時,輸出如下值
當然我們也可以直接給這三個li添加事件,但是那樣的話就綁定了3次事件,如果有1萬個元素,就綁定了1萬次,而通過事件委派則只需要綁定一次。
最主要倒也不是說不能給li添加事件,而是如果這些li並不是事先添加的,而是通過後端返回的數據,再渲染的,那麼要是我們再放回數據之前就給li添加事件,那麼就會有問題,因為根本就不存在li元素,也就是說後添加的元素無法事先去添加事件,比如下麵這段代碼就有些問題。
<ul id="ul">
<li>1111</li>
</ul>
<script>
var ul = document.getElementById("ul");
var lis = ul.children;
for(var i=0;i<lis.length;i++){
lis[i].onclick = function(){
console.log(this);
};
}
var li = document.createElement("li");
li.innerText = "2222";
ul.appendChild(li);
</script>
當我點擊第二個li時,什麼都沒有輸出,如下圖
因為第二個li是在for迴圈以後添加的,所有並沒有給第二個li添加上事件。而如果是給ui添加事件,那就不一樣了,代碼如下
<ul id="ul">
<li>1111</li>
</ul>
<script>
var ul = document.getElementById("ul");
ul.onclick = function(event){
console.log(event.target);
};
var li = document.createElement("li");
li.innerText = "2222";
ul.appendChild(li);
</script>
不管我點擊第幾個li都可以正常的輸出,如下圖
不過因為event.target獲取到的是最終觸發這個事件的元素,所以在寫代碼的時候,我們經常需要加上判斷,因為通過event.target獲取到的不一定是我們想要的元素,比如下麵這個例子
<ul id="ul">
<li>
<em>111</em>
</li>
</ul>
<script>
var ul = document.getElementById("ul");
ul.onclick = function(event){
console.log(event.target);
};
</script>
當我點擊111的時候,獲取到的是em標簽,如下圖
但我想要的是li,因此我們就得加上判斷,我經常使用的一招就是,通過判斷元素的標簽名,代碼如下
<ul id="ul">
<li>
<em>111</em>
</li>
</ul>
<script>
var ul = document.getElementById("ul");
ul.onclick = function(event){
var target = event.target;
if(!(target.tagName.toLowerCase()==="li")){
target = target.parentNode;
}
if(target.tagName.toLowerCase()==="li"){
console.log(target);
}
};
</script>
tagName可以獲取到元素名,但是每個瀏覽器獲取到的都有可能不同,有些瀏覽器獲取到的是大寫的標簽名,有些瀏覽器獲取到的是小寫的標簽名,因此在上面那段代碼中,將標簽名都轉換成小寫的,通過判斷標簽名來確定是不是我要的元素。
雖然這種判斷可行,但仔細想想也能想到,這個方法,也是有缺陷的,如果DOM比較複雜,則容易判斷錯誤,目前還沒有想到更好的方法。
記住正是因為有了事件捕獲和事件冒泡才有了event.target的用武之處。
relatedTarget
在event中有一個relatedTarget屬性,它可以獲取到和它相關的元素(通過誰來到這個元素上的,要到哪個元素上去),舉個例子
<div id="box">
<p>新的起點,新的夢想。</p>
</div>
<script>
var box = document.getElementById("box");
box.onmouseout = function(event){
console.log(event.relatedTarget);
};
</script>
在onmouseout中relatedTarget可以獲取到它要到哪個元素上去,相反在onmouseover中relatedTarget獲取到的是從哪個元素來的,代碼如下
<div id="box">
<p>新的起點,新的夢想。</p>
</div>
<script>
var box = document.getElementById("box");
box.onmouseover = function(event){
console.log(event.relatedTarget);
};
</script>
可以看到當我從html移入到div上時,relatedTarget獲取到的是html,也就是它從html來到div的。
想起一句話:我從哪裡來,又要到哪裡去。
當第一次看到這個屬性的時候,給我的感覺是,它肯定是很有用的,事實上也確實有些用處,如果細心的朋友,看上面的那個動畫圖,會發現一件事,onmouseover和onmouseout存在一個問題,離開或進入它的子元素事件也會被觸發。大多數情況我們是不希望這樣的,因此如果使用onmouseover或onmouseout時,最好判斷一下,是否真的移出了盒子。
在元素中有一個contains方法,可以用來判斷某個元素是否是它的子元素,如果是返回true,否則返回false,而以上問題我們就可以通過這個方法來寫,代碼如下
<div id="box">
<p>1111111
<em>新的起點,新的夢想。</em>
</p>
</div>
<script>
var box = document.getElementById("box");
box.onmouseout = function(event){
if(!this.contains(event.relatedTarget)){
console.log(this);
}
};
</script>
如果你只是想解決以上這個問題,那麼大可不必這樣寫,因為在瀏覽器中,分別有兩個和它們相同的事件,但它們並沒有這個問題,分別是onmouseenter滑鼠移入事件和onmouseleave滑鼠移出事件。
需要註意的是relatedTarget屬性只對onmouseout和onmouseover事件有用,雖然對onmouseenter和onmouseleave事件也有用,不過很少有人會那麼去用,因為我發現relatedTarget屬性除了用在解決上面那個問題以外,還真沒發現有什麼其他的用處。
註意點
需要註意一點,在普通函數中是不存在event對象的,只有在事件中才有,比如這麼這段代碼,如果使用event就會輸出undefined。
(function(event){
console.log(event); //undefined
})();
自定義事件
官方提供了一些如onclick、onmouseover、onscroll等事件給我們使用,但難免也會有自己建立事件的需求,比如監聽某個變數的變化,雖然聽起來好像不太可能,但方法總是有的,我們不讓使用者直接操作某個變數,而是提供一個方法給他操作,下麵是一段實現思路。
<script>
var Foo = (function(){
var a = 10;
return {
get:function(){
return a;
},
set:function(value){
if(a!==value){
a = value;
this.change();
}
},
change:function(){
console.log("a的值有變化");
}
};
})();
Foo.set(9);
Foo.set(52);
</script>
以上我將變數a封死在函數作用域裡面,讓外部無法直接操作變數a,要操作就只能通過調用我事先設置的set方法,之所以要這樣弄是因為我們是無法知道變數是什麼時候改變了的,而如果讓使用者操作我們提供事先提供的方法,那麼我們就可以對其進行處理了。
以上並不是一個自定義事件,如果想要實現自定義方法,我們可以通過javascript提供的3個方法來弄,分別是document.createEvent()、event.initEvent()、element.dispatchEvent()。
- createEvent用來創建一個新的事件
- initEvent用來初始化事件
- dispatchEvent用來觸發事件
具體可以看 http://www.w3school.com.cn/xmldom/met_event_initevent.asp
實現如下
<script>
var ev = document.createEvent("HTMLEvents");
ev.initEvent("changeA",false,false);
var Foo = (function(){
var a = 10;
return {
get:function(){
return a;
},
set:function(value){
if(a!==value){
a = value;
document.dispatchEvent(ev);
}
}
};
})();
document.addEventListener("changeA",function(){
console.log("a有變化",Foo.get());
});
Foo.set(555);
Foo.set(520);
</script>
以上也就是將set中的change方法改成dispatchEvent,用來觸發changeA事件,如果你想要解綁這個事件,和你給onclick事件解綁是一樣的。
IE attachEvent之坑
當我們通過attachEvent添加事件時,需要註意一件事,我們為它添加的事件函數,IE並沒有將這個函數作為元素的方法調用,而是作為全局函數來調用,因此,它裡面的this並不指向當前綁定的事件對象,而是window,並且event也不指向當前事件對象,看下麵這段代碼
<a href="javascript:;" id="a">addEventListener</a>
<script>
var a = document.getElementById("a");
a.attachEvent("onclick",function(){
console.log("this:",this);
console.log("currentTarget:",event.currentTarget);
});
</script>
當我點擊a標簽時,輸出如下: