你瞭解“意外處理”這個概念嗎?意外處理是進階程式設計語言(比如 C++、 Java 以及 VBScript)的一個重要功能,它負責控制當應用程式發生錯誤時,應該執行什麼操作。獨立的 JavaScript 並不支援意外處理,這導致一些編程人員更喜歡使用 VBScript 而不是 JavaScript。但是自從出現了IE5,JavaScript 的這方面功能得以補充且增強。通過向文法中增加意外處理功能,Microsoft 將 JavaScript 提升到 C++ 和 Java 的語言標準。我們有理由相信:一些 VBScript 編程人員將很快轉移到 JavaScript 的陣營中。
詳細的內容是:
如何編寫一個 JavaScript 導致意外的意外處理代碼
如何編寫一個使用者導致意外的意外處理代碼
如何在應用程式中編寫使用者導致意外的意外處理代碼
JavaScript 、C++ 和 Java 的意外處理區別
JavaScript 和 VBScript 的意外處理區別
如何避開 JavaScript 和 VBScript 之間的矛盾
在許多程式設計語言中,當試圖執行一個非法指令時,代碼都會導致一個已耗用時間意外。我們可能經曆的一個最微不足道的非法操作就是“除以0”錯誤。JavaScript 不會執行這一操作,它將給結果變數賦值為無窮大。其它的非法操作對於其它程式設計語言也很普遍,包括 JavaScript。訪問一個null 指標或一個Null 物件也是一個例子,訪問一個超出數組合法範圍的數組元素是另一個例子。
對於開發人員而言,通常是爭取避免發生系統錯誤的。系統資訊通常很含糊,一般的使用者看不懂。事實上,系統錯誤將嚴重影響一個程式員的聲譽,因為它們是臭蟲(Bug)的清楚明白的證據。我們希望在代碼中尋找這些錯誤,在它們攻擊作業系統之前發現它們以避免產生錯誤資訊。在代碼中尋找意外會造成代碼臃腫。當語言不支援意外處理時,將正常的代碼流和錯誤尋找混合在一起在所難免。以下的代碼將尋找兩個錯誤 - 訪問一個Null 物件和超出範圍的數組元素:
var newObject = createObject(dataPiece1, dataPiece2, dataPiece3);
if (newObject == null) {
// do something here when the object was not created
...
}
else {
// getElementIndex return an index or -1 for an error
var index = newObject.getElementIndex();
if (index == -1) {
// handle the error case
...
}
else {
{
// finally, you can do something with index
...
}
在 JavaScript 中支援意外處理的關鍵所在(在其它語言中也是一樣,本文後面將做解釋)是 try...catch 語句。 Try 這個關鍵字引導著一塊可能會引起意外的正常處理代碼,Catch 關鍵字引導著一塊意外處理代碼。一旦 try 這個區中引發了一個意外,程式流程的控制就切換到跟隨它的第一個 catch 地區。但是一旦由 JavaScript 引發(這正是程式員所反對的),就沒有機制能夠斷定錯誤的類型。以下是用來類比上面流程的 try...catch 代碼區:
try {
var newObject = createObject(dataPiece1, dataPiece2, dataPiece3);
var index = newObject.getElementIndex();
// do something with index
}
catch {
alert("System Error; Call your sw vendor");
}
下面是另一個例子,可以在你的電腦上實際運行:
< HTML>
< HEAD>
< TITLE> example 1 < /TITLE>
< /HEAD>
< BODY>
< IMG SRC="doc.gif">
< SCRIPT LANGUAGE="JavaScript1.2">
< !--
try {
for (image=0; image < document.images.length + 2; image++) {
alert(document.images[image].border);
}
}
catch (kuku) {
alert("We have experienced a JavaScript error");
}
// -->
< /SCRIPT>
< /BODY>
< /HTML>
這個例子中執行的意外是訪問數組邊界外的一個數組元素。在頁面(doc.gif) 上,圖形數是length (1),所以使用的最大指標是length+1 (2)。請注意傳遞給 catch (捕捉)區的變數kuku。JavaScript允許傳遞任何變數類型,在以後的頁面中將做解釋。雖然我們並不使用這個變數,但你必須要指定一個。
處理常式員導致的意外
並不是所有的意外都由 JavaScript 導致,還有一些是由程式員有意製造的。事實上,好的軟體工程師把包含大量意外代碼的使用作為簡化代碼的一種方式。其中的原因在於:這樣處理後,正常處理流就同查錯部分分開了,從而使代碼可讀性更強,更易於維護和修改。每當要處理一個特定錯誤時,就可以製造一個意外。你不必使用 if...else 就能在正常流和意外處理流之間切換。一旦一個意外已經發生,流量控制就切換到下一個 catch(捕捉)區,在那裡進行判斷和錯誤處理。由於程式員完全明白哪個意外是他自己製造的,因此他可以設計這樣一個機制,很容易地計算出哪個是意外並向使用者提供有用的資訊。
下面這個簡單的例子說明了你可以使用的意外處理機制:
< HTML>
< HEAD>
< TITLE> example 1 < /TITLE>
< /HEAD>
< BODY>
< SCRIPT LANGUAGE="JavaScript1.2">
< !--
function createException(msgNum, msgText) {
this.messageNumber = msgNum;
this.messageText = msgText;
}
function triggerException() {
exceptionObj = new createException(1, "error blablabla");
throw exceptionObj;
}
function raiseException() {
try {
triggerException();
}
catch (exceptionObj) {
if (exceptionObj instanceof createException) {
alert("Call the programmer ASAP")
}
else {
throw exceptionObj;
}
}
}
raiseException();
// -->
< /SCRIPT>
< /BODY>
< /HTML>
主程式只有一行指令碼:raiseException()。正如函數名所表示的,這個程式的唯一目的就是要產生一個意外。raiseException() 函數的主體部分包含 try...catch 語句:
function raiseException() {
try {
triggerException();
}
catch (exceptionObj) {
if (exceptionObj instanceof createException) {
alert("Call the programmer ASAP")
}
else {
throw exceptionObj;
}
}
}
try 代碼區建立一個意外對象並觸發它:
function triggerException() {
exceptionObj = new createException(1, "error blablabla");
throwexceptionObj;
}
意外對象
意外對象是一個規則的 JavaScript 對象。這時我們將對象命名為 exceptionObj,把它命名為 kuku 也可以。這個意外對象的目的是把資料從 try 代碼區運送到 catch 代碼區,以將其破譯並向使用者報告錯誤的細節。無論你希望意外對象包含多少屬性和方法都可以。在我們的例子中定義了兩個屬性:錯誤的資訊數和其文本資訊。對象建立函數是規則的JavaScript:
function createException(msgNum, msgText) {
this.messageNumber = msgNum;
this.messageText = msgText;
}
catch(捕捉)代碼區只有一個參數,可以是任何資料類型。傳遞到 catch 代碼區的對象與錯誤數和錯誤資訊相通訊。注意首先要進行物件類型確認:
if (exceptionObj instanceof createException)
為了區別 JavaScript 和程式員查出的錯誤,這種確認是必要的。正如它的名字所說明的,catch 代碼區捕捉相關的 try 代碼區出來的所有意外。為了讓使用者獲得正確的錯誤資訊,我們需要確定意外是由 JavaScript 導致的(例如訪問超出範圍的數組元素)還是由程式員導致的。最好的方法是證實意外對象是否與我們建立它們所用的對象屬於同一類型。如果類型相匹配,我們就可以肯定意外是要我們去處理的。在這裡的例子中,僅向使用者顯示了一個資訊:
if (exceptionObj instanceof createException) {
alert("Call the programmer ASAP")
}
如果類型不符,我們就繼續向上製造意外。意外處理是嵌套的。你可以將一個 try...catch 語句放在另一個 try 代碼區裡,這樣每當從第一個 catch 代碼區製造一個意外時,它就被其母 catch 代碼區所捕捉。在我們的例子中,由於沒有更高層次的 try...catch 語句,因此意外就由作業系統所處理。作業系統負責處理那些以前沒有被更低層次的 catch 代碼區處理過的意外。下面是我們例子中的意外拋出:
else {
throw exceptionObj;
}
下面我們來增強一點指令碼功能。在上面的指令碼中,我們指定了資訊數以及建立意外對象時的描述:
exceptionObj = new createException(1, "error blablabla");
但是這時 triggerException() 和 raiseException() 函數只適用於一種意外類型。為了讓它們更通用一些,我們將錯誤數和文本參數增加到上面的兩個函數中:
< HTML>
< HEAD>
< TITLE> example 1 < /TITLE>
< /HEAD>
< BODY>
< SCRIPT LANGUAGE="JavaScript1.2">
< !--
function createException(msgNum, msgText) {
this.messageNumber = msgNum;
this.messageText = msgText;
}
function triggerException(messageNum, messageTxt) {
exceptionObj = new createException(messageNum, messageTxt);
throw exceptionObj;
}
function raiseException(num, message) {
try {
triggerException(num, message);
}
catch (exceptionObj) {
if (exceptionObj instanceof createException) {
alert("Call the programmer ASAP")
}
else {
throw exceptionObj;
}
}
}
raiseException(1, "Doc JavaScript Column 36");
// -->
< /SCRIPT>
< /BODY>
< /HTML>
程式員製造的意外:一個真實的例子(一)
現在,我們使用前面頁面中的意外處理函數來處理一個更加實際的例子。假設你的一個頁面中有一個
< IMG> 標記,程式接收一些屬性名稱並提取出相應的繪圖物件屬性值。意外有可能是由訪問不正確的屬性和範圍以外的數組元素引起的。下面是完整的代碼:
< HTML>
< HEAD>
< TITLE> example 1 < /TITLE>
< /HEAD>
< BODY>
< IMG SRC="doc.gif">
< SCRIPT LANGUAGE="JavaScript1.2">
< !--
function createException(msgNum, msgText) {
this.messageNumber = msgNum;
this.messageText = msgText;
}
function triggerException(messageNum, messageTxt) {
exceptionObj = new createException(messageNum, messageTxt);
throw exceptionObj;
}
function raiseException(num, message) {
try {
triggerException(num, message);
}
catch (exceptionObj) {
if (exceptionObj instanceof createException) {
alert("Call the programmer ASAP")
}
else {
throw exceptionObj;
}
}
}
fieldNames = new Array("border", "complete", "height", "hspace", "lowsrc", "name", "src", "vspace", "width");
try {
for (image=0; image < document.images.length + 1; image++) {
for (i=0; i < fieldNames.length; i++) {
if (document.images[image] == null) triggerException( 98, "illegal access to images array");
if (eval("document.images[image]." + fieldNames[i]) != null) alert(fieldNames[i] + " is " + eval("document.images[image]." + fieldNames[i]))
else triggerException(i, "field name " + fieldNames[i] + " is not legal");
}
}
}
catch (exceptionObj) {
if (exceptionObj instanceof createException) {
if (exceptionObj.messageNumber > 0 && exceptionObj.messageNumber < 9) alert("Problem in property: " + exceptionObj.messageText)
else if (exceptionObj.messageNumber < = 98 && exceptionObj.messageNumber >= 97) alert("Problem in images array: " + exceptionObj.messageText)
else raiseException(99, "Catch All");;
}
}
//raiseException(1, "Doc JavaScript Column 36");
// -->
< /SCRIPT>
< /BODY>
< /HTML>
程式員製造的意外:一個真實的例子(二)
首先我們定義數組 fieldNames。Try 代碼區包括兩個嵌套迴圈,外面一個在頁面的圖形之間迴圈:
for (image=0; image < document.images.length + 1; image++) {
裡面的迴圈在 fieldNames 元素之間迴圈:
for (i=0; i < fieldNames.length; i++) {
我們在迴圈內部檢查2個意外。第一檢驗是否有存取範圍外數組元素的嘗試。當有一個無效嘗試時,我們起動一個意外:
if (document.images[image] == null) triggerException( 98, "illegal access to images array");
第二個檢驗列印相關對象屬性的值或當網域名稱與任何對象的網域名稱都不匹配時,起動一個意外:
if (eval("document.images[image]." + fieldNames[i]) != null) alert(fieldNames[i] + " is " + eval("document.images[image]." + fieldNames[i]))
else triggerException(i, "field name " + fieldNames[i] + " is not legal");
catch(捕捉)代碼區對意外對象進行解密。首先我們檢驗對象是否是正確的類型:
if (exceptionObj instanceof createException) {
前面已經解釋過,這種檢驗對於區分 JavaScript 錯誤和使用者錯誤是至關重要的。一旦我們知道它是一個使用者導致的意外後,我們就檢驗錯誤數是否在1—9之間,並列印一個適當的資訊:
if (exceptionObj.messageNumber > 0 && exceptionObj.messageNumber < 9) alert("Problem in property: " + exceptionObj.messageText)
我們檢驗錯誤數是否是97或98,並列印一個與意外相關的資訊:
else if (exceptionObj.messageNumber < = 98 && exceptionObj.messageNumber >= 97) alert("Problem in images array: " + exceptionObj.messageText)
如果意外對象不與任何錯誤類型相匹配,我們就觸發一個用作業系統處理的普通意外:
else raiseException(99, "Catch All")
一定要記住,如果你不處理這個意外,別人就會處理。作業系統會處理所有沒人理會的意外。
與 Java、 C++ 之比較
請不要吃驚,你會發現:Java 和 C++ 的意外處理文法與 JavaScript 的非常相似。同 JavaScript 中一樣,它們也是以 try..catch 語句為中心的。主要的區別在於 catch 的變數。就象前面頁面中所解釋的一樣,在 JavaScript 中這個變數可以是任何資料類型,可以是一個對象、一個字串、一個整數或任何其它被支援的類型。實際上,JavaScript 不支援型別宣告,所以就無法聲明 catch 的變數類型。在 Java 中,catch 的變數是 Java 類的一個例示,將標準 Java 擴充成可導致的類。Catch 語句應該是這樣的:
catch (objectType exception)
其中意外是一個 objectType 類型的對象。C++ 就不是那麼限制了,Catch 的變數可以是標準 C++ 意外類的一個例示,或者是任何 C++ 類型:
catch (char *message)
由於在 JavaScript 中沒有型別宣告,在每個 try 代碼區它只支援一個 catch 代碼區。在前面頁面中已經顯示了,當我們的意外處理要依賴於變數類型時,我們就使用 instanceOf 函數來檢查它是否是正確的類型。在 C++ 和 Java 中,對每一個 try 代碼區你可以使用多個 catch 代碼區。每個 catch 代碼區負責處理適當類型的意外。假如你想處理 char 類型和 myException 類型的意外:
try {
// following function can throw exceptions of type char or myException
doSomeThingUseful();
}
catch (char *exceptionString) {
// handle the case that exception is a string
}
catch (myException exceptionObject)
// handle the case that exception is an instance of type myException
}
在 JavaScript 中,你要用這些程式碼:
try {
// following function can throw exceptions of type char or myException
doSomeThingUseful();
}
catch (exception) {
// verify exception is of myExceptionObject type
if (exception instanceOf myExceptionObject) {
if (exception.kind == "string") {
// handle the sting exception
}
else if (exception.kind == "myException") {
// handle the object exception
}
}
另一個區別是 Java 和 C++ 支援意外 catch-all(全捕捉),也就是在一個 catch 代碼區捕捉全部的意外。在 Java 中的文法是:
catch (Throwable t) {
// Handle all cases
}
其中 Throwable 是一個事先定義的 Java 類。在 C++ 中相應的 catch-all 文法是:
catch ( ... ) {
// Handle all cases
}
正如上面所說過的,由於 JavaScript 只支援一個 catch 代碼區,根據定義,所有的意外都是由同一個 catch 代碼區捕捉的。你應該使用:
catch ( exception ) {
// Handle all exceptions
}
同樣的區別在 C++、 Java 和 JavaScript 的導致語句中也存在。在三種語言中文法是一樣的:
throw exceptionObject;
由於 Java 只能捕捉 Throwable 類型的意外, exceptionObject 必須是這種類型。C++ 和 JavaScript 可以拋出任何資料類型,包括字串、整數、對象等。
C++ 和 Java 在意外向上繁殖的方式上與 JavaScript 也不同。如果一個意外不在 C++ 或 Java 中處理,那麼在調用方案中,它就自動被提升到一個更高層次,如調用函數、主程式或瀏覽器。在 JavaScript 中,如果你希望一個更高層次處理這個意外的話,就不得不自己來提升這個意外。前面頁中已經顯示過,你應該使用這個簡單的文法:
throw exceptionObj;
最後一個區別是 C++ 和 Java 不支援 try..catch 代碼區的嵌套。而在 JavaScript 中,你可以將一個 try...catch 語句裝在另一個語句的 try 或 catch 區內。
與 VBScript 相比較
VBScript 對意外處理的支援程度要低於 JavaScript 的新意外處理方式。如果到現在還因為喜歡其錯誤處理而堅持使用VBScript 的話,你就沒有充分的理由了,因為 JavaScript 現在可以處理意外了。VBScript 意外處理的原理很簡單,調用函數然後檢驗是否有錯誤。
JavaScript 是用 catch 語句來捕捉意外的,而 VBScript 是用 On Error 語句來捕捉的。省略掉 On Error 語句會將所有意外傳播到瀏覽器,瀏覽器隨之向使用者顯示一個錯誤對話方塊。事實上, JavaScript 也有這種行為。如果不使用 try...catch 語句,所有意外都會由瀏覽器來處理並向使用者顯示一個錯誤資訊。前面已經解釋過,使用者並不十分清楚他該向哪裡去,因此必須要趕在瀏覽器之前對意外進行處理。如果考慮為使用者處理意外,這樣的 Web 網站就屬於等級較高的。
On Error 語句很直觀:
On Error Resume Next
這個語句只是開啟了錯誤處理。如果接下來的行中的任何位置出現了意外,運行就從緊接著出錯行的那行程式繼續執行,出錯行指的是你調用了引起意外的函數的行。在 JavaScript 中與此相當的是 try...catch 語句:
try {
// Exceptions in this block will be handled by the following catch block.
// Any exception will quit this block and switch control to the catch block
}
catch (exceptionObj) {
// Handle exception from the try block above
// Code in this block will be executed before the line immediately
// following the culprit line that caused the exception
}
在 VBScript 和 JavaScript 中捕捉意外的主要區別是:一旦你開啟了錯誤處理,在 VBScript 中就不能將其關閉。在VBScript中,錯誤處理只能在函數結束時才能關閉。而在 JavaScript 中,你可以將你想要開啟錯誤處理的那個語句區包含在 try 代碼區內。簡單地說,在VBScript 和 JavaScript 中,你都可以控制一個區的開始。而只有在 JavaScript 中你才能控制一個區的結束。在 VBScript 中,區的結束就是函數的結束。
在 VBScript 中處理意外比捕捉它們要複雜得多。在每個可疑語句之後,你都要明確地檢查錯誤:
` turn on error handling
On Error Resume Next
` clear the Err object
Err.Clear
` The following statement or procedure call might cause an exception
Kuku()
` Check if an exception was raised
If Err.Number > 0 then
` handle the exception here
End If
你已經注意到了 Err 對象。 VBScript 中的意外處理就是以它為基礎的。它有6個屬性是由最近的意外設定的: Number(數量)、 Description(描述)、 Source(來源)、 HelpFile(協助檔案)、 HelpContext(協助上下文)、 LastDLLError(上一個DLL錯誤)。 JavaScript 的 Error 對象共用這些屬性中的兩個