深入理解JavaScript系列(24):JavaScript與DOM(下)

來源:互聯網
上載者:User
介紹

上一章我們介紹了JavaScript的基本內容和DOM對象的各個方面,包括如何訪問node節點。本章我們將講解如何通過DOM操作元素並且討論瀏覽器事件模型。

本文參考:http://net.tutsplus.com/tutorials/javascript-ajax/javascript-and-the-dom-lesson-2/

操作元素

上一章節我們提到了DOM節點集合或單個節點的訪問步驟,每個DOM節點都包括一個屬性集合,大多數的屬性都提供為相應的功能提供了抽象。例如,如果有一個帶有ID屬性intro的文本元素,你可以很容易地通過DOM API來改變該元素的顏色:

document.getElementById('intro').style.color = '#FF0000';

為了理解這個API的功能,我們一步一步分開來看就非常容易理解了:

var myDocument = document;  
var myIntro = myDocument.getElementById('intro');
var myIntroStyles = myIntro.style;

// 現在,我們可以設定顏色了:
myIntroStyles.color = '#FF0000';

現在,我們有了該文本的style對象的引用了,所以我們可以添加其它的CSS樣式:

myIntroStyles.padding = '2px 3px 0 3px';  
myIntroStyles.backgroundColor = '#FFF';
myIntroStyles.marginTop = '20px';

這裡我們只是要了基本的CSS屬性名稱,唯一區別是CSS屬性的名稱如果帶有-的話,就需要去除,比如用marginTop代替margin-top。例如,下面的代碼是不工作的,並且會拋出語法錯誤:

myIntroStyles.padding-top = '10em';  

// 產生語法錯誤:
// 在JavaScript裡橫線-是減法操作符
// 而且也沒有這樣的屬性名稱

屬性可以像數組一樣訪問,所以利用這個知識我們可以建立一個函數來改變任何給定元素的樣式:

function changeStyle(elem, property, val) {
elem.style[property] = val; // 使用[]來訪問屬性
}

// 使用上述的函數:
var myIntro = document.getElementById('intro'); // 擷取intro文字物件
changeStyle(myIntro, 'color', 'red');

這僅僅是個例子,所以該函數也許沒什麼用,文法上來說,直接用還是會快點,例如(elem.style.color = ‘red’)。除了style屬性以外,一個節點(或元素)也還有其他很多屬性可以操作,如果你使用Firebug,點擊DOM選項卡可以看到所有該節點 (或元素)的所有屬性:

所有的屬性都可以通過點標示符來訪問(例如:Element.tabIndex)。不是所有的屬性都是未經處理資料類型(strings, numbers, Booleans等等),sytle屬性也是一個包含自己屬性的對象,很多元素的屬性都是唯讀,也就是說不能修改他們的值。例如,你不能直接修改一個節 點的parentNode屬性,如果你修改唯讀屬性的時候瀏覽器會拋出錯誤:例如,拋出錯誤“setting a property that has only a getter”,只是我們需要注意的。

通常DOM操作都是改變原始的內容,這裡有幾種方式來實現這個,最簡單的是使用innerHTML 屬性,例如:

var myIntro = document.getElementById('intro');  

// 替換當前的內容
myIntro.innerHTML = 'New content for the <strong>amazing</strong> paragraph!';

// 新增內容到當前的內容裡
myIntro.innerHTML += '... some more content...';

唯一的問題是該方法沒在規範裡定義,而且在DOM規範裡也沒有定義,如果你不反感的話請繼續使用,因為它比我們下面要討論其它的方法快多了。

Node節點

通過DOM API建立內容的時候需要注意node節點的2種類型,一種是元素節點,一種是text節點,上一章節已經列出了所有的節點類型,這兩種需要我們現在特別 注意。建立元素可以通過createElement方法,而建立text節點可以使用createTextNode,相應代碼如下:

var myIntro = document.getElementById('intro');  

// 新增內容
var someText = 'This is the text I want to add';
var textNode = document.createTextNode(someText);
myIntro.appendChild(textNode);

這裡我們使用了appendChild方法將新text節點附件到文字欄位,這樣做比非標準的innerHTML方法顯得有點長,但瞭解這些原理依然很重要,這裡有一個使用DOM方法的更詳細例子:

var myIntro = document.getElementById('intro');  

