關注[前端小謳],原創技術文章
錯誤處理
相關代碼 →
try/catch 語句
- ES3 新增了try/catch語句,基本語法與 Java 中的 try/catch 一樣
try {// 可能出錯的代碼const a = 3;a = 4;
} catch (error) {// 出錯時執行的代碼console.log("An error happened!"); // An error happened!
}
- try 塊中有代碼發生錯誤,代碼會立即退出執行并跳到 catch 塊中
- 所有瀏覽器都支持錯誤對象的message和name屬性
try {const a = 3;a = 4;
} catch (error) {console.log(error);/* TypeError: Assignment to constant variable.at Object.<anonymous> (c:\Users\43577\Desktop\工作\my_project\my_demos\javascript高級程序設計(第四版)\第21章 錯誤處理與調試\21.2.錯誤處理.js:13:5)at Module._compile (internal/modules/cjs/loader.js:1085:14)at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)at Module.load (internal/modules/cjs/loader.js:950:32)at Function.Module._load (internal/modules/cjs/loader.js:790:12)at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)at internal/main/run_main_module.js:17:47*/console.log(error.name); // TypeError(類型錯誤)console.log(error.message); // Assignment to constant variable.(常量被賦值)
}
finally 子句
- try/catch 中可選的 finally 子句始終運行,二者均無法阻止finally 塊執行
- try 中代碼運行完,會執行 finally 的代碼
- 出錯并執行 catch 中代碼,仍會執行 finally 的代碼
// finally 子句
try {console.log(1); // 1,執行
} catch (error) {console.log(2); // 不執行
} finally {console.log(3); // 3,執行
}try {const a = 3;a = 4;
} catch (error) {console.log(2); // 2,try出錯執行catch
} finally {console.log(3); // 3,仍執行
}
- 代碼中包含 finally, try 或 catch 中的 return 會被忽略
console.log((function testFinally() {try {console.log("try"); // try,非return語句不受影響return 1;} catch (error) {return 2;} finally {console.log("finally"); // finallyreturn 3;}})()
); // 3,包含finally語句,try或catch中的return會被忽略
錯誤類型
- Error,基類型
- InternalError,底層引擎異常時,如遞歸過多導致的棧溢出
- EvalError,使用 eval()異常時,但瀏覽器不會總拋出 EvalError
- RangeError,數值越界時
- ReferenceError,找不到對象時
- SyntaxError,給 eval()傳入的字符串包含語法錯誤時
- TypeError,最常見
- 變量不是預期類型時
- 訪問不存在的方法時
new Array(-1); // RangeError: Invalid array length
let obj = x; // ReferenceError: x is not defined
eval("1++2"); // SyntaxError: Invalid left-hand side expression in postfix operation
console.log("a" in "abc"); // TypeError: Cannot use 'in' operator to search for 'a' in abc
Function.prototype.toString().call("name"); // TypeError: Function.prototype.toString(...).call is not a function
- 可以使用instanceof操作符在 catch 塊中確定錯誤類型
try {const a = 3;a = 4;
} catch (error) {if (error instanceof TypeError) {console.log("TypeError!");}
} // TypeError!
try {new Array(-1);
} catch (error) {if (error instanceof RangeError) {console.log("RangeError!");}
} // RangeError!
try/catch 的用法
- 瀏覽器認為 try/catch 中發生的錯誤已被處理,不會再報錯
- try/catch 最好使用在開發者無法控制但有可能出現錯誤上(如不便修改代碼的第三方 js 庫,最好使用 try/catch 把函數調用包起來)
拋出錯誤
-
throw 操作符可在任何時候拋出自定義錯誤
- throw 操作符必須有值,類型不限
// throw 12345; // Uncaught 12345,后續代碼停止 // throw "Hello world"; // Uncaught Hello world,后續代碼停止 // throw true; // Uncaught true,后續代碼停止 // throw { name: "JS" }; // Uncaught {name: 'JS'},后續代碼停止
- 使用 throw 時代碼立即停止,try/catch 語句中捕獲了拋出的值時除外
try {throw 123; } catch (error) {console.log(123); } // 123 console.log(5); // 5,throw被try/catch捕獲,后續代碼照常
-
可通過內置錯誤類型模擬瀏覽器錯誤
// throw new SyntaxError; // Uncaught SyntaxError
// throw new InternalError; // Uncaught InternalError
// throw new TypeError; // Uncaught TypeError
// throw new RangeError; // Uncaught RangeError
// throw new EvalError; // Uncaught EvalError
// throw new URIError; // Uncaught URIError
// throw new RefenceError; // Uncaught RefenceError
- 可通過繼承 Error創建自定義錯誤類型,創建時需提供 name 和 message 屬性
class CustomError extends Error {constructor(message) {super(message); // super調用父類構造函數,手動給父類傳參,并將返回值賦給子類中的thisthis.name = "CustomError";this.message = message;}
}
// throw new CustomError("My message"); // CustomError: My message
何時拋出錯誤
- 已知函數無法正確執行時,瀏覽器會自動拋出錯誤
- 復雜的程序很難找到錯誤原因,適當創建自定義錯誤可有效提高代碼的可維護性
- 應仔細評估每個函數,尤其可能導致失敗的情形
function process(values) {if (!(values instanceof Array)) {throw new Error("process(): Argument must be an Array.");}values.sort(); // 如果values不是數組,則瀏覽器會報錯。因此在此句之前判斷參數類型且用自定義錯誤,可有效提高代碼可維護性for (let value of values) {if (value > 100) {return value;}}return -1;
}
// process(1); // Error: process(): Argument must be an Array.
// process(1); // TypeError: values.sort is not a function(如果沒有throw代碼段的結果)
拋出錯誤與 try/catch
- 捕獲錯誤的目的是阻止瀏覽器以其默認方式響應
- 拋出錯誤的目的是提供有關其發生原因的說明
- 應該在明確接下來做什么時捕獲錯誤
error 事件
- 沒有被 try/catch 捕獲的錯誤會在瀏覽器 window 對象上觸發 error 事件
- onerror 事件處理程序中,任何瀏覽器都不傳入 event 對象
- 傳入 3 個參數:錯誤消息、發生錯誤的 URL、發生錯誤的行號
- 任何錯誤發生都會觸發 error 事件,并執行事件的處理程序,瀏覽器默認行為會生效
- 可以返回 false 來阻止瀏覽器默認報告錯誤的行為
window.onerror = (message, url, line) => {console.log(message);return false; // 阻止瀏覽器默認報告錯誤
};
- 圖片中 src 屬性的 url 沒有返回可識別的圖片格式,也會觸發 error 事件
const image = new Image();
image.addEventListener("load", (event) => {console.log("Image loaded!");
});
image.addEventListener("error", (event) => {console.log("Image not loaded!");
});
image.src = "a.jpg"; // Image not loaded!
識別錯誤
類型轉換錯誤
-
主要原因是使用了會自動改變某個值的數據類型的草錯付或語言構造
- 在比較過程中,應使用嚴格相等和嚴格不等避免錯誤
console.log(5 == "5"); // true console.log(5 === "5"); // false,數據類型不同 console.log(1 == true); // true console.log(1 === true); // false,數據類型不同
- 在 if、for、while 等流程控制語句中,應堅持使用布爾值作為條件避免錯誤
function concat(str1, str2, str3) {let result = str1 + str2;if (str3) {result += str3;}return result; } console.log(concat("1", "2", "0")); // '120' console.log(concat("1", "2")); // '12',str3是undifined,轉化為false console.log(concat("1", "2", 0)); // '12',str3是數值0,轉化為false,與預期不符function concat(str1, str2, str3) {let result = str1 + str2;if (str3 !== undefined) {result += str3;}return result; } console.log(concat("1", "2", "03")); // '120' console.log(concat("1", "2")); // '12',str3 是 undifined,轉化為 false console.log(concat("1", "2", 0)); // '120',達到預期
數據類型錯誤
-
JS 是松散類型,其變量和函數參數都不能保證數據類型
- 原始類型的值,使用typeof檢測
function getQueryString(url) {const pos = url.indexOf("?"); // indexOf是字符串才有的方法if (pos > 1) {console.log(url.substring(pos + 1)); // substring是字符串才有的方法return;}console.log("not has ?"); } // getQueryString(123); // TypeError: url.indexOf is not a functionfunction getQueryString2(url) {if (typeof url === "string") {// 確保不會因為參數是非字符串值而報錯const pos = url.indexOf("?");if (pos > 1) {console.log(url.substring(pos + 1));return;}console.log("not has ?");} } getQueryString2(123); // 不打印 getQueryString2("123"); // 'not has ?' getQueryString2("https://www.baidu.com?keyWord=error"); // 'keyWord=error'
- 對象值,使用instanceof檢測
function reverseSort(values) {if (values) {// 不可取,values為true的情況很多values.sort();values.reverse();} } // reverseSort(1); // TypeError: values.sort is not a functionfunction reverseSort2(values) {if (values !== null) {// 不可取,values不為null的情況很多values.sort();values.reverse();} } // reverseSort2(1); // TypeError: values.sort is not a functionfunction reverseSort3(values) {if (typeof values.sort === "function") {// 不可取,假如values有sort()方法但不是數組則會報錯values.sort();values.reverse();} } // reverseSort3({ // sort: () => { // console.log("3"); // }, // }); // 先values.sort()打印3,后報錯TypeError: values.reverse is not a functionfunction reverseSort4(values) {if (values instanceof Array) {// 可取,確保values是Array的實例values.sort();values.reverse();} } let val1 = 1; let val2 = [1, 2]; reverseSort4(val1); reverseSort4(val2); console.log(val1); // 1 console.log(val2); // [2,1]
通信錯誤
- 對于 url 的查詢字符串,都要通過encodeURIComponent(),以確保編碼合適
let url = "https://www.baidu.com?keyWord=https://www.taobao.com"; // url格式不正確
function addQueryStringArg(url, name, value) {if (url.indexOf("?") === -1) {url += "?";} else {url += "&";}url += `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;return url;
}
let url2 = addQueryStringArg("https://www.baidu.com","keyWord","https://www.taobao.com"
);
console.log(url2); // https://www.baidu.com?keyWord=https%3A%2F%2Fwww.taobao.com,與服務器通信的正確url格式
區分重大與非重大錯誤
- 非重大錯誤
- 不影響用戶主要任務
- 只影響頁面中某個部分
- 可恢復
- 重復操作可能成功
- 重大錯誤
- 程序無法繼續運行
- 嚴重影響用戶的主要目標
- 會導致其他錯誤
- 程序某個部分的錯誤,不應該影響其他部分
- 模塊初始化時,可在 for 循環中加入 try/catch 語句,避免某一模塊初始化時發生錯誤影響其他模塊
let mods = [{name: "mod1",init: () => {const a = 1;a = 2;console.log("mod1 init");}, // mod1的init方法里有錯誤},{name: "mod2",init: () => {console.log("mod2 init");},},
];
for (let mod of mods) {// mod.init(); // 不好,只要有一個mod的init方法出錯,影響后續try {mod.init(); // 'mod2 init',mod2照常運行} catch (error) {console.log(error); // TypeError: Assignment to constant variable.}
}
總結 & 問點
- 錯誤對象中的哪些屬性在全部瀏覽器中都向用戶顯示?
- finally 對 try 或 catch 中的非 return 語句和 return 語句分別有什么影響?
- 請舉例說明有哪些常見錯誤類型及其出現的原因
- 請寫一段代碼,在 try/catch 塊中,確定錯誤的類型
- throw 操作符必須有值嘛?需要什么數據類型的值?如何才能既使用該操作符又不影響后續代碼執行?
- 寫一段代碼,通過繼承 Error 創建一個自定義的錯誤類型,創建其實例并用 throw 將其拋出
- 寫一段代碼,在一個函數里,通過創建自定義錯誤類型提高其可維護性
- 常見的類型轉換錯誤有哪些?分別該如何避免呢?
- 應分別怎樣檢測,以避免原始值和對象值在函數傳參時可能發生的數據類型錯誤?
- 寫一個方法,處理與服務器通信的 url 的正確格式
- 寫一段代碼,用 for 循環模擬模塊初始化,某一模塊加載時發生錯誤但不影響后續模塊