錯誤處理機制

Error 實例對象

JavaScript 解析或運行時旬痹,一旦發(fā)生錯誤撵割,引擎就會拋出一個錯誤對象弄诲。JavaScript 原生提供Error構造函數(shù)菱鸥,所有拋出的錯誤都是這個構造函數(shù)的實例澎蛛。

var err = new Error('出錯了');
err.message // "出錯了"

上面代碼中抚垄,我們調用Error構造函數(shù),生成一個實例對象err谋逻。Error構造函數(shù)接受一個參數(shù)呆馁,表示錯誤提示,可以從實例的message屬性讀到這個參數(shù)斤贰。拋出Error實例對象以后智哀,整個程序就中斷在發(fā)生錯誤的地方,不再往下執(zhí)行荧恍。

JavaScript 語言標準只提到瓷叫,Error實例對象必須有message屬性,表示出錯時的提示信息送巡,沒有提到其他屬性摹菠。大多數(shù) JavaScript 引擎,對Error實例還提供namestack屬性骗爆,分別表示錯誤的名稱和錯誤的堆棧次氨,但它們是非標準的,不是每種實現(xiàn)都有摘投。

  • message:錯誤提示信息
  • name:錯誤名稱(非標準屬性)
  • stack:錯誤的堆棧(非標準屬性)

使用namemessage這兩個屬性煮寡,可以對發(fā)生什么錯誤有一個大概的了解虹蓄。

if (error.name) {
  console.log(error.name + ': ' + error.message);
}

stack屬性用來查看錯誤發(fā)生時的堆棧。

function throwit() {
  throw new Error('');
}

function catchit() {
  try {
    throwit();
  } catch(e) {
    console.log(e.stack); // print stack trace
  }
}

catchit()
// Error
//    at throwit (~/examples/throwcatch.js:9:11)
//    at catchit (~/examples/throwcatch.js:3:9)
//    at repl:1:5

上面代碼中幸撕,錯誤堆棧的最內層是throwit函數(shù)薇组,然后是catchit函數(shù),最后是函數(shù)的運行環(huán)境坐儿。

原生錯誤類型

Error實例對象是最一般的錯誤類型律胀,在它的基礎上,JavaScript 還定義了其他6種錯誤對象貌矿。也就是說炭菌,存在Error的6個派生對象。

SyntaxError 對象

SyntaxError對象是解析代碼時發(fā)生的語法錯誤逛漫。

// 變量名錯誤
var 1a;
// Uncaught SyntaxError: Invalid or unexpected token

// 缺少括號
console.log 'hello');
// Uncaught SyntaxError: Unexpected string

上面代碼的錯誤黑低,都是在語法解析階段就可以發(fā)現(xiàn),所以會拋出SyntaxError酌毡。第一個錯誤提示是“token 非法”投储,第二個錯誤提示是“字符串不符合要求”。

ReferenceError 對象

ReferenceError對象是引用一個不存在的變量時發(fā)生的錯誤阔馋。

// 使用一個不存在的變量
unknownVariable
// Uncaught ReferenceError: unknownVariable is not defined

另一種觸發(fā)場景是,將一個值分配給無法分配的對象娇掏,比如對函數(shù)的運行結果或者this賦值呕寝。

// 等號左側不是變量
console.log() = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment

// this 對象不能手動賦值
this = 1
// ReferenceError: Invalid left-hand side in assignment

上面代碼對函數(shù)console.log的運行結果和this賦值,結果都引發(fā)了ReferenceError錯誤婴梧。

RangeError 對象

RangeError對象是一個值超出有效范圍時發(fā)生的錯誤下梢。主要有幾種情況,一是數(shù)組長度為負數(shù)塞蹭,二是Number對象的方法參數(shù)超出范圍孽江,以及函數(shù)堆棧超過最大值。

// 數(shù)組長度不得為負數(shù)
new Array(-1)
// Uncaught RangeError: Invalid array length

TypeError 對象

TypeError對象是變量或參數(shù)不是預期類型時發(fā)生的錯誤番电。比如岗屏,對字符串、布爾值漱办、數(shù)值等原始類型的值使用new命令这刷,就會拋出這種錯誤,因為new命令的參數(shù)應該是一個構造函數(shù)娩井。

