錯誤處理與調試
JavaScript在錯誤處理調試上一直是它的軟肋,如果腳本出錯,給出的提示經常也讓人摸不着頭腦。ECMAScript第3版為了解決這個問題引入了try...catch和throw語句以及一些錯誤類型,讓開發人員更加适時的處理錯誤。
一.浏覽器錯誤報告
随着浏覽器的不斷更新,JavaScript代碼的調試能力也逐漸變強。IE、Firefox、Safari、Chrome和Opera等浏覽器,都具備報告JavaScript錯誤的機制。隻不過,浏覽器一般面向的是普通使用者,預設情況下會隐藏此類資訊。
IE:在預設情況下,左下角會出現錯誤報告,輕按兩下這個圖示,可以看到錯誤消息對話框。如果開啟禁止腳本調試,那麼出錯的時候,會彈出錯誤調試框。設定方法為:工具->Internet Options選項->進階->禁用腳本調試,取消勾選即可。
Firefox:在預設情況下,錯誤不會通過浏覽器給出提示。但在背景的錯誤控制台可以檢視。檢視方法為:工具->[Web開發者]->Web控制台|錯誤控制台。除了浏覽器自帶的,開發人員為Firefox提供了一個強大的插件:Firebug。它不但可以提示錯誤,還可以調試JavaScript和CSS、DOM、網絡連結錯誤等。
Safari:在預設情況下,錯誤不會通過浏覽器給出提示。是以,我們需要開啟它。檢視方法為:顯示菜單欄->編輯->偏好設定->進階->在菜單欄中顯示開發->顯示Web檢查器|顯示錯誤控制器。
Opera:在預設情況下,錯誤會被隐藏起來。打開錯誤記錄的方式為:顯示菜單欄->檢視->開發者工具->錯誤控制台。
Chrome:在預設情況下,錯誤會被隐藏起來。打開錯誤記錄的方法為:工具->JavaScript控制台。
二.錯誤處理
良好的錯誤處理機制可以及時的提醒使用者,知道發生了什麼事,而不會驚慌失措。為此,作為開發人員,我們必須了解在處理JavaScript錯誤的時候,都有哪些手段和工具可以利用。
try-catch語句
ECMA262第3版引入了try-catch語句,作為JavaScript中處理異常的一種标準方式。
try {//嘗試着執行try包含的代碼
window.abcdefg();//不存在的方法
} catch (e) {//如果有錯誤,執行catch,e是異常對象
alert('發生錯誤啦,錯誤資訊為:' + e);//直接列印調用toString()方法
}
在e對象中,ECMA-262還規定了兩個屬性:message和name,分别列印出資訊和名稱。
alert('錯誤名稱:' + e.name);
alert('錯誤名稱:' + e.message);
PS:Opera9之前的版本不支援這個屬性。并且IE提供了和message完全相同的description屬性、還添加了number屬性提示内部錯誤數量。Firefox提供了fileName(檔案名)、lineNumber(錯誤行号)和stack(棧跟蹤資訊)。Safari添加了line(行号)、sourceId(内部錯誤代碼)和sourceURL(内部錯誤URL)。是以,要跨浏覽器使用,那麼最好隻使用通用的message。
finally子句
finally語句作為try-catch的可選語句,不管是否發生異常處理,都會執行。并且不管try或是catch裡包含return語句,也不會阻止finally執行。
try {
window.abcdefg();
} catch (e) {
alert('發生錯誤啦,錯誤資訊為:' + e.stack);
} finally {//總是會被執行
alert('我都會執行!');
}
PS:finally的作用一般是為了防止出現異常後,無法往下再執行的備用。也就是說,如果有一些清理操作,那麼出現異常後,就執行不到清理操作,那麼可以把這些清理操作放到finally裡即可。
錯誤類型
執行代碼時可能會發生的錯誤有很多種。每種錯誤都有對應的錯誤類型,ECMA-262定義了7種錯誤類型:
1.Error
2.EvalError
3.RangeError
4.ReferenceError
5.SyntaxError
6.TypeError
7.URIError
其中,Error是基類型(其他六種類型的父類型),其他類型繼承自它。Error類型很少見,一般由浏覽器抛出的。這個基類型主要用于開發人員抛出自定義錯誤。
PS:抛出的意思,就是目前錯誤無法處理,丢給另外一個人,比如丢給一個錯誤對象。
new Array(-5);//抛出RangeError(範圍)
錯誤資訊為:RangeError: invalid array length(無效的數組的長度)
PS:RangeError錯誤一般在數值超出相應範圍時觸發
var box = a;//抛出ReferenceError(引用)
錯誤資訊為:ReferenceError: a is not defined(a是沒有定義的)
PS:ReferenceError通常通路不存在的變量産生這種錯誤
a $ b;//抛出SyntaxError(文法)
錯誤資訊為:SyntaxError: missing ; before statement(失蹤;語句之前)
PS:SyntaxError通常是文法錯誤導緻的
new 10;//抛出TypeError(類型 )
錯誤資訊為:TypeError: 10 is not a constructor(10不是一個構造函數)
PS:TypeError通常是類型不比對導緻的
PS:EvalError類型表示全局函數eval()的使用方式與定義的不同時抛出,但實際上并不能産生這個錯誤,是以實際上碰到的可能性不大。
PS:在使用encodeURI()和decodeURI()時,如果URI格式不正确時,會導緻URIError錯誤。但因為URI的相容性非常強,導緻這種錯誤幾乎見不到。
alert(encodeURI('李炎恢'));
利用不同的錯誤類型,可以更加恰當的給出錯誤資訊或處理。
try {
new 10;
} catch (e) {
if (e instanceof TypeError) {//如果是類型錯誤,那就執行這裡
alert('發生了類型錯誤,錯誤資訊為:' + e.message);
} else {
alert('發生了未知錯誤!');
}
}
善用try-catch
在明明知道某個地方會産生錯誤,可以通過修改代碼來解決的地方,是不适合用try-catch的。或者是那種不同浏覽器相容性錯誤導緻錯誤的也不太适合,因為可以通過判斷浏覽器或者判斷這款浏覽器是否存在此屬性和方法來解決。
try {
var box = document.getElementbyid('box');//單詞大小寫錯誤,導緻類型錯誤
} catch (e) {//這種情況沒必要try-catch
alert(e);
}
try {
alert(innerWidth);//W3C支援,IE報錯
} catch (e) {
alert(document.documentElement.clientWidth);//相容IE
}
PS:正常錯誤和這種浏覽器相容錯誤,我們都不建議使用try-catch。因為正常錯誤可以修改代碼即可解決,浏覽器相容錯誤,可以通過普通if判斷即可。并且try-catch比一般語句消耗資源更多,負擔更大。是以,在萬不得已,無法修改代碼,不能通過普通判斷的情況下才去使用try-catch,比如後面的Ajax技術。
抛出錯誤
使用catch來處理錯誤資訊,如果處理不了,我們就把它抛出丢掉。抛出錯誤,其實就是在浏覽器顯示一個錯誤資訊,隻不過,錯誤資訊可以自定義,更加精确和具體。
try {
new 10;
} catch (e) {
if (e instanceof TypeError) {
throw new TypeError('執行個體化的類型導緻錯誤!');//直接中文解釋錯誤資訊
} else {
throw new Error('抛出未知錯誤!');
}
}
PS:IE浏覽器隻支援Error抛出的錯誤,其他錯誤類型不支援。
三.錯誤事件
error事件是當某個DOM對象産生錯誤的時候觸發。
addEvent(window, 'error', function () {
alert('發生錯誤啦!')
});
new 10;//寫在後面
<img src="123.jpg" οnerrοr="alert('圖像加載錯誤!')" />
四.錯誤處理政策
由于JavaScript錯誤都可能導緻網頁無法使用,是以何時搞清楚及為什麼發生錯誤至關重要。這樣,我們才能對此采取正确的應對方案。
常見的錯誤類型
因為JavaScript是松散弱類型語言,很多錯誤的産生是在運作期間的。一般來說,需要關注3種錯誤:
1.類型轉換錯誤;2.資料類型錯誤;3.通信錯誤,這三種錯誤一般會在特定的模式下或者沒有對值進行充分檢查的情況下發生。
類型轉換錯誤
在一些判斷比較的時候,比如數組比較,有相等和全等兩種:
alert(1 == '1');//true
alert(1 === '1');//false
alert(1 == true);//true
alert(1 === true);//false
PS:由于這個特性,我們建議在這種會類型轉換的判斷,強烈推薦使用全等,以保證判斷的正确性。
var box = 10;//可以試試0
if (box) {//10自動轉換為布爾值為true
alert(box);
}
PS:因為0會自動轉換為false,其實0也是數值,也是有值的,不應該認為是false,是以我們要判斷box是不是數值再去列印。
var box = 0;
if (typeof box == 'number') {//判斷box是number類型即可
alert(box);
}
PS:typeof box == 'number'這裡也是用的相等,沒有用全等呀?原因是typeof box本身傳回的就是類型的字元串,右邊也是字元串,那沒必要驗證類型,是以相等就夠了。
資料類型錯誤
由于JavaScript是弱類型語言,在使用變量和傳遞參數之前,不會對它們進行比較來確定資料類型的正确。是以,這樣開發人員必須需要靠自己去檢測。
function getQueryString(url) {//傳遞了非字元串,導緻錯誤
var pos = url.indexOf('?');
return pos;
}
alert(getQueryString(1));
PS:為了避免這種錯誤的出現,我們應該使用類型比較。
function getQueryString(url) {
if (typeof url == 'string') {//判斷了指定類型,就不會出錯了
var pos = url.indexOf('?');
return pos;
}
}
alert(getQueryString(1));
對于傳遞參數除了限制數字、字元串之外,我們對數組也要進行限制。
function sortArray(arr) {
if (arr) {//隻判斷布爾值遠遠不夠
alert(arr.sort());
}
}
var box = [3,5,1];
sortArray(box);
PS:隻用if (arr)判斷布爾值,那麼數值、字元串、對象等都會自動轉換為true,而這些類型調用sort()方法比如會産生錯誤,這裡提一下:空數組會自動轉換為true而非false。
function sortArray(arr) {
if (typeof arr.sort == 'function') {//判斷傳遞過來arr是否有sort方法
alert(arr.sort());//就算這個繞過去了
alert(arr.reverse());//這個就又繞不過去了
}
}
var box = {//建立一個自定義對象,添加sort方法
sort : function () {}
};
sortArray(box);
PS:這斷代碼本意是判斷arr是否有sort方法,因為隻有數組有sort方法,進而判斷arr是數組。但忘記了,自定義對象添加了sort方法就可以繞過這個判斷,且arr還不是數組。
function sortArray(arr) {
if (arr instanceof Array) {//使用instanceof判斷是Array最為合适
alert(arr.sort());
}
}
var box = [3,5,1];
sortArray(box);
通信錯誤
在使用url進行參數傳遞時,經常會傳遞一些中文名的參數或URL位址,在背景處理時會發生轉換亂碼或錯誤,因為不同的浏覽器對傳遞的參數解釋是不同的,是以有必要使用編碼進行統一傳遞。
比如:?user=李炎恢&age=100
var url = '?user=' + encodeURIComponent('張三') + '&age=100';//編碼
PS:在AJAX章節中我們會繼續探讨通信錯誤和編碼問題。
五.調試技術
在JavaScript初期,浏覽器并沒有針對JavaScript提供調試工具,是以開發人員就想出了一套自己的調試方法,比如alert()。這個方法可以列印你懷疑的是否得到相應的值,或者放在程式的某處來看看是否能執行,得知之前的代碼無誤。
var num1 = 1;
var num2 = b;//在這段前後加上alert('')調試錯誤
var result = num1 + num2;
alert(result);
PS:使用alert('')來調試錯誤比較麻煩,重要裁剪和粘貼alert(''),如果遺忘掉沒有删掉用于調試的alert('')将特别頭疼。是以,我們現在需要更好的調試方法。
将消息記錄到控制台
IE8、Firefox、Opera、Chrome和Safari都有JavaScript控制台,可以用來檢視JavaScript錯誤。對于Firefox,需要安裝Firebug,其他浏覽器直接使用console對象寫入消息即可。
console對象的方法
console.error('錯誤!');//紅色帶叉
console.info('資訊!');//白色帶資訊号
console.log('日志!');//白色
console.warn('警告!');//黃色帶感歎号
PS:這裡以Firefox為标準,其他浏覽器會稍有差異。
var num1 = 1;
console.log(typeof num1);//得到num1的類型
var num2 = 'b';
console.log(typeof num2);//得到num2的類型
var result = num1 + num2;
alert(result);//結果是1b,匪夷所思
PS:我們誤把num2指派成字元串了,其實應該是數值,導緻最後的結果是1b。那麼傳統調試就必須使用alert(typeo num1)來看看是不是數值類型,比較麻煩,因為alert()會阻斷後面的執行,看過之後還要删,删完估計一會兒又忘了,然後又要alert(typeof num1)來加深印象。如果用了console.log的話,所有要調試的變量一目了然,也不需要删除,放着也沒事。
将錯誤抛出
之前已經将結果錯誤的抛出,這裡不在贅述。
if (typeof num2 != 'number') throw new Error('變量必須是數值!');
六.調試工具
IE8、Firefox、Chrome、Opera、Safari都自帶了自己的調試工具,而開發人員隻習慣了Firefox一種,是以很多情況下,在Firefox開發調試,然後去其他浏覽器做相容。其實Firebug工具提供了一種Web版的調試工具:Firebug lite。
以下是網頁版直接調用調試工具的代碼:直接複制到浏覽器網址即可。
javascript:(function(F,i,r,e,b,u,g,L,I,T,E){if(F.getElementById(b))return;E=F[i+'NS']&&F.documentElement.namespaceURI;E=E?F[i+'NS'](E,'script'):F[i]('script');E[r]('id',b);E[r]('src',I+g+T);E[r](b,u);(F[e]('head')[0]||F[e]('body')[0]).appendChild(E);E=new%20Image;E[r]('src',I+L);})(document,'createElement','setAttribute','getElementsByTagName','FirebugLite','4','firebug-lite.js','releases/lite/latest/skin/xp/sprite.png','https://getfirebug.com/','#startOpened');
還有一種離線版,把firebug-lite下載下傳好,載入工具即可,導緻最終工具無法運作,其他浏覽器運作完好。雖然Web版本的Firebug Lite可以跨浏覽器使用Firebug,但除了Firefox原生的之外,都不支援斷點、單步調試、監視、控制台等功能。好在,其他浏覽器自己的調試器都有。
PS:Chrome浏覽器必須在伺服器端方可有效。測試也發現,隻能簡單調試,如果遇到錯誤,系統不能自動抛出錯誤給firebug-lite。
1.設定斷點
我們可以選擇Script(腳本),點選要設定斷點的JS腳本處,即可設定斷點。當我們需要調試的時候,從斷點初開始模拟運作,發現代碼執行的流程和變化。
2.單步調試
設定完斷點後,可以點選單步調試,一步步看代碼執行的步驟和流程。上面有五個按鈕:
重新運作:重新單步調試
斷繼:正常執行代碼
單步進入:一步一步執行流程
單步跳過:跳到下一個函數塊
單步退出:跳出執行到内部的函數
3.監控
單擊“監控”頁籤上,可以檢視在單步進入是,所有變量值的變化。你也可以建立監控表達式來重點檢視自己所關心的變量。
4.控制台
顯示各種資訊。之前已了解過。
PS:其他浏覽器除IE8以上均可實作以上的調試功能,大家可以自己常識下。而我們主要采用Firebug進行調試然後相容到其他浏覽器的做法以提高開發效率。