JavaScript不僅門檻低,而且是一門有趣、功能強大和非常重要的語言。各行各業的人發現自己最混亂的選擇是JavaSscript程式設計語言。由於有著各種各樣的背景,所以不是每個人都對JavaScript及其基本原理有廣泛的認識。通常來書,除非你去參加工作面試才會去思考為什麼或者怎麼做,否則JavaScript只是你工作的內容。
這個系類的目標是深入探討JavaScript的一些概念和理論。主題來自於 Darcy Clarke的JavaScript典型面試問題列表。希望你不僅僅是為了答案而閱讀完這篇文章,每一篇文章會讓對過去學過的知識有一個新的理解,或者重溫你學習的東西,這有利於你用JavaScript實現所有互動。
詳解事件委託
事件委託是一種由其它元素而非事件目標元素來響應事件產生的行為的思想。用document元素來處理按鈕的點擊行為就是事件委託的一個例子,另一種常見情況是,用ul元素來處理其子項目li的事件。
有多種方法來處理事件委託。標準方法來源於原生瀏覽器的功能。瀏覽器以一種特定的工作流程來處理事件,並支援事件捕獲和事件冒泡。W3C關於瀏覽器怎麼支援事件的文檔:W3C DOM Level 3 Events。一些JS庫和架構公開了其它方式,如發布/訂閱模型(將在後文提及)。
事件捕獲和事件冒泡是事件流中的兩個階段,任何事件產生時,如點擊一個按鈕,將從最頂端的容器開始(一般是html的根節點)。瀏覽器會向下遍曆DOM樹直到找到觸發事件的元素,一旦瀏覽器找到該元素,事件流就進入事件目標階段,該階段完成後,瀏覽器會沿DOM樹向上冒泡直到最頂層容器,看看是否有其它元素需要使用同一個事件。
下面的樣本說明了這個過程。點擊按鈕會導致事件流識別本身在容器下面的文本,每一個元素都接收同樣的點擊監聽代碼,由於事件捕獲,點擊事件會首先觸發HTML節點綁定的點擊處理常式,然後在事件冒泡階段的末尾返回到最頂層元素。
var nodes = $('*'),
node = {};
for (var i = 0; i < nodes.length; i++) {
node = nodes[i];
node.addEventListener('click', function (event) {
$('#EventListing').append('Bubble ' + (event.currentTarget.id || event.currentTarget.tagName) + '<br/>');
});
node.addEventListener('click', function (event) {
if (event.currentTarget != event.target) {
$('#EventListing ').append('Capture ' + (event.currentTarget.id || event.currentTarget.tagName) + ' <br/>');
}
}, true);
}
大多數現代庫使用冒泡監聽,而在捕獲階段處理。瀏覽器包含一個方法來管理事件冒泡。事件處理常式可以調用stopPropagation告訴DOM事件停止冒泡,第二個方式是調用stopImmediatePropagation,它不僅停止冒泡,也會阻止這個元素上其它監聽當前事件的處理常式觸發。然而,停止傳播事件時要小心,因為你不知道是否有其它上層的DOM元素可能需要知道當前事件。
還有第三個可以控制元素如何對事件作出回應的方法。所有現代瀏覽器支援preventDefault方法,這個方法會阻止瀏覽器處理事件的預設行為。一個常見樣本就是連結,使用連結執行UI操作是一種常見的做法。然而,當我們不希望連結跟普通被啟用的連結一樣會在新標籤頁開啟一個新頁面,就可以使用preventDefault方法來阻止這個預設行為。
還有其它實現事件委託的方法可以考慮,其中值得一提的就是發布/訂閱模型。發布/訂閱模型也稱為了廣播模型,牽涉到兩個參與者。通常,兩個參與者在DOM中並沒有緊密的聯絡,而且可能是來自兄弟的容器。可以給它們共同的祖先元素設定監聽處理常式,但是如果共同的祖先元素在DOM樹中處於較高層次(離根節點比較近),就會監聽很多同輩元素的事件,會造成意想不到的結果;當然,也可能存在邏輯或結構原因要分開這兩個元素。
發布/訂閱模型也能自訂事件。發布/訂閱模型從一個元素髮送訊息後並向上遍曆,有時也向下遍曆,DOM會通知遍曆路徑上的所有元素事件發生了。在下面的樣本中,JQuery通過trigger方法傳遞事件。
$('#container').on('myCustomEvent', function (e, data) {
$('#insertText').append(data);
});
$('#button2').click(function () {
$('#button2').trigger('myCustomEvent', ['I triggered']);
});
使用事件委託來管理事件流有很多優點,其中最大的優點是改善效能。元素繫結的每一個監聽器都會佔用一些記憶體,如果頁面上只有少數幾個監聽器,我們也不會注意到它們之間的區別,然後,如果要監聽一個50行5列的表格中的每個儲存格,你的Web應用會開始變慢,為了使應用程式最快啟動並執行最好方式是保持儘可能低的記憶體使用量。
使用事件委託能減少監聽器數量,在元素的容器上綁定事件意味著只需要一個監聽器。這種方法的缺點是,父容器的接聽程式可能需要檢查事件來選擇正確的操作,而元素本身不會是一個監聽器。額外處理帶來的影響遠低於許多存在記憶體中的監聽器。
更少的監聽器和更少的DOM互動也易於維護。父容器層次的監聽器能處理多種不同的事件操作,這是一種簡單的方法來管理相關的事件操作,這些事件通常需要執行相關功能或需要共用資料。
如果父容器是監聽器,然後要執行獨立的內部操作而並不需要添加或者移除本身的監聽器。元素操作在單頁應用中是極其常見的,為某部分添加一個按鈕這樣簡單的事情也會為應用程式建立一個潛在的效能塊,沒有合適的事件委託,就必須手動為每一個按鈕添加監聽,如果每個接聽程式不清理乾淨,它可能會導致記憶體流失。瀏覽器不會清理頁面,因此在單頁應用中,所有從記憶體中清理不當的片段都會留在記憶體中,這些片段會降低程式效能。
當在頁面中添加互動時,仔細考慮一下,是否真的需要去監聽元素。
this在JavaScript中是怎麼工作的
this 關鍵字在JavaScript中的一種常用方法是指代碼當前上下文。
如果this沒有被設定,則預設指向全域對象,其通常是window
如果一個函數中運行了一個內嵌函式,比如一個事件監聽器,則this指向內嵌函式的原始碼。例如,當設定一個按鈕的單擊處理常式,this將引用匿名函數內的按鈕。
如果函數是一個對象的建構函式,this指向新對象。
如果函數被定義在一個對象上,然後調用對象時,this指向該對象。
在非同步編程中,this可以很容易改變過程中一個功能操作。保持處理常式內容相關的一個小技巧是將其設定到閉包內的一個變數,當在上下文改變的地方調用一個函數時,如setTimeout,你仍然可以通過該變數引用需要的對象。
操作this的另一種方式是通過call、apply和bind。三種方法都被用於調用一個函數,並能指定this的上下文,你可以讓代碼使用你規定的對象,而不是依靠瀏覽器去計算出this指向什麼。Call、apply和bind本身是相當複雜的,應該有自己的文檔記錄,我們會把這當做未來待解決問題的一部分。下面是一個改變this指向方法的樣本:
var div = $('#appendHere');
$('#clickMe').on('click', function () {
var that = this;
div.append(checkForWindow(this));
setTimeout(function () {
div.append(checkForWindow(this));
div.append('<strong>that</strong> is the ' + that.tagName + '<br/>');
}, 300);
});
function checkForWindow(elm) {
if (elm instanceof Window) {
return 'this is the Window<br/>';
} else if (elm.tagName) {
return ('this is the ' + elm.tagName + '<br/>');
} else {
return ('this is ' + elm + '<br/>');
}
}
事件委託和this是現代JavaScript中重要的功能,理解它們的工作原理是成功開發產品的關鍵,並且可以肯定的是,這是應聘JavaScript工程師必須要瞭解的