关注前端小讴,阅读更多原创技术文章

错误处理

相关代码 →

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 块中
  • 所有浏览器都支持错误对象的messagename属性
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"); // finally
      return 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调用父类构造函数,手动给父类传参,并将返回值赋给子类中的this
    this.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 function
    
    function 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 function
    
    function reverseSort2(values) {
      if (values !== null) {
        // 不可取,values不为null的情况很多
        values.sort();
        values.reverse();
      }
    }
    // reverseSort2(1); // TypeError: values.sort is not a function
    
    function 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 function
    
    function 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 循环模拟模块初始化,某一模块加载时发生错误但不影响后续模块

小讴
217 声望16 粉丝