事件處理是對象化編程的一個很重要的環節,沒有了事件處理,程序就會變得很死,缺乏靈活性。事件處理的過程可以這樣表示:發生事件 - 啟動事件處理程序 - 事件處理程序作出反應。其中,要使事件處理程序能夠啟動,必須先告訴對象,如果發生了什么事情,要啟動什么處理程序,否則這個流程就不能進行下去。事件的處理程序可以是任意 JavaScript 語句,但是我們一般用特定的自定義函數(function)來處理事情。
指定事件處理程序有三種方法:
方法一 直接在 HTML 標記中指定。這種方法是用得最普遍的。方法是:
<標記 ... ... 事件="事件處理程序" [事件="事件處理程序" ...]>
讓我們來看看例子:
<body ... onload="alert('網頁讀取完成,請慢慢欣賞!')" onunload="alert('再見!')">
這樣的定義<body>標記,能使文檔讀取完畢的時候彈出一個對話框,寫著“網頁讀取完成,請慢慢欣賞”;在用戶退出文檔(或者關閉窗口,或者到另一個頁面去)的時候彈出“再見”。
方法二 編寫特定對象特定事件的 JavaScript。這種方法用得比較少,但是在某些場合還是很好用的。方法是:
<script language="JavaScript" for="對象" event="事件">
...
(事件處理程序代碼)
...
</script>
例:
<script language="JavaScript" for="window" event="onload">
alert('網頁讀取完成,請慢慢欣賞!');
</script>
方法三 在 JavaScript 中說明。方法:
<事件主角 - 對象>.<事件> = <事件處理程序>;
用這種方法要注意的是,“事件處理程序”是真正的代碼,而不是字符串形式的代碼。如果事件處理程序是一個自定義函數,如無使用參數的需要,就不要加“()”。例:
...
function ignoreError() {
return true;
}
...
window.onerror = ignoreError; // 沒有使用“()”
這個例子將 ignoreError() 函數定義為 window 對象的 onerror 事件的處理程序。它的效果是忽略該 window 對象下任何錯誤(由引用不允許訪問的 location 對象產生的“沒有權限”錯誤是不能忽略的)。
onblur 事件 發生在窗口失去焦點的時候。
應用于:window 對象
onchange 事件 發生在文本輸入區的內容被更改,然后焦點從文本輸入區移走之后。捕捉此事件主要用于實時檢測輸入的有效性,或者立刻改變文檔內容。
應用于:Password 對象;Select 對象;Text 對象;Textarea 對象
onclick 事件 發生在對象被單擊的時候。單擊是指鼠標停留在對象上,按下鼠標鍵,沒有移動鼠標而放開鼠標鍵這一個完整的過程。
一個普通按鈕對象(Button)通常會有 onclick 事件處理程序,因為這種對象根本不能從用戶那里得到任何信息,沒有 onclick 事件處理程序就等于廢柴。按鈕上添加 onclick 事件處理程序,可以模擬“另一個提交按鈕”,方法是:在事件處理程序中更改表單的 action, target, encoding, method 等一個或幾個屬性,然后調用表單的 submit() 方法。
在 Link 對象的 onclick 事件處理程序中返回 false 值(return false),能阻止瀏覽器打開此連接。即,如果有一個這樣的連接:<a onclick="return false">Go!</a>,那么無論用戶怎樣點擊,都不會去到 www.a.com 網站,除非用戶禁止瀏覽器運行 JavaScript。
應用于:Button 對象;Checkbox 對象;Image 對象;Link 對象;Radio 對象;Reset 對象;Submit 對象
onerror 事件 發生在錯誤發生的時候。它的事件處理程序通常就叫做“錯誤處理程序”(Error Handler),用來處理錯誤。上邊已經介紹過,要忽略一切錯誤,就使用:
function ignoreError() {
return true;
}
window.onerror = ignoreError;
應用于:window 對象
onfocus 事件 發生在窗口得到焦點的時候。
應用于:window 對象
onload 事件 發生在文檔全部下載完畢的時候。全部下載完畢意味著不但 HTML 文件,而且包含的圖片,插件,控件,小程序等全部內容都下載完畢。本事件是 window 的事件,但是在 HTML 中指定事件處理程序的時候,我們是把它寫在<body>標記中的。
應用于:window 對象
onmousedown 事件 發生在用戶把鼠標放在對象上按下鼠標鍵的時候。參考 onmouseup 事件。
應用于:Button 對象;Link 對象
onmouseout 事件 發生在鼠標離開對象的時候。參考 onmouseover 事件。
應用于:Link 對象
onmouseover 事件 發生在鼠標進入對象范圍的時候。這個事件和 onmouseout 事件,再加上圖片的預讀,就可以做到當鼠標移到圖像連接上,圖像更改的效果了。有時我們看到,在指向一個連接時,狀態欄上不顯示地址,而顯示其它的資料,看起來這些資料是可以隨時更改的。它們是這樣做出來的:
<a href="..."
onmouseover="window.status='Click Me Please!'; return true;"
onmouseout="window.status=''; return true;">
應用于:Link 對象
onmouseup 事件 發生在用戶把鼠標放在對象上鼠標鍵被按下的情況下,放開鼠標鍵的時候。如果按下鼠標鍵的時候,鼠標并不在放開鼠標的對象上,則本事件不會發生。
應用于:Button 對象;Link 對象
onreset 事件 發生在表單的“重置”按鈕被單擊(按下并放開)的時候。通過在事件處理程序中返回 false 值(return false)可以阻止表單重置。
應用于:Form 對象
onresize 事件 發生在窗口被調整大小的時候。
應用于:window 對象
onsubmit 事件 發生在表單的“提交”按鈕被單擊(按下并放開)的時候。可以使用該事件來驗證表單的有效性。通過在事件處理程序中返回 false 值(return false)可以阻止表單提交。
應用于:Form 對象
onunload 事件 發生在用戶退出文檔(或者關閉窗口,或者到另一個頁面去)的時候。與 onload 一樣,要寫在 HTML 中就寫到<body>標記里。
有的 Web Masters 用這個方法來彈出“調查表單”,以“強迫”來者填寫;有的就彈出廣告窗口,唆使來者點擊連接。我覺得這種“onunload="open..."”的方法很不好,有時甚至會因為彈出太多窗口而導致資源缺乏。有什么對來者說就應該在網頁上說完,不對嗎?
應用于:window 對象
現在我們有實力學習以下關于對象化編程,但其實屬于上一章的內容了。
with 語句 為一個或一組語句指定默認對象。
用法:with (<對象>) <語句>;
with 語句通常用來縮短特定情形下必須寫的代碼量。在下面的例子中,請注意 Math 的重復使用:
x = Math.cos(3 * Math.PI) + Math.sin(Math.LN10);
y = Math.tan(14 * Math.E);
當使用 with 語句時,代碼變得更短且更易讀:
with (Math) {
x = cos(3 * PI) + sin(LN10);
y = tan(14 * E);
}
this 對象 返回“當前”對象。在不同的地方,this 代表不同的對象。如果在 JavaScript 的“主程序”中(不在任何 function 中,不在任何事件處理程序中)使用 this,它就代表 window 對象;如果在 with 語句塊中使用 this,它就代表 with 所指定的對象;如果在事件處理程序中使用 this,它就代表發生事件的對象。
一個常用的 this 用法:
<script>
...
function check(formObj) {
...
}
...
</script>
<body ...>
...
<form ...>
...
<input type="text" ... onchange="check(this.form)">
...
</form>
...
</body>
這個用法常用于立刻檢測表單輸入的有效性。
自定義構造函數 我們已經知道,Array(),Image()等構造函數能讓我們構造一個變量。其實我們自己也可以寫自己的構造函數。自定義構造函數也是用 function。在 function 里邊用 this 來定義屬性。
function <構造函數名> [(<參數>)] {
...
this.<屬性名> = <初始值>;
...
}
然后,用 new 構造函數關鍵字來構造變量:
var <變量名> = new <構造函數名>[(<參數>)];
構造變量以后,<變量名>成為一個對象,它有它自己的屬性——用 this 在 function 里設定的屬性。
以下是一個從網上找到的搜集瀏覽器詳細資料的自定義構造函數的例子:
function Is() {
var agent = navigator.userAgent.toLowerCase();
this.major = parseInt(navigator.appVersion); //主版本號
this.minor = parseFloat(navigator.appVersion);//全版本號
this.ns = ((agent.indexOf('mozilla')!=-1) &&
((agent.indexOf('spoofer')==-1) && //是否 Netscape
(agent.indexOf('compatible') == -1)));
this.ns2 = (this.ns && (this.major == 3)); //是否 Netscape 2
this.ns3 = (this.ns && (this.major == 3)); //是否 Netscape 3
this.ns4b = (this.ns && (this.minor < 4.04)); //是否 Netscape 4 低版本
this.ns4 = (this.ns && (this.major >= 4)); //是否 Netscape 4 高版本
this.ie = (agent.indexOf("msie") != -1); //是否 IE
this.ie3 = (this.ie && (this.major == 2)); //是否 IE 3
this.ie4 = (this.ie && (this.major >= 4)); //是否 IE 4
this.op3 = (agent.indexOf("opera") != -1); //是否 Opera 3
this.win = (agent.indexOf("win")!=-1); //是否 Windows 版本
this.mac = (agent.indexOf("mac")!=-1); //是否 Macintosh 版本
this.unix = (agent.indexOf("x11")!=-1); //是否 Unix 版本
}
var is = new Is();
這個構造函數非常完整的搜集了瀏覽器的信息。我們看到它為對象定義了很多個屬性:major, minor, ns, ie, win, mac 等等。它們的意思見上面的注釋。把 is 變量定義為 Is() 對象后,用 if (is.ns) 這種格式就可以很方便的知道瀏覽器的信息了。我們也可以從這個構造函數中看到,它也可以使用一般的 JavaScript 語句(上例中為 var 語句)。
讓我們再來看一個使用參數的構造函數:
function myFriend(theName, gender, theAge, birthOn, theJob) {
this.name = theName;
this.isMale = (gender.toLowerCase == 'male');
this.age = theAge;
this.birthday = new Date(birthOn);
this.job = theJob
}
var Stephen = new myFriend('Stephen', 'Male', 18, 'Dec 22, 1982', 'Student');
從這個構造函數我們不但看到了參數的用法,還看到了不同的屬性用不同的數據型是可以的(上例五個屬性分別為:字符串,布爾值,數字,日期,字符串),還看到了構造函數里也可以用構造函數來“構造”屬性。如果用了足夠的“保護措施”來避免無限循環,更可以用構造函數自身來構造自己的屬性。
在講述 window 對象的時候,我們提到過,一個框架內的網頁也是 window 對象,也就是說,Frame 對象也是 window 對象。用最容易理解的話說,每一個 HTML 文件占用一個 window 對象,包括定義框架的網頁(“框架網頁”)。在 IE 里用“<iframe>”標記在文檔中插入的框架也是 window 對象,但是用“包含網頁”的方法(在 HTML 中顯示為“<!--webbot bot="include" ...-->”)讀取的 HTML 就不占用獨自的 window 對象。每一個框架都是包含它的頁的 window 對象的一個子對象(不知道應該叫“屬性”不該),要引用它,可以用以下幾種方法之一:
window.frames[x]
window.frames['frameName']
window.frameName
其中,x 指的是該 window 對象中指定的第幾個框架,與其它數組一樣,x 也是從零開始的。frameName 指的是該框架的名字,跟<frame>里的“name”屬性一樣。
如果使用 window.frameName 指定的 window 對象又是一個框架網頁,那么引用它的框架的方法:window.frameName.subFrameName。以此類推。
要注意的時,無論在何處,引用“window”對象所返回的,都是“當前”window 對象。如果要訪問其它 window 對象,就要用到 parent 和 top 屬性。parent 指的是“父級”window 對象,也就是包含當前 window 對象的框架網頁;top 指的是窗口最頂端的 window 對象。
使用框架還要密切留意你的 JavaScript 中定義的全局變量和自定義函數。它們都有它們的所屬——所在的 window 對象。要引用其它框架中的全局變量或自定義函數,都要用“窗口對象.框架對象[.框架對象…].全局變量或自定義函數”這種很煩的方法。
以上這個問題在建立連接時經常會被忽略:如果在<head>中定義了一個默認目標窗口(<base target="...">),在<a href="javascript:...">中,要知道輸入的 JavaScript 語句是在默認目標窗口中運行的,必要時加一些“parent”“top”屬性。
我們已經知道,在 document 對象中有一個 cookie 屬性。但是 Cookie 又是什么?“某些 Web 站點在您的硬盤上用很小的文本文件存儲了一些信息,這些文件就稱為 Cookie。”—— MSIE 幫助。一般來說,Cookies 是 CGI 或類似,比 HTML 高級的文件、程序等創建的,但是 JavaScript 也提供了對 Cookies 的很全面的訪問權利。
在繼續之前,我們先要學一學 Cookie 的基本知識。
每個 Cookie 都是這樣的:<cookie名>=<值>
<cookie名>的限制與 JavaScript 的命名限制大同小異,少了“不能用 JavaScript 關鍵字”,多了“只能用可以用在 URL 編碼中的字符”。后者比較難懂,但是只要你只用字母和數字命名,就完全沒有問題了。<值>的要求也是“只能用可以用在 URL 編碼中的字符”。
每個 Cookie 都有失效日期,一旦電腦的時鐘過了失效日期,這個 Cookie 就會被刪掉。我們不能直接刪掉一個 Cookie,但是可以用設定失效日期早于現在時刻的方法來間接刪掉它。
每個網頁,或者說每個站點,都有它自己的 Cookies,這些 Cookies 只能由這個站點下的網頁來訪問,來自其他站點或同一站點下未經授權的區域的網頁,是不能訪問的。每一“組”Cookies 有規定的總大小(大約 2KB 每“組”),一超過最大總大小,則最早失效的 Cookie 先被刪除,來讓新的 Cookie“安家”。
現在我們來學習使用 document.cookie 屬性。
如果直接使用 document.cookie 屬性,或者說,用某種方法,例如給變量賦值,來獲得 document.cookie 的值,我們就可以知道在現在的文檔中有多少個 Cookies,每個 Cookies 的名字,和它的值。例如,在某文檔中添加“document.write(document.cookie)”,結果顯示:
name=kevin; email=kevin@kevin.com; lastvisited=index.html
這意味著,文檔包含 3 個 Cookies:name, email 和 lastvisited,它們的值分別是 kevin, kevin@kevin.com 和 index.html。可以看到,兩個 Cookies 之間是用分號和空格隔開的,于是我們可以用 cookieString.split('; ') 方法得到每個 Cookie 分開的一個數組(先用 var cookieString = document.cookie)。
設定一個 Cookie 的方法是對 document.cookie 賦值。與其它情況下的賦值不同,向 document.cookie 賦值不會刪除掉原有的 Cookies,而只會增添 Cookies 或更改原有 Cookie。賦值的格式:
document.cookie = 'cookieName=' + escape('cookieValue')
+ ';expires=' + expirationDateObj.toGMTString();
是不是看到頭暈了呢?以上不是粗體字的地方是要照抄不誤的,粗體字是要按實際情況做出改動的。cookieName 表示 Cookie 的名稱,cookieValue 表示 Cookie 的值,expirationDateObj 表示儲存著失效日期的日期對象名,如果不需要指定失效日期,則不需要第二行。不指定失效日期,則瀏覽器默認是在關閉瀏覽器(也就是關閉所有窗口)之后過期。
看到了上面的一些下劃線了么?這些是應該注意的地方。
首先 escape() 方法:為什么一定要用?因為 Cookie 的值的要求是“只能用可以用在 URL 編碼中的字符”。我們知道“escape()”方法是把字符串按 URL 編碼方法來編碼的,那我們只需要用一個“escape()”方法來處理輸出到 Cookie 的值,用“unescape()”來處理從 Cookie 接收過來的值就萬無一失了。而且這兩個方法的最常用途就是處理 Cookies。其實設定一個 Cookie 只是“document.cookie = 'cookieName=cookieValue'”這么簡單,但是為了避免在 cookieValue 中出現 URL 里不準出現的字符,還是用一個 escape() 好。
然后“expires”前面的分號:注意到就行了。是分號而不是其他。
最后 toGMTString() 方法:設定 Cookie 的時效日期都是用 GMT 格式的時間的,其它格式的時間是沒有作用的。
現在我們來實戰一下。設定一個“name=rose”的 Cookie,在 3 個月后過期。
var expires = new Date();
expires.setTime(expires.getTime() + 3 * 30 * 24 * 60 * 60 * 1000);
/* 三個月 x 一個月當作 30 天 x 一天 24 小時
x 一小時 60 分 x 一分 60 秒 x 一秒 1000 毫秒 */
document.cookie = 'name=rose;expires=' + expires.toGMTString();
為什么沒有用 escape() 方法?這是因為我們知道 rose 是一個合法的 URL 編碼字符串,也就是說,'rose' == escape('rose')。一般來說,如果設定 Cookie 時不用 escape(),那獲取 Cookie 時也不用 unescape()。
再來一次:編寫一個函數,作用是查找指定 Cookie 的值。
function getCookie(cookieName) {
var cookieString = document.cookie;
var start = cookieString.indexOf(cookieName + '=');
// 加上等號的原因是避免在某些 Cookie 的值里有
// 與 cookieName 一樣的字符串。
if (start == -1) // 找不到
return null;
start += cookieName.length + 1;
var end = cookieString.indexOf(';', start);
if (end == -1) return unescape(cookieString.substring(start));
return unescape(cookieString.substring(start, end));
}
這個函數用到了字符串對象的一些方法,如果你不記得了(你是不是這般沒記性啊),請快去查查。這個函數所有的 if 語句都沒有帶上 else,這是因為如果條件成立,程序運行的都是 return 語句,在函數里碰上 return,就會終止運行,所以不加 else 也沒問題。該函數在找到 Cookie 時,就會返回 Cookie 的值,否則返回“null”。
現在我們要刪除剛才設定的 name=rose Cookie。
var expires = new Date();
expires.setTime(expires.getTime() - 1);
document.cookie = 'name=rose;expires=' + expires.toGMTString();
可以看到,只需要把失效日期改成比現在日期早一點(這里是早 1 毫秒),再用同樣的方法設定 Cookie,就可以刪掉 Cookie 了。