new 123
// Uncaught TypeError: number is not a func

var obj = {};
obj.unknownMethod()
// Uncaught TypeError: obj.unknownMethod is not a function

上面代碼的第二種情況暇屋,調用對象不存在的方法,也會拋出TypeError錯誤洞辣,因為obj.unknownMethod的值是undefined咐刨,而不是一個函數(shù)昙衅。

URIError 對象

URIError對象是 URI 相關函數(shù)的參數(shù)不正確時拋出的錯誤,主要涉及encodeURI()定鸟、decodeURI()而涉、encodeURIComponent()decodeURIComponent()仔粥、escape()unescape()這六個函數(shù)婴谱。

decodeURI('%2')
// URIError: URI malformed

EvalError 對象

eval函數(shù)沒有被正確執(zhí)行時,會拋出EvalError錯誤躯泰。該錯誤類型已經(jīng)不再使用了谭羔,只是為了保證與以前代碼兼容,才繼續(xù)保留麦向。

總結

以上這6種派生錯誤瘟裸,連同原始的Error對象,都是構造函數(shù)诵竭。開發(fā)者可以使用它們话告,手動生成錯誤對象的實例。這些構造函數(shù)都接受一個函數(shù)卵慰,代表錯誤提示信息(message)沙郭。

var err1 = new Error('出錯了!');
var err2 = new RangeError('出錯了裳朋,變量超出有效范圍病线!');
var err3 = new TypeError('出錯了,變量類型無效鲤嫡!');

err1.message // "出錯了送挑!"
err2.message // "出錯了,變量超出有效范圍暖眼!"
err3.message // "出錯了惕耕,變量類型無效!"

自定義錯誤

除了 JavaScript 原生提供的七種錯誤對象诫肠,還可以定義自己的錯誤對象司澎。

