前端的 JavaScript 開(kāi)發(fā)中,發(fā)現(xiàn)開(kāi)發(fā)者對(duì)于錯(cuò)誤異常的處理普遍都比較簡(jiǎn)單粗暴,如果應(yīng)用程序中缺少有效的錯(cuò)誤處理和容錯(cuò)機(jī)制,代碼的健壯性就無(wú)從談起。本文整理出了一些常見(jiàn)的錯(cuò)誤異常處理的場(chǎng)景,旨在為前端的 JavaScript 錯(cuò)誤異常處理提供一些基礎(chǔ)的指導(dǎo)。
先來(lái)簡(jiǎn)單介紹一下 JavaScript 中的 Error 對(duì)象,通常 Error 對(duì)象由重要的兩部分組成,包含了 error.message 錯(cuò)誤信息和 error.stack 錯(cuò)誤追溯棧。
產(chǎn)生一個(gè)錯(cuò)誤很簡(jiǎn)單,比如在 foo.js 中直接調(diào)用一個(gè)不存在的 callback 函數(shù)。
// foo.js
function foo () {
callback();
}
foo();
此時(shí)通過(guò) Chrome 瀏覽器的控制臺(tái)會(huì)展示如下的信息。
Uncaught ReferenceError: callback is not defined
at foo (foo.js:2)
at foo.js:5
其中 Uncaught ReferenceError: callback is not defined 就是 error.message 錯(cuò)誤信息,而剩下的 at xxx 就是具體的錯(cuò)誤追溯棧,在 Chrome 的控制臺(tái)中,對(duì)錯(cuò)誤的展示進(jìn)行了優(yōu)化。
如果我們通過(guò) window.onerror 來(lái)捕獲到該錯(cuò)誤后將 Error 對(duì)象直接輸出到頁(yè)面中會(huì)展示出更原始的數(shù)據(jù)。
<!-- 展示錯(cuò)誤的容器 -->
<textarea id="error"></textarea>
// 輸出錯(cuò)誤
window.onerror=function (msg, url, line, col, err) {
document.getElementById('error').textContent=err.message + '\n\n' + err.stack;
};
原始的錯(cuò)誤數(shù)據(jù)中會(huì)展示出錯(cuò)誤追溯棧中的 Source URL。
callback is not defined
ReferenceError: callback is not defined
at foo (http://example.com/js-error/foo.js:2:5)
at http://example.com/js-error/foo.js:5:1
有了錯(cuò)誤追溯棧,就能通過(guò)發(fā)生錯(cuò)誤的文件 Source URL 和錯(cuò)誤在代碼中的具體位置來(lái)快速定位到錯(cuò)誤。
看起來(lái)好像很簡(jiǎn)單,但實(shí)際的開(kāi)發(fā)中如何有效的捕獲錯(cuò)誤,如何有效的拋出錯(cuò)誤都有一些需要注意的點(diǎn),下面逐個(gè)的來(lái)講解。
前端在捕獲錯(cuò)誤時(shí)都會(huì)通過(guò)綁定 window.onerror 事件來(lái)捕獲全局的 JavaScript 執(zhí)行錯(cuò)誤,標(biāo)準(zhǔn)的瀏覽器在響應(yīng)該事件時(shí)會(huì)依次提供 5 個(gè)參數(shù)。
window.onerror=function(message, source, lineno, colno, error) { ... }
使用 window.addEventListener 也能綁定 error 事件,但是該事件函數(shù)的參數(shù)是一個(gè) ErrorEvent 對(duì)象。
綁定 window.onerror 事件時(shí),事件處理函數(shù)的第 5 個(gè)參數(shù)在低版本瀏覽中或 JS 資源跨域場(chǎng)景下可能不是 Error 對(duì)象。
在 Chrome 瀏覽器中如果頁(yè)面加載的 JS 資源文件中存在跨域的 script 標(biāo)簽,在發(fā)生錯(cuò)誤時(shí)會(huì)提示 Script error 而缺乏錯(cuò)誤追溯棧。
window.onerror 在響應(yīng)跨域 JavaScript 錯(cuò)誤時(shí)缺乏錯(cuò)誤追溯棧時(shí)的 arguments 對(duì)象如下:
[
'Script error.',
'',
0,
0,
null
]
為了正常的捕獲到跨域 JS 資源文件的錯(cuò)誤,需要具備兩個(gè)條件: 1. 為 JS 資源文件增加 CORS 響應(yīng)頭。 2. 通過(guò) script 引用該 JS 文件時(shí)增加 crossorigin="anonymous" 的屬性,如果是動(dòng)態(tài)加載的 JS,可以寫(xiě)作 script.crossOrigin=true 。
window.onerror 能捕獲一些全局的 JavaScript 錯(cuò)誤,但還有不少場(chǎng)景在全局是捕獲不到的。
window.onerror 能捕獲全局場(chǎng)景下的錯(cuò)誤,如果已知一些程序的場(chǎng)景中可能會(huì)出現(xiàn)錯(cuò)誤,這個(gè)時(shí)候一般會(huì)使用 try/catch 來(lái)進(jìn)行捕獲。
但是在使用 try/catch 塊時(shí)無(wú)法捕獲異步錯(cuò)誤,例如塊中使用了 setTimeout 。
try {
setTimeout(function () {
callTimeout(); // callTimeout 未定義,會(huì)拋錯(cuò)
}, 1000);
}
catch (err) {
console.log('catch the error', err); // 不會(huì)被執(zhí)行
}
try/catch 在處理 setTimeout 這類(lèi)異步場(chǎng)景時(shí)是無(wú)效的,執(zhí)行時(shí)仍會(huì)拋錯(cuò),catch 中的代碼不會(huì)被執(zhí)行。
雖然在 try/catch 中沒(méi)有捕獲到,此時(shí)如果有綁定 window.onerror 則會(huì)被全局捕獲。
由此可見(jiàn), try/catch 應(yīng)該是只能捕獲 JS Event Loop 中同步的任務(wù)。
如果想正確的捕獲 setTimeout 中的錯(cuò)誤,需要將 try/catch 塊寫(xiě)到 setTimeout 的函數(shù)中。
setTimeout(function () {
try {
callTimeout(); // callTimeout 未定義,不會(huì)拋錯(cuò)
}
catch (err) {
console.log('catch the error', err); // 將會(huì)被執(zhí)行
}
}, 1000);
Promise 有自己的錯(cuò)誤處理機(jī)制,通常 Promise 函數(shù)中的錯(cuò)誤無(wú)法被全局捕獲。
var promise=new Promise(executor);
promise.then(onFulfilled, onRejected);
比較容易遺漏錯(cuò)誤處理的地方有 executor 和 onFulfilled ,在這些函數(shù)中如果發(fā)生錯(cuò)誤都不能被全局捕獲。
正確的捕獲 Promise 的錯(cuò)誤,應(yīng)該使用 Promise.prototype.catch 方法,意外的錯(cuò)誤和使用 reject 主動(dòng)捕獲的錯(cuò)誤都會(huì)觸發(fā) catch 方法。
catch 方法中通常會(huì)接收到一個(gè) Error 對(duì)象,但是當(dāng)調(diào)用 reject 函數(shù)時(shí)傳入的是一個(gè)非 Error 對(duì)象時(shí),catch 方法也會(huì)接收到一個(gè)非 Error 對(duì)象,這里的 reject 和 throw 的表現(xiàn)是一樣的,所以在使用 reject 時(shí),最好是傳入一個(gè) Error 對(duì)象。
reject(
new Error('this is reject message')
);
值得注意的是,如果 Promise 的 executor 中存在 setTimeout 語(yǔ)句時(shí), setTimeout 的報(bào)錯(cuò)會(huì)被全局捕獲。
Async Function 和 Promise 一樣,發(fā)生錯(cuò)誤不會(huì)被全局的 window.onerror 捕獲,所以在使用時(shí)如果有報(bào)錯(cuò),需要手動(dòng)增加 try/catch 語(yǔ)句。
匿名函數(shù)的使用在 JavaScript 中很常見(jiàn),但是當(dāng)出現(xiàn)匿名函數(shù)的報(bào)錯(cuò)時(shí),在錯(cuò)誤追溯棧中會(huì)以 anonymous 來(lái)標(biāo)識(shí)錯(cuò)誤,為了排查錯(cuò)誤方便,可以將函數(shù)進(jìn)行命名,或者使用函數(shù)的 displayName 屬性。
函數(shù)如果有 displayName 屬性,在錯(cuò)誤棧中會(huì)展示該屬性值,如果用于命名重要的業(yè)務(wù)邏輯屬性,將有效幫助排查錯(cuò)誤。
上面說(shuō)了很多錯(cuò)誤捕獲的注意點(diǎn),如果要主動(dòng)的拋錯(cuò),都會(huì)使用 throw 來(lái)拋錯(cuò),常見(jiàn)的幾種拋錯(cuò)方法如下:
throw new Error('Problem description.') // 方法 1
throw Error('Problem description.') // 方法 2
throw 'Problem description.' // 方法 3
throw null // 方法 4
其中方法 1 和方法 2 的效果一樣,瀏覽器都能正確的展示錯(cuò)誤追溯棧。方法 3 和方法 4 不推薦,雖然能拋錯(cuò),但是在拋錯(cuò)的時(shí)候不能展示錯(cuò)誤追溯棧。
try/catch 和 throw ,一個(gè)用來(lái)捕獲錯(cuò)誤,一個(gè)用來(lái)拋出錯(cuò)誤,如果兩個(gè)結(jié)合起來(lái)用通常等于脫了褲子放屁多此一舉,唯一有點(diǎn)用的是可以對(duì)錯(cuò)誤信息進(jìn)行再加工。
可以在 Chrome 控制臺(tái)中模擬出一個(gè)結(jié)合使用的實(shí)際場(chǎng)景。
try {
foo();
}
catch (err) {
err.message='Catch the error: ' + err.message;
throw Error(err);
}
由于在 catch 塊中又拋出了錯(cuò)誤,所以該錯(cuò)誤沒(méi)有被捕獲到,但此時(shí)錯(cuò)誤信息經(jīng)過(guò)了二次封裝。
Uncaught Error: ReferenceError: Catch the error: foo is not defined
通過(guò)對(duì)錯(cuò)誤信息的二次封裝,可以增加一些有利于快速定位錯(cuò)誤的額外信息。
原作者:雨夜帶刀's Blog
當(dāng)javascript代碼中出現(xiàn)錯(cuò)誤的時(shí)候,js引擎就會(huì)根據(jù)js的調(diào)用棧逐級(jí)尋找對(duì)應(yīng)的catch,如果沒(méi)有找到相應(yīng)的catch handler或catch handler本身又有error或者又拋出新的error,最后就會(huì)把這個(gè)error的處理交給瀏覽器,并顯示在錯(cuò)誤控制臺(tái)中)顯示錯(cuò)誤信息給訪問(wèn)者。
是js提供的異常處理機(jī)制,用法如下:
try {
// 這段代碼從上往下運(yùn)行,其中任何一個(gè)語(yǔ)句拋出異常該代碼塊就結(jié)束運(yùn)行}
catch (e) {
// 如果try代碼塊中拋出了異常,catch代碼塊中的代碼就會(huì)被執(zhí)行。
// e是一個(gè)局部變量,用來(lái)指向Error對(duì)象或者其他拋出的對(duì)象
}
finally {
//無(wú)論try中代碼是否有異常拋出(甚至是try代碼塊中有return語(yǔ)句),
//finally代碼塊中始終會(huì)被執(zhí)行
}
顧名思義,典型的語(yǔ)法錯(cuò)誤。
function foo{
}
if{}
Js代碼是從上往下依次執(zhí)行,但是js引擎先解析代碼是否語(yǔ)法出錯(cuò),如果語(yǔ)法都解析不通過(guò),那么顯而易見(jiàn):一行代碼也不會(huì)執(zhí)行,從而會(huì)在控制臺(tái)中輸出語(yǔ)法報(bào)錯(cuò):
syntaxError錯(cuò)誤
變量未定義(也叫未聲明)指的是,當(dāng)程序中使用了一個(gè)未定義的變量則會(huì)報(bào)錯(cuò)。
如下代碼:
var foo=1
var bar=foo + n1
顯而易見(jiàn),n1變量是未定義的就直接使用,從而會(huì)在控制臺(tái)中輸出如下錯(cuò)誤:
XXX is not defined 變量未定義
TypeError錯(cuò)誤指的是數(shù)據(jù)類(lèi)型未正確使用。
例如一:
var foo=function(){
console.log('foo')
}
foo='hello world'
foo()
在某些邏輯下,foo本身存儲(chǔ)的是函數(shù),但是誤把foo賦值了一個(gè)字符串或其它不是函數(shù)的數(shù)據(jù)類(lèi)型,但是foo當(dāng)作函數(shù)來(lái)調(diào)用了,則會(huì)報(bào)TypeError錯(cuò)誤在控制臺(tái)中輸出:
TypeError,xxx is not a function
例如二:
未正確獲取元素,導(dǎo)致得到一個(gè)null而不是DOM節(jié)點(diǎn)對(duì)象后,綁定事件而引發(fā)的TypeError錯(cuò)誤。
<script>
var oBtn=document.getElementById('btn')
//因?yàn)榇a從上往下解析的原因,還未解析到button標(biāo)簽,返回為null。
//null是空對(duì)象,不能綁定任何屬性,onclick雖然是事件,
//但也是對(duì)象中屬性的一部分,所以報(bào)TypeError錯(cuò)誤。
oBtn.onclick=function(){
console.log('bar')
}
</script>
<button id="btn">foo</button>
cannot set property 'onclick' of null
正確錯(cuò)誤是把選擇元素的js代碼放置html標(biāo)簽之后,也就是緊鄰 </body>標(biāo)簽,或放在windo.onload事件中。
<script>
window.onload=function(){
var oBtn=document.getElementById('btn')
//因?yàn)榇a從上往下解析的原因,還未解析到button標(biāo)簽,返回為null。
//null是空對(duì)象,不能綁定任何屬性,onclick雖然是事件,
//但也是對(duì)象中屬性的一部分,所以報(bào)TypeError錯(cuò)誤。
oBtn.onclick=function(){
console.log('bar')
}
}
</script>
<button id="btn">foo</button>
首先,我們需要了解JSON是什么 ?
JSON(JavaScript Object Notation, JS 對(duì)象簡(jiǎn)譜) 是一種輕量級(jí)的數(shù)據(jù)交換格式。它基于 ECMAScript (歐洲計(jì)算機(jī)協(xié)會(huì)制定的js規(guī)范)的一個(gè)子集,采用完全獨(dú)立于編程語(yǔ)言的文本格式來(lái)存儲(chǔ)和表示數(shù)據(jù)。簡(jiǎn)潔和清晰的層次結(jié)構(gòu)使得 JSON 成為理想的數(shù)據(jù)交換語(yǔ)言。
而它的定義規(guī)則和js中字面量聲明對(duì)象很像,所以很多初學(xué)者以為json就是js對(duì)象,其實(shí)這是錯(cuò)誤的。
key : value
名稱(chēng)/值包括字段名稱(chēng)(在雙引號(hào)中),后面寫(xiě)一個(gè)冒號(hào),然后是值:
"name" : "foo"
前方高能~~~
談到這里,json數(shù)據(jù)從哪來(lái)呢?
在請(qǐng)求Ajax的時(shí)候,會(huì)從后臺(tái)服務(wù)器中拿到j(luò)son數(shù)據(jù),往往會(huì)把json解析成js對(duì)象。則前端工程師會(huì)用到JSON.parse方法。有時(shí)候前端也會(huì)定義JSON數(shù)據(jù),如果語(yǔ)法不正確當(dāng)轉(zhuǎn)成js對(duì)象時(shí),則會(huì)報(bào)錯(cuò)。如下代碼:
//var foo='{ name:"bar" }'//name未帶雙引號(hào)
var foo='{ "name":bar }'//bar未帶雙引號(hào)
var f=JSON.parse( foo )
token n in JSON at position
正確的JSON轉(zhuǎn)換js對(duì)象的方式如下:
var foo='{ "name":"bar","age":20 }'//20無(wú)需帶,則理解為數(shù)值類(lèi)型
var f=JSON.parse( foo )
console.log( f ) //{name: "bar", age: 20} ,此時(shí)可以正確的把json轉(zhuǎn)換成js對(duì)象,
//通過(guò) 點(diǎn) 語(yǔ)法,也就是f.name和f.age訪問(wèn)到具體的數(shù)據(jù)
以上是JavaScript中常見(jiàn)的錯(cuò)誤,后期遇到會(huì)不斷更新,感謝小伙伴們的踴躍投稿和留言。
內(nèi)容是《Web前端開(kāi)發(fā)之Javascript視頻》的課件,請(qǐng)配合大師哥《Javascript》視頻課程學(xué)習(xí)。
錯(cuò)誤處理對(duì)于web應(yīng)用開(kāi)發(fā)至關(guān)重要,任何javascript錯(cuò)誤都有可能會(huì)導(dǎo)致網(wǎng)頁(yè)無(wú)法使用,因此作為開(kāi)發(fā)人員,必須要及時(shí)處理有可能出現(xiàn)的錯(cuò)誤;
從IE4.0之后,幾乎所有的瀏覽器都包含了一些基本的錯(cuò)誤處理功能,但并沒(méi)有統(tǒng)一,后來(lái),由ECMAscript添加了異常處理機(jī)制,也就是try…catch…finally結(jié)構(gòu)以及throw操作;
錯(cuò)誤處理的重要性: 好的錯(cuò)誤處理技術(shù)可以讓腳本的開(kāi)發(fā)、調(diào)試和部署更加流暢,能對(duì)代碼進(jìn)行更好的控制;另外,JS缺乏標(biāo)準(zhǔn)的開(kāi)發(fā)環(huán)境;
錯(cuò)誤類(lèi)型:
語(yǔ)法錯(cuò)誤(syntax error):也稱(chēng)為解析錯(cuò)誤,發(fā)生在傳統(tǒng)編程語(yǔ)言的編譯解釋時(shí);發(fā)生語(yǔ)法錯(cuò)誤時(shí),就會(huì)發(fā)生阻塞,也就是不能繼續(xù)執(zhí)行代碼,但只是同一個(gè)線程中的代碼會(huì)受影響,其他線程中的代碼不受影響;
// Uncaught SyntaxError: Invalid or unexpected token
// 未捕獲的語(yǔ)法錯(cuò)誤:無(wú)效或意外的標(biāo)記
document.write("zeronetwork;
運(yùn)行時(shí)錯(cuò)誤(Runtime error):也稱(chēng)為exception異常, 其發(fā)生在編譯期/解釋期后,此時(shí),問(wèn)題并不出現(xiàn)在代碼的語(yǔ)法上,而是在嘗試完成一個(gè)非法的操作;
<input type="button" value="單擊" onclick="handleClick()" />
<script>
// Uncaught ReferenceError: openMy is not defined
// 未捕獲的引用錯(cuò)誤:未定義openMy
function handleClick(){
openMy();
}
</script>
錯(cuò)誤報(bào)告:
因?yàn)槊總€(gè)瀏覽器都有自己的內(nèi)置Javascript解釋程序,所以每種瀏覽器報(bào)告錯(cuò)誤的方式都不同;有些是彈出錯(cuò)誤信息,有些是把信息打印在控制臺(tái)中;
IE(windows): 默認(rèn)情況下,會(huì)彈出包含錯(cuò)誤細(xì)節(jié)的對(duì)話框,并詢(xún)問(wèn)是否繼續(xù)執(zhí)行頁(yè)面上的腳本;如果瀏覽器有調(diào)試器(如:Microsoft Script Debugger) ,此對(duì)話框會(huì)提供一個(gè)是調(diào)試還是忽略的選項(xiàng);如果在IE設(shè)置中取消了”顯示錯(cuò)誤”,那么會(huì)在頁(yè)面左下角顯示一個(gè)黃色的圖標(biāo);
注:如果JS代碼就在HTML里,顯示錯(cuò)誤行是正確的,如果是外部的JS文件,則行號(hào)往往差一行,如第5行則為第4行;
Mozilla(所有平臺(tái)): 在控制臺(tái)中打印錯(cuò)誤信息,并發(fā)出警告;其會(huì)報(bào)告三種類(lèi)型的消息:錯(cuò)誤、嚴(yán)格警告和消息等的;
Safari (MacOS):是對(duì)JavaScript錯(cuò)誤和調(diào)試的支持最差,默認(rèn)情況下,它對(duì)終端用戶(hù)不提供任何javascript錯(cuò)誤報(bào)告;
錯(cuò)誤處理:
Javascript提供了兩種處理錯(cuò)誤的方式:
onerror事件處理函數(shù):
window對(duì)象的onerror屬性是一個(gè)事件處理程序,頁(yè)面上出現(xiàn)異常時(shí),error事件便在window對(duì)象上觸發(fā),并把錯(cuò)誤消息輸出到Javascript控制臺(tái)上,這種方式也稱(chēng)為全局錯(cuò)誤捕獲;如:
window.onload=function(){
show(); // 在onload事件中調(diào)用了一個(gè)不存在的函數(shù)
}
window.onerror=function(){
alert("出現(xiàn)錯(cuò)誤");
return true;
}
獲取錯(cuò)誤信息:
window.onerror事件處理程序在調(diào)用時(shí)可以傳5個(gè)參數(shù),由這5個(gè)參數(shù)可以獲取詳細(xì)的錯(cuò)誤信息;
window.onerror=function(sMessage, sUrl, sLine, sColumn, error){
console.log("Error:" + sMessage + " URL:" + sUrl + " Line:" + sLine + " Column:" + sColumn);
console.log(error);
return true;
}
onerror處理程序的返回值:
如果返回true,則阻止執(zhí)行默認(rèn)的事件處理程序,也就是將通知瀏覽器,事件處理程序已經(jīng)處理了錯(cuò)誤,不需要其他操作,反之會(huì)顯示錯(cuò)誤消息;
某些元素也支持onerror; 但其處理函數(shù)沒(méi)有任何關(guān)于error信息的參數(shù),如:
document.images[0].onerror=function(event){
console.log(event); // Event
console.log(event.type); // error
}
這里的event參數(shù)是一個(gè)類(lèi)型為Event事件對(duì)象,其存儲(chǔ)的信息除了type返回了error,并沒(méi)有其他和錯(cuò)誤相關(guān)的信息;
全局錯(cuò)誤處理window.onerror通常不能恢復(fù)腳本繼續(xù)執(zhí)行,但會(huì)給開(kāi)發(fā)者發(fā)送錯(cuò)誤信息;
window.onerror=function(error){
console.log(error);
}
show();
console.log("中止,不會(huì)被執(zhí)行");
window.onload=function(){
console.log("也不會(huì)被執(zhí)行");
}
可以是簡(jiǎn)單的打印,也可以把錯(cuò)誤保存到日志記錄里;
window.onerror就是綁定在window對(duì)象的error事件,也可以使用標(biāo)準(zhǔn)的添加事件偵聽(tīng)的方式window.addEventListener(eventtype, handler),其需要兩個(gè)參數(shù),eventtype為事件類(lèi)型,在此為error,handler是事件處理函數(shù),其需要一個(gè)參數(shù)event,一個(gè)ErrorEvent類(lèi)型的對(duì)象,其保存著有關(guān)事件和錯(cuò)誤的所有信息,如:
window.addEventListener("error", function(event){
console.log(event); // ErrorEvent
console.log(event.error); // Error對(duì)象
console.log(event.error.name);
console.log(event.error.message);
console.log(event.error.stack);
console.log(event.lineno); // 行
console.log(event.colno); // 列
console.log(event.filename);
});
在實(shí)際的開(kāi)發(fā)中,這兩種方式都會(huì)被使用,只不過(guò)addEventListener有定的兼容必問(wèn)題,所以要兼顧所有的瀏覽器且不太關(guān)注事件對(duì)象本身的話,就使用window.onerror;
當(dāng)加載自不同域的腳本中發(fā)生語(yǔ)法錯(cuò)誤時(shí),為避免信息泄露,語(yǔ)法錯(cuò)誤的細(xì)節(jié)將不會(huì)報(bào)告,只會(huì)返回簡(jiǎn)單的"Script error.";
<script>
window.onerror=function(msg, url, lineNo, columnNo, error){
console.log(msg); // Script error
console.log(url); // ""
console.log(lineNo); // 0
console.log(columnNo); // 0
console.log(error); // null
}
</script>
<script src="https://www.zeronetwork.cn/demo/demo.js"></script>
可以針對(duì)同域和不同域的錯(cuò)誤分開(kāi)處理,如:
<script>
window.onerror=function(msg, url, lineNo, columnNo, error){
var str_error=msg.toLowerCase();
var sub_string="script error";
if(str_error.indexOf(sub_string) > -1)
alert("腳本發(fā)生錯(cuò)誤,詳情請(qǐng)?jiān)诳刂婆_(tái)查看");
else{
var message=[
'消息:' + msg,
'URL:' + url,
'行:' + lineNo,
'列:' + columnNo,
'錯(cuò)誤對(duì)象:' + error
].join(" - ");
alert(message);
}
}
show();
</script>
<script src="https://www.zeronetwork.cn/demo/demo.js"></script>
從上在的執(zhí)行結(jié)果來(lái)看,error事件執(zhí)行了兩次,原因是使用了兩個(gè)script,也就是當(dāng)一個(gè)script有錯(cuò)誤發(fā)生時(shí),它只會(huì)阻止當(dāng)前的script塊,而不會(huì)阻止其他的script塊;如:
<script>
show(); // 會(huì)捕獲
console.log("不會(huì)被執(zhí)行");
myshow(); // 不會(huì)捕獲
</script>
<script src="https://www.zeronetwork.cn/demo/demo.js"></script>
<script>
console.log("執(zhí)行了");
demo(); // 會(huì)捕獲
console.log("不會(huì)被執(zhí)行");
</script>
body元素的onerror特性,也可以充當(dāng)事件處理函數(shù),如:
<body onerror="alert('出現(xiàn)了錯(cuò)誤');return true;">
注意,先注釋掉window.onerror等代碼;
此時(shí),可以直接使用event、source、lineno、colno、error等屬性;
<body onerror="alert(event + '\n' + source + '\n' + lineno + '\n' + colno + '\n' + error);return true;">
當(dāng)然了,也可以為body綁定error事件,此時(shí)各屬性,必須明確指定,如:
document.body.onerror=function(msg, url,lineno,colno,error){
alert(msg + '\n' + url + '\n' + lineno + '\n' + colno + '\n' + error);
return true;
}
try-catch語(yǔ)句:
try語(yǔ)句中為期待正常執(zhí)行的代碼塊,當(dāng)在try語(yǔ)句中發(fā)生錯(cuò)誤,其余代碼會(huì)中止執(zhí)行,catch語(yǔ)句就處理該錯(cuò)誤,如果沒(méi)有錯(cuò)誤,就跳過(guò)catch語(yǔ)句;try和catch必須成對(duì)出現(xiàn);
try{
//code
[break]
}catch([exception]){
//code
}[finally]{
//code
}
// 如
try {
show();
alert("不能執(zhí)行");
} catch (error) {
alert("出現(xiàn)一個(gè)錯(cuò)誤:" + error);
} finally{
alert("管你呢");
}
try語(yǔ)句塊內(nèi)的錯(cuò)誤只會(huì)中止try語(yǔ)句塊中發(fā)生錯(cuò)誤之后的邏輯代碼,并不會(huì)中止整個(gè)腳本的運(yùn)行;執(zhí)行try-catch語(yǔ)句,必須是運(yùn)行時(shí),運(yùn)行時(shí)錯(cuò)誤,也被稱(chēng)為異常;try-catch語(yǔ)句中指定只能有一個(gè)catch子句;try-catch語(yǔ)句適合處理無(wú)法預(yù)知、無(wú)法控制的錯(cuò)誤;finally常被用于無(wú)論結(jié)果是否有異常,都要執(zhí)行的代碼,如:
try{
alert("try");
show();
alert("no exec");
}catch(error){
alert("catch");
}finally{
alert("finally");
}
alert("continute");
代碼執(zhí)行的兩條路徑:如果沒(méi)有異常,執(zhí)行路徑為:try->finally,反之為:try的部分->catch->finally;
一般用于關(guān)閉打開(kāi)的鏈接和釋放資源;
var connection={open: function(){},close: function(){},send: function(data){}}
// var data="大師哥王唯"; // 注釋這一行,讓它產(chǎn)生異常
connection.open();
try{
connection.send(data);
}catch(exception){
console.log("出現(xiàn)一個(gè)錯(cuò)誤");
}finally{
connection.close();
console.log("關(guān)閉了");
}
還有一個(gè)典型的應(yīng)用,讀寫(xiě)文件,如:
function openFile(){};
function writeFile(data){};
function closeFile(){};
openFile();
try{
writeFile();
}catch(error){
console.log(error);
}finally{
closeFile();
}
在try-catch-finally語(yǔ)句塊中的變量是全局變量:
try{
var name="王唯";
show();
var city="蚌埠";
}catch(error){
var age=18;
console.log(name);
}finally{
var sex="男";
console.log(name);
console.log(age);
}
console.log(name);
console.log(city);
console.log(age);
console.log(sex);
try-catch-finally與return:
如果直接在try-catch-finally語(yǔ)句塊中執(zhí)行return,會(huì)拋出異常,如:
try {
console.log("try");
// return; // Illegal return statement 非法返回語(yǔ)句
console.log("try agin");
} catch (error) {
console.log(error);
// return; // Illegal return statement
}finally{
console.log("finally");
// return; // Illegal return statement
}
如:
function foo(){
try {
console.log("try");
return 1;
show();
console.log("try agin");
} catch (error) {
console.log(error);
return 2;
}finally{
console.log("finally");
return 3;
}
}
console.log(foo()); // 3
try-finally:
沒(méi)有catch從句,只有try-finally也可以,目的是,只確保執(zhí)行開(kāi)始和最終的過(guò)程,而不處理錯(cuò)誤,如:
try{
console.log("try");
show();
}finally{
console.log("finally"); // 會(huì)執(zhí)行
}
console.log("over"); // 不會(huì)執(zhí)行,已中止
但此時(shí),還是會(huì)拋出異常的,但此時(shí),會(huì)在執(zhí)行完finally后中止執(zhí)行,并會(huì)查找外部的catch語(yǔ)句;
嵌套try-catch語(yǔ)句:
在catch子句中,也有可能會(huì)發(fā)生錯(cuò)誤,所以就可以使用嵌套的try-catch語(yǔ)句,如:
try {
show();
console.log("不能執(zhí)行");
} catch (error) {
console.log("出現(xiàn)一個(gè)錯(cuò)誤:" + error);
try {
var arr=new Array(10000000000000000);
arr.push(error);
} catch (error) {
console.log("又出現(xiàn)了一個(gè)錯(cuò)誤:" + error);
}
} finally{
console.log("管你呢");
}
也可以在try中嵌套try-catch-finally語(yǔ)句,如:
try{
try{
console.log("try");
show();
}catch(error){
console.log("error");
}finally{
console.log("finally");
}
}catch(error){
console.log(error);
}
一個(gè)比較典型的應(yīng)用,就是處理json數(shù)據(jù),如:
// var json='{"name":"wangwei", "age": 18, "sex": "男"}';
var json='{bad json}'; // Uncaught SyntaxError
var data=JSON.parse(json);
console.log(data.name);
console.log(data.age);
console.log(data.sex);
一量json數(shù)據(jù)發(fā)生錯(cuò)誤,整個(gè)應(yīng)用都會(huì)崩潰,所以應(yīng)該使用try-catch,如:
<div id="msg">您的信息:</div>
<script>
window.onload=function(){
var msg=document.getElementById("msg");
try{
// var json='{"name":"王唯", "age": 18, "sex": "男"}';
var json='{bad json}'; // Uncaught SyntaxError
var data=JSON.parse(json);
msg.innerHTML +="姓名:" + data.name + ",年齡:" + data.age + ",性別:" + data.sex;
}catch(error){
msg.innerHTML="開(kāi)小差了,找不到你的信息";
}
}
</script>
當(dāng)使用了try-catch語(yǔ)句,就不會(huì)將錯(cuò)誤提交給瀏覽器,也就不會(huì)觸發(fā)error事件,如:
window.onerror=function(error){
console.log(error); // 不會(huì)觸發(fā)
}
try{
show();
}catch(error){
console.log(error);
}
Error錯(cuò)誤對(duì)象:
在catch中會(huì)捕獲一個(gè)Error錯(cuò)誤對(duì)象;該對(duì)象在Javascript解析或運(yùn)行時(shí),一旦發(fā)生錯(cuò)誤,引擎就會(huì)拋出這個(gè)對(duì)象;如果沒(méi)有相關(guān)聯(lián)的try-catch捕獲該對(duì)象,就由瀏覽器輸出這個(gè)對(duì)象;
// ...
console.log("錯(cuò)誤:" + error + " name:" + error.name + " message:" + error.message);
也可以通過(guò)Error的構(gòu)造器創(chuàng)建一個(gè)錯(cuò)誤對(duì)象,這個(gè)Error對(duì)象也可用于用戶(hù)自定義的異常;語(yǔ)法:new Error([message[, filename[, lineNumber]]]);
實(shí)例化Error對(duì)象,也可以不使用new關(guān)鍵字,如:
var error=new Error("自定義錯(cuò)誤對(duì)象");
var error=Error("不使用new");
console.log(error);
console.log(error.name); // Error
console.log(error.message); // 自定義錯(cuò)誤對(duì)象
Error錯(cuò)誤對(duì)象屬性:
Error類(lèi)還有6個(gè)子類(lèi),其可以通過(guò)錯(cuò)誤對(duì)象的name屬性返回具體異常類(lèi)的名稱(chēng):
// EvalError
try{
throw new EvalError("Eval異常");
}catch(error){
console.log(error);
console.log(error instanceof EvalError); // true
console.log(error.name); // EvalError
console.log(error.message); // Eval異常
}
// RangeError
var num=1;
try{
num.toPrecision(500); // [pr??s??n] 精度
}catch(error){
console.log(error);
}
// ReferenceError
var x;
try {
x=y + 1;
} catch (error) {
console.log(error);
}
// SyntaxError
try{
eval("alert('wangwei)");
}catch(error){
console.log(error);
}
// TypeError
var num=1;
try{
num.toUpperCase(); // 無(wú)法將數(shù)字轉(zhuǎn)為大寫(xiě)
}catch(error){
console.log(error);
}
// URIError (malformed [?m?l?f??md]格式不正確,畸形的)
try{
decodeURI("%%%"); // 使用了非法字符
}catch(error){
console.log(error);
}
Error對(duì)象的message屬性是瀏覽器生成的用于表示錯(cuò)誤描述的信息,因?yàn)檫@個(gè)屬性是特定于瀏覽器的,所以不同的瀏覽器上可能產(chǎn)生不同的錯(cuò)誤信息,如:
try {
eval("a ++ b");
show();
console.log("執(zhí)行了嗎?");
} catch (error) {
// SyntaxError:Unexpected identifier或
// SyntaxError:unexpected token: identifier
console.log(error.name + ":" + error.message);
}
使用name屬性判斷錯(cuò)誤類(lèi)型:
try {
eval("a++b");
} catch (error) {
console.log(error instanceof SyntaxError); // true
if(error.name=="SyntaxError")
console.log(error.name + ":" + error.message);
else
console.log("未知錯(cuò)誤:" + error.message);
}
拋出異常:
throw語(yǔ)句的作用是手動(dòng)中斷程序執(zhí)行,拋出一個(gè)錯(cuò)誤,一般用于有目的的拋出異常,也就是允許開(kāi)發(fā)者可以創(chuàng)建自定義錯(cuò)誤;
throw可以拋出任何類(lèi)型的值,也就是說(shuō),它的參數(shù)可以是任何值;語(yǔ)法:throw error_object;error_object可以是字符串、數(shù)字、布爾或?qū)ο螅?/p>
throw "出現(xiàn)一個(gè)錯(cuò)誤";
throw 50666;
throw true;
throw new Object();
throw {
toString:function(){
return 'Error!';
}
}
function getRectArea(width, height){
if(isNaN(width) || isNaN(height))
throw '參數(shù)應(yīng)該是number類(lèi)型';
return width * height;
}
getRectArea("wangwei",10);
對(duì)于Javascript引擎來(lái)說(shuō),只要遇到throw語(yǔ)句,程序就會(huì)終止;
也可以拋出一個(gè)Error錯(cuò)誤對(duì)象;Error對(duì)象的構(gòu)造函數(shù)只有一個(gè)參數(shù),
throw new Error("請(qǐng)?jiān)俅螄L試");
其他Error子類(lèi)對(duì)象也可以拋出:
throw new SyntaxError("...");
throw new TypeError("...");
throw new RangeError("...");
throw new EvalError("...");
throw new URIError("...");
throw new ReferenceError("...");
對(duì)于Error類(lèi)和其子類(lèi)來(lái)說(shuō),錯(cuò)誤對(duì)象的name就是其構(gòu)造函數(shù)的名稱(chēng),message是其構(gòu)造函數(shù)的參數(shù);
當(dāng)拋出異常后,throw之后的語(yǔ)句將不會(huì)執(zhí)行,并跳到相關(guān)聯(lián)的catch語(yǔ)句中進(jìn)行處理,如:
<h1>請(qǐng)輸入18-99之間的數(shù)字</h1>
<input id="txtInput" type="text" />
<button id="btn">確定</button>
<p id="msg"></p>
<script>
function myFun(){
var msg,x;
msg=document.getElementById("msg");
msg.innerHTML="";
x=document.getElementById("txtInput").value;
try{
if(x=="") throw "空的";
if(isNaN(x)) throw "不是數(shù)字";
x=Number(x);
if(x < 18) throw "太小";
if(x > 99) throw "太大";
msg.innerHTML="輸入的值正確:" + String(x);
}
catch(error){
msg.innerHTML="輸入的值不正確:" + error;
}
}
var btn=document.getElementById("btn");
btn.onclick=myFun;
</script>
也可以在某個(gè)語(yǔ)句塊的外部捕獲throw異常,如:
function sum(a,b){
if(arguments.length < 2)
throw new Error("需要兩個(gè)參數(shù)");
else
return a + b;
}
try{
console.log(sum(18));
}catch(error){
// Error:需要兩個(gè)參數(shù)
console.log(error.name + ":" + error.message);
}
可以通過(guò)instanceof判斷異常的類(lèi)型來(lái)特定處理某一類(lèi)的異常,例如可以區(qū)分瀏覽器拋出的異常和開(kāi)發(fā)人員拋出的異常,如:
function sum(a,b){
if(arguments.length < 2)
throw new Error("需要兩個(gè)參數(shù)");
if(isNaN(a) || isNaN(b))
throw "參數(shù)是不是Number類(lèi)型";
return a + b;
}
try{
console.log(sum(18,12));
}catch(error){
if(error instanceof SyntaxError)
console.log("語(yǔ)法錯(cuò)誤:" + error.name + ":" + error.message);
else if(error instanceof Error)
console.log(error.name + ":" + error.message);
else
console.log(error);
}
注:判斷Error類(lèi)型要放到if的最后一個(gè)條件;
即使在catch語(yǔ)句中,還可以根據(jù)實(shí)際情況,再次拋出異常,此時(shí),其可以被外部的try-catch語(yǔ)句塊捕獲(如果存在的話);
當(dāng)發(fā)生異常時(shí),代碼會(huì)立即停止,僅當(dāng)有try-catch語(yǔ)句捕獲到異常時(shí),代碼才會(huì)繼續(xù)執(zhí)行;其背后運(yùn)行的原理是,當(dāng)發(fā)生異常,JavaScript解釋器會(huì)立即停止執(zhí)行的邏輯,并跳轉(zhuǎn)到就近的try-catch異常處理程序,如果發(fā)生異常的代碼塊中沒(méi)有相關(guān)聯(lián)的catch從句,解釋器會(huì)檢查更高層的閉合代碼塊,看它是否有相關(guān)聯(lián)的異常處理程序,以此類(lèi)推,直到找到一個(gè)異常處理程序?yàn)橹梗蝗绻l(fā)生異常的函數(shù)中沒(méi)有處理它的try-catch語(yǔ)句,異常將向上傳播到調(diào)用該函數(shù)的代碼,如此,異常就會(huì)沿著Javascript的語(yǔ)法結(jié)構(gòu)或調(diào)用棧向上傳播;如果沒(méi)有找到任何異常處理程序,JavaScript將把異常當(dāng)成程序錯(cuò)誤來(lái)處理,并通過(guò)瀏覽器報(bào)告給用戶(hù);
自定義錯(cuò)誤類(lèi)型:
可以基于Error類(lèi)來(lái)創(chuàng)建自定義的錯(cuò)誤類(lèi)型,此時(shí)可以使用throw拋出自定義的異常類(lèi),或通過(guò)instanceof來(lái)檢查這個(gè)異常類(lèi)的類(lèi)型,新類(lèi)型需要實(shí)現(xiàn)name和message屬性;
function CustomError(message){
this.name="CustomError";
this.message=message || 'Default Message';
this.stack=(new Error()).stack;
}
// CustomError.prototype=new Error();
// 或者
CustomError.prototype=Object.create(Error.prototype);
CustomError.prototype.constructor=CustomError;
try{
var name="jingjing";
if(name !=="wangwei")
throw new CustomError("自定義的錯(cuò)誤類(lèi)型");
}catch(error){
console.log(error.message);
}
小示例:
function UserException(message){
this.name="UserException";
this.message=message;
}
function getMothName(m){
m=m - 1; // 調(diào)整月份數(shù)字到數(shù)組索引(1=Jan,12=Dec)
var months=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
if(months[m] !=undefined)
return months[m];
else
throw new UserException("Invalid Month No");
}
try{
var myMonth=15;
var monthName=getMothName(myMonth);
}catch(error){
var monthName="未知";
console.log(error.name + ":" + error.message);
}
小示例:驗(yàn)證電話號(hào)碼,如:
function Telephone(num){
num=String(num);
var pattern=/^((0\d{2,3}-\d{7,8})|(1[3584]\d{9}))$/;
if(pattern.test(num)){
this.value=num.match(pattern)[0];
this.valueOf=function(){
return this.value;
};
this.toString=function(){
return String(this.value);
}
}else{
throw new TelephoneFormatException(num);
}
}
function TelephoneFormatException(value){
this.name="TelephoneFormatException";
this.message="電話號(hào)碼格式不正確";
this.value=value;
this.toString=function(){
return this.value + ":" + this.message;
}
}
// 應(yīng)用
var TELEPHONE_INVALID=-1;
var TELEPHONE_UNKNOWN_ERROR=-2;
function verifyTelephone(num){
try{
num=new Telephone(num);
}catch(error){
if(error instanceof TelephoneFormatException)
return TELEPHONE_INVALID;
else
return TELEPHONE_UNKNOWN_ERROR;
}
return num.toString();
}
console.log(verifyTelephone("010-66668888"));
console.log(verifyTelephone("13812345678"));
console.log(verifyTelephone("138123456")); // -1
console.log(verifyTelephone("wangwei")); // -1
常見(jiàn)錯(cuò)誤:
由于javaScript是松散類(lèi)型的,也不會(huì)驗(yàn)證函數(shù)的參數(shù),因此錯(cuò)誤只會(huì)在運(yùn)行時(shí)出現(xiàn);一般來(lái)說(shuō),需要關(guān)注三種錯(cuò)誤:類(lèi)型轉(zhuǎn)換錯(cuò)誤、數(shù)據(jù)類(lèi)型錯(cuò)誤、通信錯(cuò)誤;
類(lèi)型轉(zhuǎn)換錯(cuò)誤:
一般發(fā)生在使用某個(gè)操作符,或者使用其他可能自動(dòng)轉(zhuǎn)換值的數(shù)據(jù)類(lèi)型的語(yǔ)言結(jié)構(gòu)時(shí);
function output(str1,str2,str3){
var result=str1 + str2;
if(str3)
result +=str3;
return result;
}
console.log(output(1,2,3));
console.log(output(1,2));
console.log(output(1,2,0));
console.log(output(1,2,"wangwei"));
這就是一個(gè)非常典型的與期望不一致的方式;
數(shù)據(jù)類(lèi)型錯(cuò)誤:
在流控制語(yǔ)句中使用非布爾值,是極為常見(jiàn)的一個(gè)錯(cuò)誤來(lái)源,為避免此類(lèi)錯(cuò)誤,就要做到在條件比較時(shí)確定傳入的是布爾值,例如,把if語(yǔ)句改成:if(typeof str3=='number');
所以在使用某個(gè)變量或?qū)ο髸r(shí),一定要適當(dāng)?shù)貦z查它的數(shù)據(jù)類(lèi)型,如:
function reverseSort(values){
// if(values){
// values.sort();
// values.reverse();
// }
if(arguments.length > 0){
if(!Array.isArray(values))
return [];
else{
values.sort();
values.reverse();
return values;
}
}
return [];
}
var arr=[3,2,6,9,4];
// var arr=100; // Uncaught TypeError: values.sort is not a function
console.log(reverseSort(arr));
另一個(gè)常見(jiàn)的錯(cuò)誤就是將參數(shù)與null值進(jìn)行比較。與null進(jìn)行比較只能確保相應(yīng)的值不是null和undefined。要確保傳入的值有效,僅檢測(cè)null值是不夠的;
function reverseSort(values){
// if(values !=null){ // 任何非數(shù)組值都會(huì)導(dǎo)致錯(cuò)誤
if(values instanceof Array) // 非數(shù)組值被忽略
values.sort();
values.reverse();
}
return values;
}
var arr=[3,2,6,9,4];
// var arr=100; // Uncaught TypeError: values.sort is not a function
console.log(reverseSort(arr));
// 或
function reverseSort(values, fun){
if(values instanceof Array){
if(fun !=null && typeof fun==="function")
values.sort(fun);
else
values.sort();
}
return values;
}
var arr=[3,2,6,9,4];
console.log(reverseSort(arr, function(a,b){
return a > b ? -1 : 1;
}));
通信錯(cuò)誤:最典型的就是Ajax應(yīng)用,用其可以動(dòng)態(tài)加載信息,但是,javascript與服務(wù)器之間的任何一次通信,都有可能會(huì)產(chǎn)生錯(cuò)誤;
調(diào)試技巧:
使用警告框: 這是最簡(jiǎn)單、流行的方式,如:
function test(){
alert("函數(shù)內(nèi)");
var iNum1=5, iNum2=10;
alert(iNum1);
var iResult=iNum1 + iNum2;
alert(iResult);
}
test();
拋出自定義錯(cuò)誤:
function assert(bCondition, sErrorMessage){
if(!bCondition)
throw new Error(sErrorMessage);
}
function divide(iNum1, iNum2){
assert(arguments.length==2, "divide需要兩個(gè)參數(shù)");
assert((!isNaN(iNum1) && !isNaN(iNum2)), "需要Number類(lèi)型");
return iNum1 / iNum2;
}
console.log(divide(10,2));
console.log(divide(10,"c")); // 異常
// 或
try{
console.log(divide(10,"c"));
}catch(error){
console.log(error.name + ":" + error.message);
}
Javascript校驗(yàn)器:
jslint的主要目的是指出不合規(guī)范的js語(yǔ)法和可能的語(yǔ)法錯(cuò)誤,包括一些不良代碼;官網(wǎng):http://www.jslint.com/
如以下,會(huì)給出警告:
調(diào)試器:
Javascript自身不具備調(diào)試器,但目前所有的瀏覽器可以使用自身的調(diào)試器;
IE調(diào)試:
啟用IE的調(diào)試功能:
菜單“工具”|“Internet選項(xiàng)”命令,打開(kāi)“Internet選項(xiàng)”對(duì)話框,在“高級(jí)”選項(xiàng)卡中,找到兩個(gè)“禁用腳本調(diào)試”復(fù)選框并取消;開(kāi)始調(diào)試,調(diào)試的主要工作是反復(fù)地跟蹤代碼,找出錯(cuò)誤并修正;
設(shè)置斷點(diǎn):
在調(diào)試程序窗口中,將光標(biāo)移動(dòng)到需要添加斷點(diǎn)的行上,按一次F9鍵或單擊,當(dāng)前行的背景色變?yōu)榧t色,并且在窗口左邊界上標(biāo)上紅色的圓點(diǎn),當(dāng)程序運(yùn)行到斷點(diǎn)時(shí)就會(huì)暫停;
運(yùn)行調(diào)試:
單擊繼續(xù)或按F5進(jìn)行逐步運(yùn)行調(diào)試;F10步進(jìn)、F11步入,都可以繼續(xù)向下執(zhí)行;將鼠標(biāo)移動(dòng)到變量名上時(shí),會(huì)顯示變量當(dāng)前時(shí)刻的值;或者在右側(cè)的“監(jiān)視”窗格中可以觀察該變量的值;點(diǎn)擊變量信息框中的變量值或右側(cè)“監(jiān)視”空格中的變量值可以修改變量的當(dāng)前值;更多的調(diào)試操作:查看調(diào)用關(guān)系、監(jiān)視特定變量的值等;
// 示例
var balance=200.0; //
var willPay=20.0;
function pay(_balance, _pay){
return _balance - _pay;
}
function showBalance(){
debugger;
var blnc=pay(balance,willPay);
alert("當(dāng)前余額:" + blnc);
}
showBalance();
日志輸出:
程序運(yùn)行時(shí),有些中間數(shù)據(jù)需要記錄,以便檢查程序運(yùn)行的狀態(tài);對(duì)于JavaScript記錄中間數(shù)據(jù)通常是以日志的形式記錄需要記錄的數(shù)據(jù),再發(fā)送到服務(wù)器上保存起來(lái);日志記錄的內(nèi)容可以是任意的信息,根據(jù)開(kāi)發(fā)者的需要而定;
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。