// 添加新串連到文本節點
// 首先,建立新串連元素
var myNewLink = document.createElement('a'); // <a/>
myNewLink.href = 'http://google.com'; // <a href="http://google.com"/>
myNewLink.appendChild(document.createTextNode('Visit Google'));
// <a href="http://google.com">Visit Google</a>

// 將內容附件到文本節點
myIntro.appendChild(myNewLink);

另外DOM裡還有一個insertBefore方法用於再節點前面附件內容,通過insertBefore和appendChild我們可以實現自己的insertAfter函數:

// 'Target'是DOM裡已經存在的元素
// 'Bullet'是要插入的新元素

function insertAfter(target, bullet) {
target.nextSibling ?
target.parentNode.insertBefore(bullet, target.nextSibling)
: target.parentNode.appendChild(bullet);
}

// 使用了3目運算式:
// 格式:條件?條件為true時的運算式:條件為false時的運算式

上面的函數首先檢查target元素的同級下一個節點是否存在,如果存在就在該節點前面添加bullet節點,如果不存在,就說明target是最 後一個節點了,直接在後面append新節點就可以了。DOM API沒有給提供insertAfter是因為真的沒必要了——我們可以自己建立。

DOM操作有很多內容,上面你看到的只是其中一部分。

Event事件

瀏覽器事件是所有web程式的核心,通過這些事件我們定義將要發生的行為,如果在頁面裡有個按鈕,那點擊此按鈕之前你需要驗證表單是否合法,這時候就可以使用click事件,下面列出的最標準的事件列表:

註:正如我們上章所說的,DOM和JavaScript語言是2個單獨的東西,瀏覽器事件是DOM API的一部分,而不是JavaScript的一部分。

滑鼠事件
  1.  ‘mousedown’ – 滑鼠裝置按下一個元素的時候觸發mousedown事件。
  2.  ‘mouseup’ – 滑鼠裝置從按下的元素上彈起的時候觸發mouseup事件。
  3.  ‘click’ – 滑鼠點擊元素的時候觸發click事件。
  4.  ‘dblclick’ – 滑鼠雙擊元素的時候觸發dblclick事件。
  5. mouseover’ – 滑鼠移動到某元素上的時候觸發mouseover事件。
  6.  ‘mouseout’ – 滑鼠從某元素離開的時候觸發mouseout事件。
  7.  ‘mousemove’ – 滑鼠在某元素上移動但未離開的時候觸發mousemove事件。
鍵盤事件
  1. keypress’ – 按鍵按下的時候觸發該事件。
  2. keydown’ – 按鍵按下的時候觸發該事件,並且在keypress事件之前。
  3. keyup’ – 按鍵鬆開的時候觸發該事件,在keydown和keypress事件之後。
表單事件
  1. select’ – 文字欄位(input, textarea等)的文本被選擇的時候觸發該事件。
  2. change’ – 控制項失去input焦點的時候觸發該事件(或者值被改變的時候)。
  3. submit’ – 表單提交的時候觸發該事件。
  4. reset’ – 表單重設的時候觸發該事件。
  5. focus’ – 元素獲得焦點的時候觸發該事件,通常來自滑鼠裝置或Tab導航。
  6. blur’ – 元素失去焦點的時候觸發該事件,通常來自滑鼠裝置或Tab導航。
其它事件
  1. load’ – 頁面載入完畢(包括內容、圖片、frame、object)的時候觸發該事件。
  2. resize’ – 頁面大小改變的時候觸發該事件(例如瀏覽器縮放)。
  3. scroll’ – 頁面滾動的時候觸發該事件。
  4. unload’ – 從頁面或frame刪除所有內容的時候觸發該事件(例如離開一個頁面)。

還有很多各種各樣的事件,上面展示的事件是我們在JavaScript裡最常用的事件,有些事件在跨瀏覽器方面可能有所不同。還有其它瀏覽器實現的 一些屬性事件,例如Gecko實現的DOMContentLoaded或DOMMouseScroll等,Gecko的詳細事件列表請查看這裡。

事件處理

我們將了事件,但是還沒有將到如何將處理函數和事件管理起來,使用這些事件之前,你首先要註冊這些事件控制代碼,然後描述該事件發生的時候該如何處理,下面的例子展示了一個基本的事件註冊模型:

基本事件註冊:

<!-- HTML -->  
<button id="my-button">Click me!</button>