function UserError(message) {
  this.message = message || '默認信息';
  this.name = 'UserError';
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;

上面代碼自定義一個錯誤對象UserError,讓它繼承Error對象区赵。然后惭缰,就可以生成這種自定義類型的錯誤了。

new UserError('這是自定義的錯誤笼才!');

throw 語句

throw語句的作用是手動中斷程序執(zhí)行葛闷,拋出一個錯誤变勇。

if (x < 0) {
  throw new Error('x 必須為正數(shù)');
}
// Uncaught ReferenceError: x is not defined

上面代碼中觉既,如果變量x小于0,就手動拋出一個錯誤絮记,告訴用戶x的值不正確,整個程序就會在這里中斷執(zhí)行虐先≡狗撸可以看到,throw拋出的錯誤就是它的參數(shù)蛹批,這里是一個Error實例撰洗。

throw也可以拋出自定義錯誤。

function UserError(message) {
  this.message = message || '默認信息';
  this.name = 'UserError';
}

throw new UserError('出錯了腐芍!');
// Uncaught UserError {message: "出錯了差导!", name: "UserError"}

上面代碼中,throw拋出的是一個UserError實例猪勇。

實際上设褐,throw可以拋出任何類型的值。也就是說泣刹,它的參數(shù)可以是任何值助析。

// 拋出一個字符串
throw 'Error!';
// Uncaught Error椅您!

// 拋出一個數(shù)值
throw 42;
// Uncaught 42

// 拋出一個布爾值
throw true;
// Uncaught true

// 拋出一個對象
throw {
  toString: function () {
    return 'Error!';
  }
};
// Uncaught {toString: ?}

對于 JavaScript 引擎來說外冀,遇到throw語句,程序就中止了掀泳。引擎會接收到throw拋出的信息锥惋,可能是一個錯誤實例,也可能是其他類型的值开伏。

try…catch 結構

一旦發(fā)生錯誤,程序就中止執(zhí)行了遭商。JavaScript 提供了try...catch結構固灵,允許對錯誤進行處理,選擇是否往下執(zhí)行劫流。

try {
  throw new Error('出錯了!');
} catch (e) {
  console.log(e.name + ": " + e.message);
  console.log(e.stack);
}
// Error: 出錯了!
//   at <anonymous>:3:9
//   ...

上面代碼中巫玻,try代碼塊拋出錯誤(上例用的是throw語句),JavaScript 引擎就立即把代碼的執(zhí)行祠汇,轉到catch代碼塊仍秤,或者說錯誤被catch代碼塊捕獲了。catch接受一個參數(shù)可很,表示try代碼塊拋出的值诗力。

如果你不確定某些代碼是否會報錯,就可以把它們放在try...catch代碼塊之中我抠,便于進一步對錯誤進行處理苇本。

try {
  f();
} catch(e) {
  // 處理錯誤
}

上面代碼中袜茧,如果函數(shù)f執(zhí)行報錯,就會進行catch代碼塊瓣窄,接著對錯誤進行處理笛厦。

catch代碼塊捕獲錯誤之后,程序不會中斷俺夕,會按照正常流程繼續(xù)執(zhí)行下去裳凸。

try {
  throw "出錯了";
} catch (e) {
  console.log(111);
}
console.log(222);
// 111
// 222

上面代碼中,try代碼塊拋出的錯誤劝贸,被catch代碼塊捕獲后姨谷,程序會繼續(xù)向下執(zhí)行。

catch代碼塊之中悬荣,還可以再拋出錯誤菠秒,甚至使用嵌套的try...catch結構。

var n = 100;

try {
  throw n;
} catch (e) {
  if (e <= 50) {
    // ...
  } else {
    throw e;
  }
}
// Uncaught 100

上面代碼中氯迂,catch代碼之中又拋出了一個錯誤践叠。

為了捕捉不同類型的錯誤,catch代碼塊之中可以加入判斷語句嚼蚀。

try {
  foo.bar();
} catch (e) {
  if (e instanceof EvalError) {
    console.log(e.name + ": " + e.message);
  } else if (e instanceof RangeError) {
    console.log(e.name + ": " + e.message);
  }
  // ...
}

上面代碼中禁灼,catch捕獲錯誤之后,會判斷錯誤類型(EvalError還是RangeError)轿曙,進行不同的處理弄捕。

finally 代碼塊

try...catch結構允許在最后添加一個finally代碼塊,表示不管是否出現(xiàn)錯誤导帝,都必需在最后運行的語句守谓。

function cleansUp() {
  try {
    throw new Error('出錯了……');
    console.log('此行不會執(zhí)行');
  } finally {
    console.log('完成清理工作');
  }
}

cleansUp()
// 完成清理工作
// Error: 出錯了……

上面代碼中,由于沒有catch語句塊您单,所以錯誤沒有捕獲斋荞。執(zhí)行finally代碼塊以后,程序就中斷在錯誤拋出的地方虐秦。

function idle(x) {
  try {
    console.log(x);
    return 'result';
  } finally {
    console.log("FINALLY");
  }
}

idle('hello')
// hello
// FINALLY
// "result"

上面代碼說明平酿,try代碼塊沒有發(fā)生錯誤,而且里面還包括return語句悦陋,但是finally代碼塊依然會執(zhí)行蜈彼。注意,只有在其執(zhí)行完畢后俺驶,才會顯示return語句的值幸逆。

下面的例子說明,return語句的執(zhí)行是排在finally代碼之前,只是等finally代碼執(zhí)行完畢后才返回秉颗。

var count = 0;
function countUp() {
  try {
    return count;
  } finally {
    count++;
  }
}

countUp()
// 0
count
// 1

上面代碼說明痢毒,return語句的count的值,是在finally代碼塊運行之前就獲取了蚕甥。

下面是finally代碼塊用法的典型場景哪替。

openFile();

try {
  writeFile(Data);
} catch(e) {
  handleError(e);
} finally {
  closeFile();
}

上面代碼首先打開一個文件,然后在try代碼塊中寫入文件菇怀,如果沒有發(fā)生錯誤凭舶,則運行finally代碼塊關閉文件;一旦發(fā)生錯誤爱沟,則先使用catch代碼塊處理錯誤帅霜,再使用finally代碼塊關閉文件。

下面的例子充分反映了try...catch...finally這三者之間的執(zhí)行順序呼伸。

function f() {
  try {
    console.log(0);
    throw 'bug';
  } catch(e) {
    console.log(1);
    return true; // 這句原本會延遲到 finally 代碼塊結束再執(zhí)行
    console.log(2); // 不會運行
  } finally {
    console.log(3);
    return false; // 這句會覆蓋掉前面那句 return
    console.log(4); // 不會運行
  }

  console.log(5); // 不會運行
}

var result = f();
// 0
// 1
// 3

result
// false

上面代碼中身冀,catch代碼塊結束執(zhí)行之前,會先執(zhí)行finally代碼塊括享。

catch代碼塊之中搂根,觸發(fā)轉入finally代碼快的標志,不僅有return語句铃辖,還有throw語句剩愧。

function f() {
  try {
    throw '出錯了!';
  } catch(e) {
    console.log('捕捉到內部錯誤');
    throw e; // 這句原本會等到finally結束再執(zhí)行
  } finally {
    return false; // 直接返回
  }
}

try {
  f();
} catch(e) {
  // 此處不會執(zhí)行
  console.log('caught outer "bogus"');
}

//  捕捉到內部錯誤

上面代碼中娇斩,進入catch代碼塊之后仁卷,一遇到throw語句,就會去執(zhí)行finally代碼塊犬第,其中有return false語句锦积,因此就直接返回了,不再會回去執(zhí)行catch代碼塊剩下的部分了歉嗓。

參考連接

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末充包,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子遥椿,更是在濱河造成了極大的恐慌,老刑警劉巖淆储,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冠场,死亡現(xiàn)場離奇詭異,居然都是意外死亡本砰,警方通過查閱死者的電腦和手機碴裙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舔株,你說我怎么就攤上這事莺琳。” “怎么了载慈?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵惭等,是天一觀的道長。 經(jīng)常有香客問我办铡,道長辞做,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任寡具,我火速辦了婚禮秤茅,結果婚禮上,老公的妹妹穿的比我還像新娘童叠。我一直安慰自己框喳,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布厦坛。 她就那樣靜靜地躺著五垮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粪般。 梳的紋絲不亂的頭發(fā)上拼余,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音亩歹,去河邊找鬼匙监。 笑死,一個胖子當著我的面吹牛小作,可吹牛的內容都是我干的亭姥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼顾稀,長吁一口氣:“原來是場噩夢啊……” “哼达罗!你這毒婦竟也來了?” 一聲冷哼從身側響起静秆,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤粮揉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抚笔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扶认,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年殊橙,在試婚紗的時候發(fā)現(xiàn)自己被綠了辐宾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狱从。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叠纹,靈堂內的尸體忽然破棺而出季研,到底是詐尸還是另有隱情,我是刑警寧澤誉察,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布与涡,位于F島的核電站,受9級特大地震影響冒窍,放射性物質發(fā)生泄漏递沪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一综液、第九天 我趴在偏房一處隱蔽的房頂上張望款慨。 院中可真熱鬧,春花似錦谬莹、人聲如沸檩奠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埠戳。三九已至,卻和暖如春蕉扮,著一層夾襖步出監(jiān)牢的瞬間整胃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工喳钟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屁使,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓奔则,卻偏偏與公主長得像蛮寂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子易茬,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)酬蹋,也就是一...
    悟名先生閱讀 4,151評論 0 13
  • 如果大家覺得這篇文章為你提供了一些有用的信息, 請點個贊_希望我們在 iOS 開發(fā)的道路上越走越精彩_作者微博: ...
    銅鑼燒的夢想閱讀 1,365評論 1 8
  • 一、Error對象 二抽莱、JS的原生錯誤類型 Error對象是最一般的錯誤類型范抓,在它的基礎上,JS還定義了其他6種錯...
    周花花啊閱讀 236評論 0 1
  • 實例化個可變字典 通過URL是拼接的食铐,是一個宏和另個宏拼接而成的匕垫。 添加參數(shù) 提示用戶請求 打印dic 獲取接口請...
    必須這么打閱讀 87評論 0 0
  • 你說,不太在意這個節(jié)日璃岳,只在乎自己的生日年缎。 你說的話,我牢牢記在心里铃慷,仔細揣摩单芜,一遍又一遍。 我們的愛情犁柜,想來都感...
    文藝復興閱讀 559評論 2 5