js之事件機制
1、事件初探
1.1 js事件的概述
JavaScript事件:JavaScript是基于事件驅(qū)動模型的,所有的內(nèi)容幾乎都可以和事件掛鉤乓搬。它首先定義了一些事件,然后在具體的某個環(huán)境觸發(fā)了該事件,再完成相應(yīng)的操作躯泰。
事件可以這么理解:它擁有事件三要素(事件源、事件华糖、監(jiān)聽器)麦向。
- 事件源:在哪個元素上發(fā)生的,p客叉、a诵竭、div、form表單
- 事件:到底發(fā)生了什么事件兼搏,click卵慰、mouseover、load佛呻、submit裳朋、focus
- 監(jiān)聽器:如何應(yīng)對事件的發(fā)生,如何回應(yīng)發(fā)生的事件吓著,通常以函數(shù)的形式來出現(xiàn)再扭。
重申事件的幾個坑:
- 事件實際上應(yīng)該稱之為事件模型氧苍,事件本身只是一個單純的行為。
- 事件不是以 on 開頭的那個名稱泛范,如 onclick 不是事件让虐,click才是事件。onclick引用的是一個元素對象的屬性罢荡,它指向click事件類型綁定的實際處理函數(shù)赡突。
1.2 js事件的幾個概念
事件:指的是文檔或者瀏覽器窗口中發(fā)生的一些特定交互瞬間。我們可以通過偵聽器(或者處理程序)來預(yù)定事件区赵,以便事件發(fā)生的時候執(zhí)行相應(yīng)的代碼惭缰。
事件處理程序:我們用戶在頁面中進行的點擊這個動作、鼠標移動的動作笼才,網(wǎng)頁頁面加載完成的動作等漱受,都可以稱之為事件名稱,即:click骡送、mousemove昂羡、load等都是事件的名稱。響應(yīng)某個事件的函數(shù)則稱為事件處理程序摔踱,或者叫做事件偵聽器虐先。事件處理程序的名字是以“on”開頭,因此click事件的處理程序就是onclick派敷。
-
事件類型:
- UI事件:如load蛹批、unload、error篮愉、resize腐芍、scroll、select试躏、DOMActive猪勇,是用戶與頁面上的元素交互時觸發(fā)的。
- 焦點事件:如blur冗酿、DOMFocusIn埠对、DOMFocusOut络断、focus裁替、focusin、focusout貌笨,在元素獲得或失去焦點的時候觸發(fā)弱判,這些事件當中,最為重要的是blur和focus锥惋,有一點需要引起注意昌腰,這一類事件不會發(fā)生冒泡开伏!
- 鼠標與滾輪事件:如click、dblclick遭商、mousedown固灵、mouseenter、mouseleave劫流、mousemove巫玻、mouseout、mouseover祠汇、mouseup仍秤,是當用戶通過鼠標在頁面執(zhí)行操作時所觸發(fā)的。
- 滾輪事件:mousewheel(IE6+均支持)可很、DOMMouseScroll(FF支持的诗力,與mousewheel效果一樣)。是使用鼠標滾輪時觸發(fā)的我抠。
- 文本事件:textInput苇本,在文檔中輸入文本觸發(fā)。
- 鍵盤事件:keydown屿良、keyup圈澈、keypress,當用戶通過鍵盤在頁面中執(zhí)行操作時觸發(fā)尘惧。
- 合成事件:DOM3級新增康栈,用于處理IME的輸入序列。所謂IME喷橙,指的是輸入法編輯器啥么,可以讓用戶輸入在物理鍵盤上找不到的字符。compositionstart贰逾、compositionupdate悬荣、compositionend三種事件。
- 變動事件:DOMsubtreeModified疙剑、DOMNodeInserted氯迂、DOMNodeRemoved、DOMAttrModified言缤、DOMCharacterDataModified等嚼蚀,當?shù)讓覦OM結(jié)構(gòu)發(fā)生變化時觸發(fā)。IE8-不支持管挟。
- 變動名稱事件:指的是當元素或者屬性名變動時觸發(fā)轿曙,當前已經(jīng)棄用!
對于事件的基本類型,隨著HTML5的出現(xiàn)和發(fā)展导帝,又新增了HTML5事件守谓、設(shè)備事件、觸摸事件您单、手勢事件
- 事件流:描述的是從頁面中接收事件的順序斋荞。
事件冒泡:事件開始的時候由最具體的元素(文檔中嵌套層次最深的那個節(jié)點)接收,然后逐級向上傳播到較為不具體的節(jié)點(文檔)虐秦。
事件捕獲:事件開始的時候由最不具體的節(jié)點接收譬猫,然后逐級向下傳播到最具體的節(jié)點。
IE與原來的NetScape(網(wǎng)景)羡疗,對于事件流提出的是完全不同的順序染服。IE團隊提出的是事件冒泡流;NetScape的事件流是事件捕獲流叨恨。
事件對象:在觸發(fā)DOM上的某個事件的時候柳刮,會產(chǎn)生一個事件對象event,而在這個對象當中會包含著所有與事件有關(guān)的信息痒钝。
事件委托:給元素的父級或者祖級秉颗,甚至頁面綁定事件,然后利用事件冒泡的基本原理送矩,通過事件目標對象進行檢測蚕甥,然后執(zhí)行相關(guān)操作。
移除事件處理程序:每當將一個事件處理程序指定給一個元素時栋荸,在運行中的瀏覽器代碼與支持頁面交互的JavaScript代碼之間就會建立一個連接菇怀。連接數(shù)量也直接影響著頁面的執(zhí)行速度。所以晌块,當內(nèi)存中存在著過時的“空事件處理程序”的時候爱沟,就會造成Web應(yīng)用程序的內(nèi)存和性能問題。
上述的這些概念會在下面幾個環(huán)節(jié)中逐個提出并解釋匆背,下面大概會有事件源(DOM)呼伸、事件綁定(處理程序)、事件對象钝尸、事件類型等幾個部分介紹括享。
2、事件源
之所以這環(huán)節(jié)說DOM珍促,一方面是因為它是事件三要素之一铃辖,另一方面可以看出javascript在操作HTML文檔時,中間折現(xiàn)出的一些原理踢星。下面對DOM只說大概歷程澳叉,不談具體方法。
2.1 DOM
Document Object Model:文檔對象模型沐悦,是一組描述腳本與結(jié)構(gòu)化文檔進行交互的web的標準成洗,它定義了一系列對象、方法和屬性藏否,用于訪問瓶殃、操作和創(chuàng)建文檔中的內(nèi)容、結(jié)構(gòu)和行為副签。
- D:documnet遥椿,文檔,html文檔或者xml文檔
- O:object淆储,對象冠场,在轉(zhuǎn)成樹模型的時候,得到的對象本砰,它有相應(yīng)的屬性和方法碴裙,利用他們可以完成任何操作。
- M:model点额,模型舔株,樹模型,有節(jié)點構(gòu)成的一顆樹还棱。節(jié)點(元素载慈、屬性和文本)轉(zhuǎn)成對象。
在W3C的標準中珍手,DOM是獨于平臺和語言的接口办铡,它允許程序和腳本動態(tài)地訪問和更新文檔的內(nèi)容、結(jié)構(gòu)和樣式琳要。
W3C DOM由以下三部分組成:
- 核心DOM - 針對任何結(jié)構(gòu)化文檔的標準模型
- XML DOM - 針對 XML 文檔的標準模型
- HTML DOM - 針對 HTML 文檔的標準模型
javascript是ECMAscript的產(chǎn)物料扰、DOM是W3C的產(chǎn)物、兩者結(jié)合相得益彰焙蹭。
2.1.1 DOM0晒杈、DOM1、DOM2孔厉、DOM3的區(qū)別
實際上是未形成標準的試驗性質(zhì)的初級階段的DOM拯钻,現(xiàn)在習慣上被稱為DOM0。它定義了一些document的屬性和方法撰豺,提供了查詢和操作Web文檔的內(nèi)容API粪般。
常在使用的有:forms,cookie污桦,title亩歹,其它不建議使用。
W3C結(jié)合網(wǎng)景和IE的優(yōu)點于1998年10月推出了一個標準化的DOM,完成了第一級DOM小作,即:DOM1亭姥。W3C將DOM定義為一個與平臺和編程語言無關(guān)的接口,通過這個接口程序和腳本可以動態(tài)的訪問和修改文檔的內(nèi)容顾稀、結(jié)構(gòu)和樣式达罗。
DOM1級主要定義了HTML和XML文檔的底層結(jié)構(gòu)。在DOM1中静秆,DOM由兩個模塊組成:DOM Core(DOM核心)和DOM HTML粮揉。其中,DOM Core規(guī)定了基于XML的文檔結(jié)構(gòu)標準抚笔,通過這個標準簡化了對文檔中任意部分的訪問和操作扶认。DOM HTML則在DOM核心的基礎(chǔ)上加以擴展,添加了針對HTML的對象和方法殊橙,如:JavaScript中的Document對象蝠引。
DOM2級在原來DOM的基礎(chǔ)上又擴充了鼠標、用戶界面事件蛀柴、范圍螃概、遍歷等細分模塊,而且通過對象接口增加了對CSS的支持鸽疾。
在DOM2中引入了下列模塊吊洼,在模塊包含了眾多新類型和新接口:
- DOM視圖(DOM Views):定義了跟蹤不同文檔視圖的接口
- DOM事件(DOM Events):定義了事件和事件處理的接口
- DOM樣式(DOM Style):定義了基于CSS為元素應(yīng)用樣式的接口
- DOM遍歷和范圍(DOM Traversal and Range):定義了遍歷和操作文檔樹的接口
DOM3進一步擴展了DOM,在DOM3中引入了以下模塊:
- DOM加載和保存模塊(DOM Load and Save):引入了以統(tǒng)一方式加載和保存文檔的方法
- DOM驗證模塊(DOM Validation):定義了驗證文檔的方法
- DOM核心的擴展(DOM Style):支持XML 1.0規(guī)范制肮,涉及XML Infoset冒窍、XPath和XML Base
其整體的發(fā)展歷程如下:
3、事件綁定
3.1 事件的綁定方式
第一種方式:DOM0級豺鼻,即以屬性的方式直接寫在行內(nèi)综液。一般的驗證碼切換就有這樣的機制。
<a href="#" id="dom0" onclick="changeCaptcha();">
第二種方式:DOM1級儒飒,給元素添加屬性(例:onclick)谬莹,屬性的值就是一個具體的函數(shù)(click事件類型綁定的處理函數(shù))蔗崎。這里就有一個問題沛简,無法允許團隊不同人員對同一元素監(jiān)聽同一事件但做出不用的響應(yīng)。
<body>
<div id="event">這是事件機制學(xué)習</div>
<script>
var div=document.getElementById('event');
// 甲程序猿
div.onclick=function(){
console.log('甲要紅背景');
div.setAttribute('style', 'background: #ff0000');
};
// 乙程序猿
div.onclick=function(){
console.log('乙要黃背景');
div.setAttribute('style', 'background: #ffff00');
};
</script>
</body>
這里面出現(xiàn)的問題:無法給同一個元素綁定多個相同的事件馆衔,然而在web開發(fā)中井誉,這個是非常普遍的一個應(yīng)用蕉扮。
第三種方式:DOM2級,對主流瀏覽器來說颗圣,使用addEventListener
和removeListener
方法喳钟,它們都接受3個參數(shù):要處理的事件名屁使、作為事件處理程序的函數(shù)和一個布爾值。最后的布爾值參數(shù)如果是true奔则,表示在捕獲階段調(diào)用事件處理程序蛮寂;如果是false,表示在冒泡階段調(diào)用事件處理程序应狱。若最后的布爾值不填寫,則和false效果一樣祠丝。這里它支持同一dom元素注冊多個同種事件疾呻,還有新增了捕獲
和冒泡
的概念。
<body>
<div id="event">這是事件機制學(xué)習</div>
<script>
var div=document.getElementById('event');
div.addEventListener('click', function(){
console.log('這是DOM2級写半,甲綁定事件的的響應(yīng)');
});
div.addEventListener('click', function(){
console.log('這是DOM2級岸蜗,乙綁定事件的的響應(yīng)');
});
</script>
</body>
它也有問題:兼容性,IE8對此自定義了兩個自己的方法attachEvent
和detachEvent
方法叠蝇。同時璃岳,需要注意IE在這里是‘onclick’。
div.attachEvent('onclick', function(){
console.log('這是DOM2級悔捶,IE綁定事件的的響應(yīng)');
});
那么為了保持瀏覽器兼容性問題铃慷,我們還需要自己封裝一個簡單的函數(shù)去實現(xiàn)事件的綁定。同時蜕该,由于attachEvent()方法中的this指向window(下面會有說明)犁柜,所以需要對this進行顯式修改。
<body>
<div id="event">這是事件機制學(xué)習</div>
<script>
var div=document.getElementById('event');
function reponseCode(){
console.log('這是封裝的綁定事件');
}
// 事件的綁定機制
function addHandle(obj, type, handle){
if(obj.addEventListener){
obj.addEventListener(type, handle堂淡,false);
}else if(obj.attachEvent){
obj.attachEvent('on'+type, function(event) {
return handler.call(target, event);
});
}else{
obj['on'+type]=handle;
}
}
addHandle(div, 'click', reponseCode);
</script>
</body>
移除事件綁定:通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除馋缅,移除時傳入的參數(shù)與添加處理程序時使用的參數(shù)相同。這意味著绢淀,addEventListener()添加的匿名函數(shù)將無法移除萤悴。同理attachEvent()和detachEvent();
無效代碼:
<div id="box" style="height:30px;width:200px;background-color:pink;"></div>
<script>
box.addEventListener("click",function(){
this.innerHTML += '1'
},false);
box.removeEventListener('click',function(){
this.innerHTML += '1'
},false);
</script>
有效代碼:
<div id="box" style="height:30px;width:200px;background-color:pink;"></div>
<script>
var handle = function(){
this.innerHTML += '1'
};
box.addEventListener("click",handle,false);
box.removeEventListener('click',handle,false);
</script>
說完上面綁定事件的幾種方式,這里還要指出一點皆的,即事件處理程序中的this
所指覆履。
<div id="box" style="height:100px;width:300px;background-color:pink;"
onclick = "console.log(this)">//<div>
</div>
<div id="box" style="height:100px;width:300px;background-color:pink;"></div>
<script>
box.onclick= function(){
console.log(this);//<div>
}
</script>
<div id="box" style="height:100px;width:300px;background-color:pink;"></div>
<script>
box.addEventListener('click',function(){
console.log(this);//<div>
});
</script>
<div id="box" style="height:100px;width:300px;background-color:pink;"></div>
<script>
box.attachEvent('onclick',function(){
console.log(this);//window
});
</script>
與其他三個事件處理程序不同,IE事件處理程序的this指向window费薄,而非被綁定事件的元素内狗。
3.2 事件流
javascript操作CSS稱為腳本化CSS,而javascript與HTML的交互是通過事件實現(xiàn)的义锥。事件就是文檔或瀏覽器窗口中發(fā)生的一些特定的交互瞬間柳沙,而事件流(又叫事件傳播)描述的是從頁面中接收事件的順序。
3.2.1 歷史淵源
當瀏覽器發(fā)展到第四代時(IE4及Netscape4)拌倍,瀏覽器開發(fā)團隊遇到了一個很有意思的問題:頁面的哪一部分會擁有某個特定的事件赂鲤?想象畫在一張紙上的一組同心圓噪径。如果把手指放在圓心上,那么手指指向的不是一個圓数初,而是紙上的所有圓找爱。
兩家公司的瀏覽器開發(fā)團隊在看待瀏覽器事件方面還是一致的。如果單擊了某個按鈕泡孩,他們都認為單擊事件不僅僅發(fā)生在按鈕上车摄,甚至也單擊了整個頁面。
但有意思的是仑鸥,IE和Netscape開發(fā)團隊居然提出了差不多是完全相反的事件流的概念吮播。IE
的事件流是事件冒泡流
,而Netscape
的事件流是事件捕獲流
眼俊。
一個普通的HTML文檔意狠,下面統(tǒng)一使用。
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<body>
<div></div>
</body>
</html>
3.2.2 'IE'的'事件冒泡流'
IE的事件流叫做事件冒泡(event bubbling)疮胖,即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節(jié)點)接收环戈,然后逐級向上傳播到較為不具體的節(jié)點(文檔)。
如果單擊了頁面中的'div'元素澎灸,那么這個click事件沿DOM樹向上傳播院塞,在每一級節(jié)點上都會發(fā)生,按照如下順序傳播:
(1) <div>
(2) <body>
(3) <html>
(4) document
所有現(xiàn)代瀏覽器都支持事件冒泡性昭,但在具體實現(xiàn)在還是有一些差別迫悠。IE9、Firefox巩梢、Chrome创泄、Safari將事件一直冒泡到window對象,如下括蝠。
(1) <div>
(2) <body>
(3) <html>
(4) document
(5) window
事件冒泡流測試代碼:
<body>
<div id="box" style="height: 100px;width: 300px;background: pink;"></div>
<button id="reset">還原</button>
<script>
var box=document.getElementById('box');
var reset=document.getElementById('reset');
reset.onclick=function(){
history.go();
};
box.onclick=function(){
box.innerHTML+='div鞠抑、';
};
document.documentElement.onclick=function(){
box.innerHTML+='html、';
};
document.body.onclick=function(){
box.innerHTML+='body忌警、';
};
document.onclick=function(){
box.innerHTML+='document搁拙、';
};
window.onclick=function(){
box.innerHTML+='window<br>';
};
</script>
</body>
效果如下:
3.2.3 'Netscape'的'事件捕獲流'
網(wǎng)景的事件捕獲流,思想是不太具體的節(jié)點應(yīng)該更早接收到事件法绵,而最具體的節(jié)點應(yīng)該最后接收到事件箕速。事件捕獲的用意在于在事件到達預(yù)定目標之前就捕獲它。
在事件捕獲過程中朋譬,document對象首先接收到click事件盐茎,然后事件沿DOM樹依次向下,一直傳播到事件的實際目標徙赢,即<div>元素字柠。
(1) document
(2) <html>
(3) <body>
(4) <div>
IE9探越、Firefox、Chrome窑业、Safari等現(xiàn)代瀏覽器都支持事件捕獲钦幔,但是從window對象開始捕獲。
(1) window
(2) document
(3) <html>
(4) <body>
(5) <div>
事件捕獲流代碼:
<body>
<div id="box" style="height: 100px;width: 300px;background: #ccc;overflow: auto;"></div>
<button id="reset">還原</button>
<script>
//IE8-瀏覽器返回div body html document
//其他瀏覽器返回div body html document window
var box=document.getElementById('box');
var reset=document.getElementById('reset');
reset.onclick=function(){
history.go();
};
box.addEventListener('click', function(){
box.innerHTML+='div\n';
}, true);
document.documentElement.addEventListener('click', function(){
box.innerHTML+='html\n';
}, true);
document.body.addEventListener('click', function(){
box.innerHTML+='body\n';
}, true);
document.addEventListener('click', function(){
box.innerHTML+='document\n';
}, true);
window.addEventListener('click', function(){
box.innerHTML+='window\n';
}, true);
</script>
</body>
addEventListener()方法中的第三個參數(shù)設(shè)置為true時常柄,即為事件捕獲階段鲤氢。
3.2.4 'W3C'的'DOM2'
事件流又稱為事件傳播,DOM2級事件規(guī)定的事件流包括三個階段:事件捕獲階段(capture phase)西潘、處于目標階段(target phase)和事件冒泡階段(bubbling phase)卷玉。
首先發(fā)生的是事件捕獲,為截獲事件提供了機會秸架,然后是實際的目標接收到事件揍庄,最后一個階段是冒泡階段咆蒿,可以在這個階段對事件做出響應(yīng)东抹,如下圖。
即真正觸發(fā)事件的dom元素沃测,是捕獲事件的終點缭黔,是冒泡事件的起點,所以這里就不區(qū)分事件了蒂破,哪個先注冊馏谨,就先執(zhí)行哪個。
3.3 事件委托
事件委托就是利用事件冒泡附迷,只指定一個事件處理程序惧互,就可以管理某一類型的所有事件。它能讓你避免對特定的每個節(jié)點添加事件監(jiān)聽器喇伯;相反喊儡,事件監(jiān)聽器是被添加到它們的父元素上。事件監(jiān)聽器會分析從子元素冒泡上來的事件稻据,找到是哪個子元素的事件艾猜。
3.3.1 事件委托現(xiàn)象比擬
網(wǎng)摘的大牛基本都用取快遞去描述事件委托這個現(xiàn)象捻悯,大體是這么個內(nèi)容匆赃。
有三個同事預(yù)計會在周一收到快遞。為簽收快遞今缚,有兩種辦法:一是三個人在公司門口等快遞算柳;二是委托給前臺MM代為簽收。現(xiàn)實當中姓言,我們大都采用委托的方案(公司也不會容忍那么多員工站在門口就為了等快遞)埠居。前臺MM收到快遞后查牌,她會判斷收件人是誰,然后按照收件人的要求簽收滥壕,甚至代為付款纸颜。這種方案還有一個優(yōu)勢,那就是即使公司里來了新員工(不管多少)绎橘,前臺MM也會在收到寄給新員工的快遞后核實并代為簽收胁孙。
這里其實還有2層意思的:
第一,現(xiàn)在委托前臺的同事是可以代為簽收的称鳞,即程序中的現(xiàn)有的dom節(jié)點是有事件的涮较;
第二,新員工也是可以被前臺MM代為簽收的冈止,即程序中新添加的dom節(jié)點也是有事件的狂票。
3.3.2 事件委托實現(xiàn)
我們以下面的代碼舉例:
<body>
<ul id="outul">
<li id="innerli1">事件冒泡流</li>
<li id="innerli2">事件捕捉流</li>
<li id="innerli3">事件委托</li>
<li id="innerli4">事件對象</li>
</ul>
</body>
1、假設(shè)我們要實現(xiàn)熙暴,點擊每個li元素會在終端輸出標簽內(nèi)的文本內(nèi)容闺属,傳統(tǒng)的思想是這樣的:我們遍歷li標簽,然后為他們綁定點擊事件周霉。那么腳本是這樣的;
<script>
var outul=document.getElementById('outul');
var allli=document.getElementsByTagName('li');
for(var i=0; i<allli.length; i++){
allli[i].onclick=function(){
console.log(this.innerHTML);
}
}
</script>
2掂器、那么假設(shè)我們使用事件委托的方式呢。即我們?yōu)閘i標簽的父級標簽ul綁定點擊事件俱箱,從事件冒泡流的原理來說国瓮,li的點擊事件會冒泡到ul上面,這時狞谱,因為之前ul已經(jīng)綁定過了點擊事件乃摹,那么這個點擊事件就會被觸發(fā)。這里面就存在了一個問題:我們想要對不同的li標簽在響應(yīng)各自的點擊事件時跟衅,即事件三要素之一的監(jiān)聽器(事件處理程序)是不一樣的孵睬,那我們?yōu)閡l綁定的點擊事件又有什么意義呢?
為了解決上面的問題与斤,即我們要知道點擊事件被觸發(fā)時肪康,如何找到相對應(yīng)的不同的li標簽。Event對象(下面的環(huán)節(jié)會單獨介紹)提供了一個了一個屬性叫做target
撩穿,可以返回事件的目標節(jié)點磷支,即事件三要素之一的事件源,也就是說target可以表示為當前的事件操作的DOM食寡,但不是真正操作DOM雾狈。
Event對象存在兼容性問題,代碼大體如下抵皱,在下環(huán)節(jié)里面會具體解釋善榛。
<script>
var outul=document.getElementById('outul');
outul.onclick=function(event){
// event即Event對象
var ent=event || window.event;
var target=ent.target || ent.srcElement;
console.log(target);
if(target.nodeName.toLowerCase()=='li'){
console.log(target.innerHTML);
}
}
</script>
這時有人說辩蛋,上面的都是點擊li操作的是同樣的效果,要是每個li被點擊的效果都不一樣移盆,那么用事件委托還有用嗎悼院?繼續(xù)來。
這次換原代碼咒循。
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="刪除" />
<input type="button" id="move" value="移動" />
<input type="button" id="select" value="選擇" />
</div>
假設(shè)我們對不用的按鈕實現(xiàn)相應(yīng)的功能据途,簡單輸出終端名稱來代替。
3叙甸、同父元素不同子元素事件監(jiān)聽器不同颖医,傳統(tǒng)代碼。
<script>
var add=document.getElementById("add");
var remove=document.getElementById("remove");
var move=document.getElementById("move");
var select=document.getElementById("select");
add.onclick = function(){
console.log('添加');
};
remove.onclick = function(){
console.log('刪除');
};
move.onclick = function(){
console.log('移動');
};
select.onclick = function(){
console.log('選擇');
}
</script>
4裆蒸、同父元素不同子元素事件監(jiān)聽器不同熔萧,事件委托代碼。
<script>
var box=document.getElementById('box');
box.onclick=function(event){
var ent=event || window.event;
var target=ent.target || ent.srcElement;
if(target.nodeName.toLowerCase()=='input'){
switch(target.id){
case 'add':
console.log('添加');
break;
case 'remove':
console.log('刪除');
break;
case 'move':
console.log('移動');
break;
case 'select':
console.log('選擇');
break;
default:
console.log('測試');
}
}
};
</script>
5僚祷、同父元素不同子元素+新添加子元素佛致,事件委托。
<body>
<input type="button" id="add" value="添加" />
<ul id="outul">
<li id="innerli1">事件冒泡流</li>
<li id="innerli2">事件捕捉流</li>
<li id="innerli3">事件委托</li>
<li id="innerli4">事件對象</li>
</ul>
<script>
var add=document.getElementById('add');
var outul=document.getElementById('outul');
add.onclick=function(){
var li=document.createElement('li');
var text=document.createTextNode('新事件');
li.appendChild(text);
outul.appendChild(li);
};
outul.onclick=function(event){
var ent=event || window.event;
var target=ent.target || ent.srcElement;
if(target.nodeName.toLowerCase()=='li'){
console.log(target.innerHTML);
}
};
</script>
</body>
最適合使用事件委托技術(shù)的事件包括click久妆、mousedown晌杰、mouseup跷睦、keydown筷弦、keyup和keypress。
4抑诸、事件對象
Event 對象代表事件的狀態(tài)烂琴,比如事件在其中發(fā)生的元素、鍵盤按鍵的狀態(tài)蜕乡、鼠標的位置奸绷、鼠標按鈕的狀態(tài)。
在觸發(fā)DOM上的某個事件時层玲,會產(chǎn)生一個事件對象event号醉。這個對象中包含著所有與事件有關(guān)的信息。包括導(dǎo)致事件的元素辛块,事件的類型以及其他與特定事件相關(guān)的信息畔派。
例:當用戶單擊某個元素的時候,我們給這個元素注冊的事件就會觸發(fā),該事件的本質(zhì)就是一個函數(shù),而該函數(shù)的形參接收一個event對象。
有瀏覽器都支持event對象润绵,但支持方式不同线椰。
4.1 獲取事件對象
event對象是事件函數(shù)的第一個參數(shù),如文本內(nèi)容一樣尘盼,IE8不支持憨愉;再者如終端輸出烦绳,火狐不支持。
<body>
<b>IE8-瀏覽器輸出undefined配紫,其他瀏覽器則輸出事件對象[object MouseEvent]</b><br />
<div id="box" style="height: 30px; line-height: 30px; width: 200px; background: #ccc;"></div>
<script>
var box=document.getElementById('box');
box.onclick=function(ent){
box.innerHTML=ent;
// 直接訪問event變量径密、firefox瀏覽器不支持
console.log(event);
}
</script>
</body>
然后產(chǎn)生一個兼容所有瀏覽器的寫法:
<body>
<div id="box" style="height: 30px; line-height: 30px; width: 200px; background: #ccc;"></div>
<script>
var box=document.getElementById('box');
box.onclick=function(ent){
ent=ent || event;
box.innerHTML=ent;
console.log(ent);
}
</script>
</body>
這里在之前事件委托中已經(jīng)使用到。
4.2 事件的屬性和方法
事件對象包含與創(chuàng)建它的特定事件有關(guān)的屬性和方法躺孝。觸發(fā)的事件類型不一樣睹晒,可用的屬性和方法也不一樣。不過括细,所有事件都有些共有的屬性和方法伪很。
4.2.1 事件類型
事件有很多類型,事件對象中的type屬性表示被觸發(fā)的事件類型奋单。
<body>
<div id="box" style="height: 30px; line-height: 30px; width: 200px; background: #ccc;"></div>
<script>
var box=document.getElementById('box');
box.onclick=box.onmouseover=box.onmouseout=function(ent){
ent=ent || event;
box.innerHTML=ent.type;
}
</script>
</body>
上述代碼分別在鼠標移入锉试、點擊、移出時顯示:mouseover览濒、click呆盖、mouseout。
4.2.2 事件目標
關(guān)于事件目標贷笛,共有currentTarget应又、target和srcElement這三個屬性。
1乏苦、currentTarget
currentTarget屬性返回事件當前所在的節(jié)點株扛,即正在執(zhí)行的監(jiān)聽函數(shù)所綁定的那個節(jié)點,但IE8-瀏覽器不支持汇荐。
一般地洞就,currentTarget與事件中的this指向相同。但在attachEvent()事件處理程序中掀淘,this指向window旬蟋。之前事件綁定中已提及。
<body>
<ul id="outul">
<li>currentTarget</li>
<li>target</li>
</ul>
<script>
var outul=document.getElementById('outul');
var allli=document.getElementsByTagName('li');
outul.onclick=function(ent){
ent=ent || event;
allli[0].innerHTML=ent.currentTarget;
allli[1].innerHTML=(this===ent.currentTarget);
}
</script>
</body>
2革娄、target
currentTarget屬性返回事件正在執(zhí)行的監(jiān)聽函數(shù)所綁定的節(jié)點倾贰,而target屬性返回事件的實際目標節(jié)點,但IE8-瀏覽器不支持拦惋。
<body>
<ul id="outul" style="background: #ccc; width: 300px; height: 60px; line-height: 30px;">
<li>currentTarget</li>
<li>target</li>
</ul>
<script>
var outul=document.getElementById('outul');
outul.onmouseover=function(ent){
ent=ent || event;
ent.target.style.background='red';
console.log(ent.target);
};
outul.onmouseout = function(ent){
ent=ent || event;
ent.target.style.background='#ccc';
console.log(ent.target);
};
</script>
</body>
上部分代碼分別有三部分可以移入匆浙、移出,分別是左部架忌、右側(cè)上部吞彤、右側(cè)下部。效果是移入變紅、移出回顯灰色饰恕,兩者終端都會輸出相應(yīng)節(jié)點挠羔。
3、srcElement
srcElement屬性與target的功能一致埋嵌,但火狐不兼容破加。
<body>
<ul id="outul" style="background: #ccc; width: 300px; height: 60px; line-height: 30px;">
<li>currentTarget</li>
<li>target</li>
</ul>
<script>
var outul=document.getElementById('outul');
outul.onmouseover=function(ent){
ent=ent || event;
ent.srcElement.style.background='red';
console.log(ent.srcElement);
};
outul.onmouseout = function(ent){
ent=ent || event;
ent.srcElement.style.background='#ccc';
console.log(ent.srcElement);
};
</script>
</body>
故結(jié)合以上三點,一般兼容代碼如下:
<script>
var handler = function(ent){
ent=ent || event;
var target=ent.target || ent.srcElement;
};
</script>
4.2.3 事件冒泡
事件冒泡是事件流的第三個階段雹嗦,通過事件冒泡可以在這個階段對事件做出響應(yīng)范舀。
關(guān)于冒泡,事件對象中包含bubbles了罪、cancelBubble锭环、stopPropagation()和stopImmediatePropagation()這四個相關(guān)的屬性和方法。
1泊藕、屬性bubbles
bubbles屬性返回一個布爾值辅辩,表示當前事件是否會冒泡。該屬性為只讀屬性娃圆。
發(fā)生在文檔元素上的大部分事件都會冒泡玫锋,但focus、blur和scroll事件不會冒泡讼呢。所以撩鹿,除了這三個事件bubbles屬性返回false外,其他事件該屬性都為true悦屏。
<body>
<button id="btn">按鈕</button>
<input id="test">
<script>
var btn=document.getElementById('btn');
var test=document.getElementById('test');
btn.onclick=function(ent){
ent=ent || event;
btn.innerHTML=ent.bubbles;
console.log(ent.bubbles);
};
test.onfocus=function(ent){
test.innerHTML=ent.bubbles;
console.log(ent.bubbles);
}
</script>
</body>
按鈕點擊后节沦,上面的值會變成true,輸入框聚焦后沒反應(yīng)窜管,但兩者終端都會輸出相應(yīng)的bubbles的值散劫。
2稚机、方法stopPropagation()
stopPropagation()方法表示取消事件的進一步捕獲或冒泡幕帆,無返回值,但IE8-瀏覽器不支持赖条。
<body>
<button id="btn" style="width: 100px;">按鈕</button>
<input id="test">
<script>
var btn=document.getElementById('btn');
var test=document.getElementById('test');
btn.onclick=function(ent){
ent=ent || event;
test.value+='按鈕欄失乾、';
// ent.stopPropagation();
};
document.body.onclick=function(ent){
ent=ent || event;
test.value+='文檔。';
}
</script>
</body>
正常代碼這樣纬乍,從W3C事件流的說法碱茁,假設(shè)window->div這樣是從外到內(nèi),點擊事件被由外到內(nèi)分別捕捉仿贬,目標接收事件纽竣,再由內(nèi)到外冒泡響應(yīng)。
所以上面的代碼結(jié)果是在點擊按鈕之后,輸入框會顯示“按鈕欄蜓氨、文檔聋袋。”穴吹;那么如果把阻止冒泡的語句注釋刪去的話幽勒,響應(yīng)結(jié)果就會變成這樣“按鈕欄、”港令。
3啥容、方法stopImmediatePropagation()
stopImmediatePropagation()方法不僅可以取消事件的進一步捕獲或冒泡,而且可以阻止同一個事件的其他監(jiān)聽函數(shù)被調(diào)用顷霹,無返回值咪惠,但IE8-瀏覽器不支持。
<body>
<button id="btn" style="width: 100px;">按鈕</button>
<input id="test">
<script>
var btn=document.getElementById('btn');
var test=document.getElementById('test');
btn.addEventListener('click', function(ent){
ent=ent || event;
test.value+='按鈕欄淋淀、';
// ent.stopImmediatePropagation()
}, false);
btn.addEventListener('click', function(ent){
ent=ent || event;
btn.style.background='#ff0000';
}, false);
document.body.addEventListener('click', function(ent){
ent=ent || event;
test.value+='文檔硝逢。';
}, false);
</script>
</body>
上面的代碼結(jié)果是在點擊按鈕之后,輸入框會顯示“按鈕欄绅喉、文檔渠鸽。”,且按鈕底色會變紅柴罐;那么如果把阻止冒泡的語句注釋刪去的話徽缚,響應(yīng)結(jié)果就會變成這樣“按鈕欄、”革屠,它既阻止了點擊事件向body層冒泡凿试,還阻止了同層監(jiān)聽點擊事件底色變化。
這里面我們可以知道似芝,事件是先注冊那婉,先調(diào)用的原則。
4党瓮、cancelBubble
cancelBubble屬性只能用于阻止冒泡详炬,無法阻止捕獲階段。該值可讀寫寞奸,默認值是false呛谜。當設(shè)置為true時,cancelBubble可以取消事件冒泡枪萄。該屬性全瀏覽器支持隐岛,但并不是標準寫法
<body>
<button id="btn" style="width: 100px;">按鈕</button>
<input id="test">
<script>
var btn=document.getElementById('btn');
var test=document.getElementById('test');
btn.addEventListener('click', function(ent){
ent=ent || event;
test.value+='按鈕欄、';
ent.cancelBubble=true;
}, false);
document.body.addEventListener('click', function(ent){
ent=ent || event;
test.value+='文檔瓷翻。';
}, false);
</script>
</body>
當使用stopIPropagation()方法或stopImmediatePropagation()方法時聚凹,關(guān)于cancelBubble值的變化割坠,各瀏覽器表現(xiàn)不同。
- chrome/safari/opera中妒牙,cancelBubble的值為false韭脊。
- IE9+/firefox中,cancelBubble的值為true单旁。
兼容處理沪羔,阻止冒泡:
var handler = function(ent){
ent=ent || event;
if(ent.stopPropagation){
ent.stopPropagation();
}else{
ent.cancelBubble = true;
}
}
4.2.4 事件流:eventPhase屬性
eventPhase屬性返回一個整數(shù)值,表示事件目前所處的事件流階段象浑,但IE8-瀏覽器不支持蔫饰。
0表示事件沒有發(fā)生,1表示捕獲階段愉豺,2表示目標階段篓吁,3表示冒泡階段。
<body>
<button id="btn" style="width: 100px;">按鈕</button>
<script>
document.body.addEventListener('click', function(ent){
ent=ent || event;
btn.innerHTML=ent.eventPhase;
}, true);
</script>
</body>
效果為“按鈕”變成“1”蚪拦。
換腳本:
<script>
var btn=document.getElementById('btn');
btn.addEventListener('click', function(ent){
ent=ent || event;
btn.innerHTML=ent.eventPhase;
}, false);
</script>
效果為“按鈕”變成“2”杖剪。
換腳本:
<script>
document.body.addEventListener('click', function(ent){
ent=ent || event;
btn.innerHTML=ent.eventPhase;
}, false);
</script>
效果為“按鈕”變成“3”。
這里大致可以看出W3C對事件流定義的三個階段驰贷。
4.2.5 取消默認行為
常見的瀏覽器默認行為有點擊鏈接后盛嘿,瀏覽器跳轉(zhuǎn)到指定頁面;或者按一下空格鍵括袒,頁面向下滾動一段距離次兆。
關(guān)于取消默認行為的屬性包括cancelable、defaultPrevented锹锰、preventDefault()和returnValue芥炭。
使用:
在DOM0級事件處理程序中取消默認行為,使用returnValue恃慧、preventDefault()和return false都有效
在DOM2級事件處理程序中取消默認行為园蝠,使用return false無效
在IE事件處理程序中取消默認行為,使用preventDefault()無效
1痢士、cancelable屬性
cancelable屬性返回一個布爾值彪薛,表示事件是否可以取消。該屬性為只讀屬性良瞧。返回true時陪汽,表示可以取消。否則褥蚯,表示不可取消。IE8-瀏覽器不支持况增。
<a id="test" href="#">鏈接</a>
<script>
var test=document.getElementById('test');
test.onclick= function(ent){
ent=ent || event;
test.innerHTML=e.cancelable;
}
</script>
效果:點擊“鏈接”變成“true”赞庶。
2、preventDefault()方法
preventDefault()方法取消瀏覽器對當前事件的默認行為澜薄,無返回值,IE8-瀏覽器不支持。
<a id="test" >鏈接</a>
<script>
var test=document.getElementById('test');
test.onclick= function(ent){
ent=ent||event;
ent.preventDefault();
}
</script>
效果:不轉(zhuǎn)跳妒峦。
3、returnValue屬性
returnValue屬性可讀寫,默認值是true若债,但將其設(shè)置為false就可以取消事件的默認行為,與preventDefault()方法的作用相同,firefox和IE9+瀏覽器不支持泰讽。
做兼容處理:
var handler = function(ent){
ent=ent || event;
if(ent.preventDefault){
ent.preventDefault();
}else{
ent.returnValue=false;
}
}
4、return false
<script>
var test=document.getElementById('test');
test.onclick= function(ent){
return false;
}
</script>
效果:不轉(zhuǎn)跳。
5奥吩、defaultPrevented屬性
defaultPrevented屬性表示默認行為是否被阻止,返回true時表示被阻止端衰,返回false時楼誓,表示未被阻止主守。
<a id="test" >鏈接</a>
<script>
var test=document.getElementById('test');
test.onclick= function(ent){
ent=ent || event;
if(ent.preventDefault){
ent.preventDefault();
}else{
ent.returnValue=false;
}
test.innerHTML=ent.defaultPrevented;
}
</script>
效果:點擊“鏈接”變?yōu)椤皌rue”。
5、完整的簡單事件相關(guān)代碼
<script>
var EventUtil={
// 事件對象
getEvent: function(event){
return event||window.event;
},
// 事件目標節(jié)點
getTarget: function(event){
return event.target||event.srcElement;
},
// 阻止事件的默認行為
preventDefault: function(){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue=false;
}
},
// 阻止向上冒泡
stopPropagation: function(){
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;
}
},
// DOM2級添加事件
addHandler: function(element, type, handler){
if(element.addEventListener){
element.addEventListener(type, handler, false);
}else if(element.attachEvent){
element["e"+type]=function(){
handler.call(element)
};
element.attachEvent("on"+type, element["e"+type]);
}else{
element["on"+type]=handler;
}
},
// DOM2級移除事件
removeHandler: function(element, type, handler){
if(element.removeEventListener){
element.removeEventListener(type, handler, false);
}else if(element.detachEvent){
element.detachEvent("on"+type, element["e"+type]);
element["e"+type]=null;
}else{
element["on"+type]=null;
}
}
};
</script>
6、內(nèi)存泄漏
程序的運行需要內(nèi)存。只要程序提出要求,操作系統(tǒng)或者運行時(runtime)就必須供給內(nèi)存茎芋。
對于持續(xù)運行的服務(wù)進程(daemon)掏缎,必須及時釋放不再用到的內(nèi)存蒿秦。否則勺疼,內(nèi)存占用越來越高,輕則影響系統(tǒng)性能,重則導(dǎo)致進程崩潰勋功。
不再用到的內(nèi)存,沒有及時釋放,就叫做內(nèi)存泄漏(memory leak)。
6.1 垃圾回收機制
大多數(shù)語言提供自動內(nèi)存管理抽活,減輕程序員的負擔歇由,這被稱為"垃圾回收機制"(garbage collector)糊昙。
垃圾回收機制最常使用的方法叫做"引用計數(shù)"(reference counting):語言引擎有一張"引用表",保存了內(nèi)存里面所有的資源(通常是各種值)的引用次數(shù)。如果一個值的引用次數(shù)是0,就表示這個值不再用到了,因此可以將這塊內(nèi)存釋放。
let arr = [1, 2, 3, 4];
console.log('hello world');
arr = null;
數(shù)組[1, 2, 3, 4]是一個值,會占用內(nèi)存。變量arr是僅有的對這個值的引用充岛,因此引用次數(shù)為1。
如果增加最下面那行代碼,解除arr對[1, 2, 3, 4]引用伞鲫,這塊內(nèi)存就可以被垃圾回收機制釋放了儒搭。
JavaScript中最常用的垃圾收集方式是標記清除(mark-and-sweep)默穴。當變量進入環(huán)境(例如乙埃,在函數(shù)中聲明一個變量)時遇伞,就將這個變量標記為“進入環(huán)境”。從邏輯上講渐排,永遠不能釋放進入環(huán)境的變量所占的內(nèi)存炬太,因為只要執(zhí)行流進入相應(yīng)的環(huán)境,就可能用到它們驯耻。而當變量離開環(huán)境時亲族,這將其 標記為“離開環(huán)境”。雖然JavaScript 會自動垃圾收集可缚,但是如果我們的代碼寫法不當霎迫,會讓變量一直處于“進入環(huán)境”的狀態(tài),無法被回收城看。
6.2 意外的全局變量
JavaScript 處理未定義變量的方式比較寬松:未定義的變量會在全局對象創(chuàng)建一個新變量女气。在瀏覽器中,全局對象是 window 测柠。
可能一般寫了這段代碼:
function foo(arg) {
bar = "this is a hidden global variable";
}
然而這段代碼的執(zhí)行是這樣:
function foo(arg) {
window.bar = "this is a hidden global variable";
}
另一種意外導(dǎo)致定了了全局變量:
function foo() {
this.variable = "potential accidental global";
}
// foo 調(diào)用自己炼鞠,this 指向了全局對象(window)缘滥,而不是 undefined
foo();
在JavaScript文件頭部加上 'use strict',可以避免此類錯誤發(fā)生谒主。啟用嚴格模式解析 JavaScript 朝扼,避免意外的全局變量。
6.3 被遺忘的計時器或回調(diào)函數(shù)
var someResource=getData();
setInterval(function() {
var node=document.getElementById('Node');
if(node) {
// 處理 node 和 someResource
node.innerHTML = JSON.stringify(someResource);
}
}, 1000);
如果id
為Node
的元素從 DOM 中移除,該定時器仍會存在,同時,因為回調(diào)函數(shù)中包含對 someResource
的引用,定時器外面的someResource
也不會被釋放霎肯。
6.4 脫離DOM的引用
保存DOM節(jié)點內(nèi)部數(shù)據(jù)結(jié)構(gòu)很有用擎颖。假如你想快速更新表格的幾行內(nèi)容,把每一行 DOM 存成字典(JSON鍵值對)或者數(shù)組很有意義观游。此時搂捧,同樣的DOM元素存在兩個引用:一個在 DOM 樹中,另一個在字典中懂缕。將來你決定刪除這些行時允跑,需要把兩個引用都清除。
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// 更多邏輯
}
function removeButton() {
// 按鈕是 body 的后代元素
document.body.removeChild(document.getElementById('button'));
// 此時搪柑,仍舊存在一個全局的 #button 的引用
// elements 字典聋丝。button 元素仍舊在內(nèi)存中,不能被 GC 回收工碾。
}
然后還有'DOM樹'內(nèi)部或子節(jié)點的引用問題弱睦。假如你的JavaScript代碼中保存了表格某一個'td'的引用。將來決定刪除整個表格的時候渊额,直覺認為'GC'會回收除了已保存的'td'以外的其它節(jié)點况木。實際情況并非如此:此'td'是表格的子節(jié)點,子元素與父元素是引用關(guān)系端圈。由于代碼保留了'td'的引用焦读,導(dǎo)致整個表格仍待在內(nèi)存中。保存'DOM'元素引用的時候舱权,要小心謹慎矗晃。
6.5 閉包
閉包是 JavaScript 開發(fā)的一個關(guān)鍵方面:匿名函數(shù)可以訪問父級作用域的變量。
簡單來說是這個樣子的:
<script>
var leaks=(function(){
var leak='closure';
return function(){
console.log(leak);
}
})();
</script>
參考博文:一個意想不到的js內(nèi)存泄漏宴倍。
參考博文:JS常見4種內(nèi)存泄漏和chrome開發(fā)工具監(jiān)測介紹张症。
全文來源博文:javascript學(xué)習目錄。