1、錯誤處理
- 任何有影響力的Web應(yīng)用程序都需要一套完善的錯誤處理機制,良好的錯誤處理機制可以讓用戶及時得到提醒。
1.1 try-catch語句
try{
// 可能會導(dǎo)致錯誤的代碼
} catch(error){
// 在錯誤發(fā)生時怎么處理
}
(1)finally 子句
- 雖然在try-catch 語句中是可選的,但finally子句一經(jīng)使用,其代碼無論如何都會執(zhí)行。
- 只要代碼中包含finally子句,則無論try或catch語句塊中包含什么代碼——甚至return 語句,都不會阻止finally 子句的執(zhí)行
function testFinally(){
try {
return 2;
} catch (error){
return 1;
} finally {
return 0;
}
}
/**調(diào)用這個函數(shù)只能返回0**/
(2)錯誤類型
-
執(zhí)行代碼期間可能會發(fā)生的錯誤有多種類型。每種錯誤都有對應(yīng)的錯誤類型,而當(dāng)錯誤發(fā)生時,就會拋出相應(yīng)類型的錯誤對象。
- Error
- EvalError
- RangeError
- ReferenceError
- SyntaxError
- TypeError
- URIError
EvalError 類型的錯誤會在使用eval()函數(shù)而發(fā)生異常時被拋出。
new eval(); //拋出EvalError
eval = foo; //拋出EvalError
- RangeError 類型的錯誤會在數(shù)值超出相應(yīng)范圍時觸發(fā)。
var items1 = new Array(-20); //拋出RangeError
var items2 = new Array(Number.MAX_VALUE); //拋出RangeError
- 在找不到對象的情況下,會發(fā)生ReferenceError。
var obj = x; //在x 并未聲明的情況下拋出 ReferenceError
- 當(dāng)我們把語法錯誤的JavaScript字符串傳入eval()函數(shù)時,就會導(dǎo)致此SyntaxError。
eval("a ++ b"); //拋出SyntaxError
- 在執(zhí)行特定于類型的操作時,變量的類型不符合要求,會導(dǎo)致TypeError。
var o = new 10; //拋出TypeError
alert("name" in true); //拋出TypeError
Function.prototype.toString.call("name"); //拋出TypeError
在使用encodeURI()或decodeURI(),而URI 格式不正確時,就會導(dǎo)致URIError 錯誤。
利用不同的錯誤類型,可以獲悉更多有關(guān)異常的信息,從而有助于對錯誤作出恰當(dāng)?shù)奶幚怼?/p>
try {
someFunction();
} catch (error){
if (error instanceof TypeError){
//處理類型錯誤
} else if (error instanceof ReferenceError){
//處理引用錯誤
} else {
//處理其他類型的錯誤
}
}
(3)合理使用try-catch
- 使用try-catch 最適合處理那些我們無法控制的錯誤。假設(shè)你在使用一個大型JavaScript 庫中的函數(shù),該函數(shù)可能會有意無意地拋出一些錯誤。由于我們不能修改這個庫的源代碼,所以大可將對該函數(shù)的調(diào)用放在try-catch語句當(dāng)中,萬一有什么錯誤發(fā)生,也好恰當(dāng)?shù)靥幚硭鼈儭?/li>
- 在明明白白地知道自己的代碼會發(fā)生錯誤時,再使用try-catch 語句就不太合適了。
1.2 拋出錯誤
與try-catch 語句相配的還有一個throw 操作符,用于隨時拋出自定義錯誤。
拋出錯誤時,必須要給throw 操作符指定一個值,這個值是什么類型,沒有要求。
在遇到throw 操作符時,代碼會立即停止執(zhí)行。僅當(dāng)有try-catch語句捕獲到被拋出的值時,代碼才會繼續(xù)執(zhí)行。
利用原型鏈還可以通過繼承Error 來創(chuàng)建自定義錯誤類型。
function CustomError(message){
this.name = "CustomError";
this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError("My message");
(1)拋出錯誤的時機
- 要針對函數(shù)為什么會執(zhí)行失敗給出更多信息,拋出自定義錯誤是一種很方便的方式。
function process(values){
if (!(values instanceof Array)){
throw new Error("process(): Argument must be an array.");
}
values.sort();
for (var i=0, len=values.length; i < len; i++){
if (values[i] > 100){
return values[i];
}
}
return -1;
}
(2) 拋出錯誤與使用try-catch
如果你打算編寫一個要在很多應(yīng)用程序中使用的JavaScript庫,甚至只編寫一個可能會在應(yīng)用程序內(nèi)部多個地方使用的輔助函數(shù),我都強烈建議你在拋出錯誤時提供詳盡的信息。然后,即可在應(yīng)用程序中捕獲并適當(dāng)?shù)靥幚磉@些錯誤。
捕獲那些確切地知道該如何處理的錯誤。捕獲錯誤的目的在于避免瀏覽器以默認(rèn)方式處理它們;而拋出錯誤的目的在于提供錯誤發(fā)生具體原因的消息。
1.3 錯誤事件
- 在任何Web 瀏覽器中,onerror事件處理程序都不會創(chuàng)建event對象,但它可以接收三個參數(shù):錯誤消息、錯誤所在的URL 和行號。
- 只要發(fā)生錯誤,無論是不是瀏覽器生成的,都會觸發(fā)error事件,并執(zhí)行這個事件處理程序。
window.onerror = function(message, url, line){
alert(message);
//阻止瀏覽器報告錯誤的默認(rèn)行為。
return false;
};
1.4 常見的錯誤類型
- 由于JavaScript 是松散類型的,而且也不會驗證函數(shù)的參數(shù),因此錯誤只會在代碼運行期間出現(xiàn)。一般來說,需要關(guān)注三種錯誤:
- 類型轉(zhuǎn)換錯誤
- 數(shù)據(jù)類型錯誤
- 通信錯誤
(1)類型轉(zhuǎn)換錯誤
- 建議使用全等(===)和不全等(!==)操作符,以避免類型轉(zhuǎn)換。
- 容易發(fā)生類型轉(zhuǎn)換錯誤的另一個地方,就是流控制語句。像if之類的語句在確定下一步操作之前,會自動把任何值轉(zhuǎn)換成布爾值。尤其是if語句,如果使用不當(dāng),最容易出錯。
function concat(str1, str2, str3){
var result = str1 + str2;
if (str3){ //絕對不要這樣!!!
result += str3;
}
return result;
}
function concat(str1, str2, str3){
var result = str1 + str2;
if (typeof str3 == "string"){ //恰當(dāng)?shù)谋容^
result += str3;
}
return result;
}
(2) 數(shù)據(jù)類型錯誤
- 為了保證不會發(fā)生數(shù)據(jù)類型錯誤,只能依靠開發(fā)人員編寫適當(dāng)?shù)臄?shù)據(jù)類型檢測代碼。在將預(yù)料之外的值傳遞給函數(shù)的情況下,最容易發(fā)生數(shù)據(jù)類型錯誤。
/**例子一**/
//不安全的函數(shù),任何非字符串值都會導(dǎo)致錯誤
function getQueryString(url){
var pos = url.indexOf("?");
if (pos > -1){
return url.substring(pos +1);
}
return "";
}
function getQueryString(url){
if (typeof url == "string"){ //通過檢查類型確保安全
var pos = url.indexOf("?");
if (pos > -1){
return url.substring(pos +1);
}
}
return "";
}
/**例子二**/
//不安全的函數(shù),任何非數(shù)組值都會導(dǎo)致錯誤
function reverseSort(values){
if (values){ //絕對不要這樣!!!
values.sort();
values.reverse();
}
}
//不安全的函數(shù),任何非數(shù)組值都會導(dǎo)致錯誤
function reverseSort(values){
if (values != null){ //絕對不要這樣!!!
values.sort();
values.reverse();
}
}
//還是不安全,任何非數(shù)組值都會導(dǎo)致錯誤
function reverseSort(values){
if (typeof values.sort == "function"){ //絕對不要這樣!!!
values.sort();
values.reverse();
}
}
//安全,非數(shù)組值將被忽略
function reverseSort(values){
if (values instanceof Array){ //問題解決了
values.sort();
values.reverse();
}
}
(3)通信錯誤
- 第一種通信錯誤與格式不正確的URL或發(fā)送的數(shù)據(jù)有關(guān)。最常見的問題是在將數(shù)據(jù)發(fā)送給服務(wù)器之前,沒有使用encodeURIComponent()對數(shù)據(jù)進(jìn)行編碼。
- 對于查詢字符串,應(yīng)該記住必須要使用encodeURIComponent()方法。為了確保這一點,有時候可以定義一個處理查詢字符串的函數(shù)。
function addQueryStringArg(url, name, value){
if (url.indexOf("?") == -1){
url += "?";
} else {
url += "&";
}
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
1.5 區(qū)分致命錯誤和非致命錯誤
- 對于非致命錯誤,可以根據(jù)下列一或多個條件來確定:
- 不影響用戶的主要任務(wù);
- 只影響頁面的一部分;
- 可以恢復(fù);
- 重復(fù)相同操作可以消除錯誤。
- 致命錯誤,可以通過以下一或多個條件來確定:
- 應(yīng)用程序根本無法繼續(xù)運行;
- 錯誤明顯影響到了用戶的主要操作;
- 會導(dǎo)致其他連帶錯誤。
for (var i=0, len=mods.length; i < len; i++){
mods[i].init(); //可能會導(dǎo)致致命錯誤
}
for (var i=0, len=mods.length; i < len; i++){
try {
mods[i].init();
} catch (ex) {
//在這里處理錯誤
}
}
1.6 把錯誤記錄到服務(wù)器
- 開發(fā)Web 應(yīng)用程序過程中的一種常見的做法,就是集中保存錯誤日志,以便查找重要錯誤的原因。
- 要建立這樣一種JavaScript錯誤記錄系統(tǒng),首先需要在服務(wù)器上創(chuàng)建一個頁面用于處理錯誤數(shù)據(jù)。這個頁面的作用無非就是從查詢字符串中取得數(shù)據(jù),然后再將數(shù)據(jù)寫入錯
誤日志中。 - 這個頁面可能會使用如下所示的函數(shù):
function logError(sev, msg){
var img = new Image();
img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" +
encodeURIComponent(msg);
}
- 使用了Image 對象來發(fā)送請求,這樣做非常靈活,主要表現(xiàn)如下幾方面。
(1)所有瀏覽器都支持Image 對象,包括那些不支持XMLHttpRequest 對象的瀏覽器。
(2)可以避免跨域限制。通常都是一臺服務(wù)器要負(fù)責(zé)處理多臺服務(wù)器的錯誤,而這種情況下使用XMLHttpRequest 是不行的。
(3)在記錄錯誤的過程中出問題的概率比較低。
for (var i=0, len=mods.length; i < len; i++){
try {
mods[i].init();
} catch (ex){
logError("nonfatal", "Module init failed: " + ex.message);
}
}
2、調(diào)試技術(shù)
(1)將消息記錄到控制臺
- 可以通過console 對象向JavaScript 控制臺中寫入消息。
- error(message):將錯誤消息記錄到控制臺
- info(message):將信息性消息記錄到控制臺
- log(message):將一般消息記錄到控制臺
- warn(message):將警告消息記錄到控制臺
(2)將消息記錄到當(dāng)前頁面
- 在頁面中開辟一小塊區(qū)域,用以顯示消息
(3)拋出錯誤
function divide(num1, num2){
if (typeof num1 != "number" || typeof num2 != "number"){
throw new Error("divide(): Both arguments must be numbers.");
}
return num1 / num2;
}
- 對于大型應(yīng)用程序來說,自定義的錯誤通常都使用assert()函數(shù)拋出。這個函數(shù)接受兩個參數(shù),一個是求值結(jié)果應(yīng)該為true的條件,另一個是條件為false時要拋出的錯誤。
//基本的assert()函數(shù)
function assert(condition, message){
if (!condition){
throw new Error(message);
}
}
- 使用assert()函數(shù)可以減少拋出錯誤所需的代碼量.
function divide(num1, num2){
assert(typeof num1 == "number" && typeof num2 == "number",
"divide(): Both arguments must be numbers.");
return num1 / num2;
}