// JavaScript:  
var myElement = document.getElementById('my-button');

// 事件處理控制代碼:
function buttonClick() {
alert('You just clicked the button!');
}

// 註冊事件
myElement.onclick = buttonClick;

使用document.getElementById命令,通過ID=my-button擷取該button對象,然後建立一個處理函數,隨後將該函數賦值給該DOM的onclick屬性。就這麼簡單!

基本事件註冊是非常簡單的,在事件名稱前面添加首碼on作為DOM的屬性就可以使用了,這是事件處理的基本核心,但下面的代碼我不推薦使用:

<button onclick="return buttonClick()">Click me!</button>

上述Inline的事件處理方式不利用頁面維護,建議將這些處理函數都封裝在單獨的js檔案,原因和CSS樣式的一樣的。

進階事件註冊:

別被標題迷惑了,“進階”不意味著好用,實際上上面討論的基本事件註冊是我們大部分時候用的方式,但有一個限制:不能綁定多個處理函數到一個事件上。這也是我們要講解該小節原因:

該模型運行你綁定多個處理控制代碼到一個事件上,也就是說一個事件觸發的時候多個函數都可以執行,另外,該模型也可以讓你很容易裡刪除某個已經綁定的控制代碼。

嚴格來說,有2中不同的模型:W3C模型和微軟模型,除IE之外W3C模型支援所有的現代瀏覽器,而微軟模型只支援IE,使用W3C模型的代碼如下:

// 格式:target.addEventListener( type, function, useCapture );  
// 例子:
var myIntro = document.getElementById('intro');
myIntro.addEventListener('click', introClick, false);

使用IE模型的代碼如下:

// 格式: target.attachEvent ( 'on' + type, function );  
// 例子:
var myIntro = document.getElementById('intro');
myIntro.attachEvent('onclick', introClick);

introClick的代碼如下:

function introClick() {  
alert('You clicked the paragraph!');
}

事實上,要做出通用的話,我們可以自訂一個函數以支援跨瀏覽器:

function addEvent(elem, type, fn) {
if (elem.attachEvent) {
elem.attachEvent('on' + type, fn);
return;
}
if (elem.addEventListener) {
elem.addEventListener(type, fn, false);
}
}

該函數首先檢查attachEvent和addEventListener屬性,誰可以就用誰,這兩種類型的模型都支援刪除控制代碼功能,參考下面的removeEvent函數。

function removeEvent(elem, type, fn) {
if (elem.detachEvent) {
elem.detachEvent('on' + type, fn);
return;
}
if (elem.removeEventListener) {
elem.removeEventListener(type, fn, false);
}
}

你可以這樣使用:

var myIntro = document.getElementById('intro');
addEvent(myIntro, 'click', function () {
alert('YOU CLICKED ME!!!');
});

注意到我們傳入了一個匿名函數作為第三個參數,JavaScript運行我們定義和執行匿名函數,這種匿名函數特別適合作為參數傳遞,實際上我們也可以傳遞有名的函數(代碼如下),但是你們函數更容易做。

如果你只想在第一次click的時候觸發一個函數,你可以這麼做:

// 注意:前提是我們已經定於好了addEvent/removeEvent函數
// (定義好了才能使用哦)

var myIntro = document.getElementById('intro');
addEvent(myIntro, 'click', oneClickOnly);

function oneClickOnly() {
alert('WOW!');
removeEvent(myIntro, 'click', oneClickOnly);
}

當第一次觸發以後,我們就立即刪除該控制代碼,但是有匿名函數的話卻很難將自身的引用刪除,不過實際上可以通過如下的形式來做(只不過有點麻煩):

addEvent(myIntro, 'click', function () {
alert('WOW!');
removeEvent(myIntro, 'click', arguments.callee);
});

這裡我們是有了arguments對象的callee屬性,arguments對象包含了所有傳遞進來的參數以及該函數自身(callee),這樣我們就可以放心地刪除自身的引用了。
關於W3C和微軟模型還有其他的少許差異,比如this,在觸發事件的時候函數中的this一般都是該元素上下文,,也就說this引用該元素自身,在基本事件註冊和W3C模型中都沒有問題,但在微軟模型的實現裡卻可能出錯,請參考如下代碼:

function myEventHandler() {
this.style.display = 'none';
}

// 正常工作,this是代表該元素
myIntro.onclick = myEventHandler;

// 正常工作,this是代表該元素
myIntro.addEventListener('click', myEventHandler, false);

// 不正常,這時候的this是代表Window對象
myIntro.attachEvent('onclick', myEventHandler);

這裡有一些方式可以避免這個問題,最簡單的方式是使用前面的基本事件註冊方式,或者是再做一個通用的addEvent,通用代碼請參考John Resig或Dean Edward的文章。

Event對象

另外一個非常重要的內容是Event對象,當事件發生的時候出發某個函數,該Event對象將自動在函數內可用,該對象包含了很多事件觸發時候的信 息,但IE卻沒有這麼實現,而是自己實現的,IE瀏覽器是通過全域對象window下的event屬性來包含這些資訊,雖然不是大問題,但我們也需要注意 一下,下面的代碼是相容性的:

function myEventHandler(e) {

// 注意參數e
// 該函數調用的時候e是event對象(W3C實現)

// 相容IE的代碼
e = e || window.event;

// 現在e就可以相容各種瀏覽器了

}

// 這裡可以自由地綁定事件了

這裡判斷e對象(Event對象)是否存在我們使用了OR操作符:如果e不存在(為null, undefined,0等)的時候,將window.event賦值給e,否則的話繼續使用e。通過這方式很快就能在多瀏覽器裡得到真正的Event對 象,如果你不喜歡這種方式的話,你可以使用if語句來處理:

if (!e) {
e = window.event;
} // 沒有else語句,因為e在其它瀏覽器已經定義了

另外Event對象下的命令和屬性都很有用,遺憾的是不不能全相容瀏覽器,例如當你想取消預設的行為的時候你可以使用Event對象裡的preventDefault()方法,但IE裡不得不使用對象的returnValue屬性值來控制,相容代碼如下:

function myEventHandler(e) {
e = e || window.event;
// 防止預設行為
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}

例如,當你點擊一個串連的時候,預設行為是導航到href裡定義的地址,但有時候你想禁用這個預設行為,通過returnValue和preventDefault就可以實現,Event對象裡的很多屬性在瀏覽器裡都不相容,所以很多時候需要處理這些相容性代碼。

注意:現在很多JS類庫都已經封裝好了e.preventDefault代碼,也就是說在IE裡可用了,但是原理上依然是使用returnValue來實現的。

事件冒泡

事件冒泡,就是事件觸發的時候通過DOM向上冒泡,首先要知道不是所有的事件都有冒泡。事件在一個目標元素上觸發的時候,該事件將觸發一一觸發祖先節點元素,直到最頂層的元素:

,如果a串連被點擊,觸發觸發串連的click事件,然後觸發p的click事件,以此再觸發div和body的click事件。順序不變,而且不一定是在同時觸發的。
這樣你就可以利用該特性去處理自己的邏輯了,並且再任何時候都可以停止冒泡,比如,如果你只想冒泡到文本節點上,而不再進一步冒泡,你可以在p的click事件處理函數裡丁停止冒泡:

function myParagraphEventHandler(e) {

e = e || window.event;

// 停止向上冒泡
if (e.stopPropagation) {
// W3C實現
e.stopPropagation();
} else {
// IE實現
e.cancelBubble = true;
}

}

// 使用我們自訂的addEvent函數將myParagraphEventHandler綁定到click事件上:
addEvent(document.getElementsByTagName('p')[0], 'click', myParagraphEventHandler);

事件委託

舉例來說,如果你有一個很多行的大表格,在每個<tr>上綁定點擊事件是個非常危險的想法,因為效能是個大問題。流行的做法是使用事件委託。事件委託描述的是將事件綁定在容器元素上,然後通過判斷點擊的target子項目的類型來觸發相應的事件。

var myTable = document.getElementById('my-table');

myTable.onclick = function () {

// 處理瀏覽器安全色
e = e || window.event;
var targetNode = e.target || e.srcElement;

// 測試如果點擊的是TR就觸發
if (targetNode.nodeName.toLowerCase() === 'tr') {
alert('You clicked a table row!');
}

}

事件委託依賴於事件冒泡,如果事件冒泡到table之前被禁用的話,那上面的代碼就無法工作了。

總結

本章我們覆蓋到了DOM元素的操作以及相關的瀏覽器事件模型,希望大家能對DOM有了進一步的瞭解。有任何問題,請留言討論。

轉自:湯姆大叔

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.