Skip to content

JavaScript 错误

什么是错误

错误是在程序执行过程中发生的意外情况,它会导致程序无法正常运行。在 JavaScript 中,错误是一种对象,当发生错误时,JavaScript 引擎会抛出一个错误对象。

错误的类型

JavaScript 中有多种类型的错误,每种错误都有其特定的含义和触发条件。

1. SyntaxError(语法错误)

当 JavaScript 代码包含语法错误时,会抛出 SyntaxError。这是在代码解析阶段发生的错误,通常是由于代码书写不正确导致的。

javascript
// 示例:语法错误
console.log("Hello"); // 正确
console.log("Hello"); // 正确

// 以下代码会导致语法错误
// console.log('Hello'; // 缺少右括号
// if (true) // 缺少花括号
// var x = ; // 缺少赋值表达式

2. ReferenceError(引用错误)

当尝试访问一个不存在的变量或函数时,会抛出 ReferenceError。

javascript
// 示例:引用错误
console.log(x); // ReferenceError: x is not defined

function test() {
  console.log(y); // ReferenceError: y is not defined
}

test();

3. TypeError(类型错误)

当操作数的类型与操作不兼容时,会抛出 TypeError。

javascript
// 示例:类型错误
var x = 10;
x(); // TypeError: x is not a function

var y = null;
y.toString(); // TypeError: Cannot read property 'toString' of null

var z = undefined;
z.length; // TypeError: Cannot read property 'length' of undefined

4. RangeError(范围错误)

当数值超出有效范围时,会抛出 RangeError。

javascript
// 示例:范围错误
var arr = new Array(-1); // RangeError: Invalid array length

function factorial(n) {
  if (n < 0) {
    throw new RangeError("n must be non-negative");
  }
  // 计算阶乘的代码
}

factorial(-1); // RangeError: n must be non-negative

5. URIError(URI 错误)

当使用 URI 相关函数时,如果参数无效,会抛出 URIError。

javascript
// 示例:URI 错误
decodeURIComponent("%"); // URIError: URI malformed

encodeURIComponent("hello world"); // 正确: "hello%20world"

6. EvalError( eval 错误)

当 eval() 函数执行失败时,会抛出 EvalError。在现代 JavaScript 中,这个错误很少使用,通常会被其他类型的错误替代。

javascript
// 示例:eval 错误(现代浏览器中可能会抛出其他类型的错误)
try {
  eval("var x = ;"); // 语法错误
} catch (e) {
  console.log(e instanceof EvalError); // false
  console.log(e instanceof SyntaxError); // true
}

7. CustomError(自定义错误)

除了内置的错误类型,你还可以创建自定义错误类型。

javascript
// 示例:自定义错误
class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = "CustomError";
  }
}

function test() {
  throw new CustomError("This is a custom error");
}

try {
  test();
} catch (e) {
  console.log(e.name); // CustomError
  console.log(e.message); // This is a custom error
}

错误对象的属性

错误对象通常具有以下属性:

1. name

错误的名称,表示错误的类型。

javascript
try {
  console.log(x);
} catch (e) {
  console.log(e.name); // ReferenceError
}

2. message

错误的描述信息,提供了关于错误的详细信息。

javascript
try {
  console.log(x);
} catch (e) {
  console.log(e.message); // x is not defined
}

3. stack

错误的堆栈跟踪信息,显示了错误发生的位置和调用栈。

javascript
try {
  function a() {
    function b() {
      console.log(x);
    }
    b();
  }
  a();
} catch (e) {
  console.log(e.stack);
  // 输出类似:
  // ReferenceError: x is not defined
  //    at b (script.js:5:15)
  //    at a (script.js:7:5)
  //    at script.js:9:3
}

错误处理

JavaScript 提供了 try-catch-finally 语句来处理错误,使程序能够在发生错误时继续执行。

try-catch 语句

javascript
try {
  // 可能会抛出错误的代码
  console.log(x); // x 未定义,会抛出 ReferenceError
} catch (error) {
  // 处理错误的代码
  console.log("Error caught:", error.name, ":", error.message);
  // 可以在这里进行错误处理,例如记录错误、显示错误信息等
} finally {
  // 无论是否发生错误都会执行的代码
  console.log("Finally block executed");
}

// 程序会继续执行
console.log("Program continues...");

嵌套的 try-catch 语句

javascript
try {
  console.log("Outer try block");

  try {
    console.log("Inner try block");
    throw new Error("Inner error");
  } catch (innerError) {
    console.log("Inner catch block:", innerError.message);
    // 可以选择重新抛出错误
    throw new Error("Re-thrown error");
  } finally {
    console.log("Inner finally block");
  }
} catch (outerError) {
  console.log("Outer catch block:", outerError.message);
} finally {
  console.log("Outer finally block");
}

抛出错误

你可以使用 throw 语句主动抛出错误。

javascript
// 示例:抛出错误
function divide(a, b) {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}

try {
  var result = divide(10, 0);
  console.log("Result:", result);
} catch (error) {
  console.log("Error:", error.message);
}

// 示例:抛出自定义错误
class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

function validateEmail(email) {
  if (!email.includes("@")) {
    throw new ValidationError("Invalid email format");
  }
  return true;
}

try {
  validateEmail("invalid-email");
  console.log("Email is valid");
} catch (error) {
  console.log("Error:", error.name, ":", error.message);
}

错误处理的最佳实践

1. 只捕获你能处理的错误

不要捕获所有错误,只捕获你知道如何处理的错误。对于不知道如何处理的错误,应该让它继续传播。

javascript
// 推荐:只捕获特定类型的错误
try {
  // 可能会抛出 ValidationError 的代码
  validateEmail("invalid-email");
} catch (error) {
  if (error instanceof ValidationError) {
    // 处理验证错误
    console.log("Validation error:", error.message);
  } else {
    // 重新抛出其他类型的错误
    throw error;
  }
}

// 不推荐:捕获所有错误
try {
  // 代码
} catch (error) {
  // 处理所有错误
  console.log("An error occurred:", error.message);
}

2. 提供有意义的错误信息

当抛出错误时,提供清晰、具体的错误信息,以便于调试和理解。

javascript
// 推荐:提供具体的错误信息
function calculateArea(width, height) {
  if (width <= 0) {
    throw new Error("Width must be greater than 0");
  }
  if (height <= 0) {
    throw new Error("Height must be greater than 0");
  }
  return width * height;
}

// 不推荐:提供模糊的错误信息
function calculateArea(width, height) {
  if (width <= 0 || height <= 0) {
    throw new Error("Invalid parameters");
  }
  return width * height;
}

3. 记录错误

在生产环境中,应该记录错误,以便于监控和调试。

javascript
// 示例:记录错误
function logError(error) {
  // 在实际应用中,可能会发送错误到服务器或使用专门的日志系统
  console.error("Error:", error.name, ":", error.message);
  console.error("Stack:", error.stack);
}

try {
  // 可能会抛出错误的代码
  validateEmail("invalid-email");
} catch (error) {
  // 记录错误
  logError(error);
  // 处理错误
  console.log("Please enter a valid email address");
}

4. 优雅降级

当发生错误时,提供一个合理的回退方案,而不是让程序完全失败。

javascript
// 示例:优雅降级
function fetchData(url) {
  try {
    // 尝试从服务器获取数据
    return fetch(url).then((response) => response.json());
  } catch (error) {
    // 发生错误时,返回默认数据
    console.log("Error fetching data, using default data");
    return Promise.resolve({ data: "Default data" });
  }
}

// 使用函数
fetchData("https://api.example.com/data").then((data) => {
  console.log("Data:", data);
});

5. 避免使用 try-catch 处理可预见的情况

对于可预见的情况,应该使用条件判断来处理,而不是依赖 try-catch。

javascript
// 推荐:使用条件判断
function getUserName(user) {
  if (user && user.name) {
    return user.name;
  }
  return "Unknown";
}

// 不推荐:使用 try-catch
function getUserName(user) {
  try {
    return user.name;
  } catch (error) {
    return "Unknown";
  }
}

6. 使用 finally 清理资源

当使用需要清理的资源(如文件、网络连接等)时,使用 finally 块来确保资源被正确清理。

javascript
// 示例:使用 finally 清理资源
function processFile() {
  let file = null;

  try {
    // 打开文件
    file = openFile("data.txt");
    // 处理文件
    console.log("Processing file");
    // 模拟错误
    throw new Error("Error processing file");
  } catch (error) {
    console.log("Error:", error.message);
  } finally {
    // 无论是否发生错误,都会关闭文件
    if (file) {
      console.log("Closing file");
      closeFile(file);
    }
  }
}

// 模拟文件操作函数
function openFile(filename) {
  console.log("Opening file:", filename);
  return { name: filename };
}

function closeFile(file) {
  console.log("Closing file:", file.name);
}

// 调用函数
processFile();

错误处理的高级技巧

1. 使用错误边界

在 React 等框架中,可以使用错误边界来捕获组件树中的错误,防止整个应用崩溃。

2. 使用 Promise 的错误处理

对于异步操作,可以使用 Promise 的 catch 方法来处理错误。

javascript
// 示例:Promise 错误处理
function fetchData(url) {
  return fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return response.json();
    })
    .catch((error) => {
      console.log("Error fetching data:", error);
      // 可以选择返回默认值或重新抛出错误
      return { data: "Default data" };
    });
}

// 使用函数
fetchData("https://api.example.com/data").then((data) => {
  console.log("Data:", data);
});

3. 使用 async/await 的错误处理

对于使用 async/await 的异步函数,可以使用 try-catch 来处理错误。

javascript
// 示例:async/await 错误处理
async function fetchData(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.log("Error fetching data:", error);
    return { data: "Default data" };
  }
}

// 使用函数
async function main() {
  const data = await fetchData("https://api.example.com/data");
  console.log("Data:", data);
}

main();

4. 使用全局错误处理器

可以使用 window.onerror 或 window.addEventListener('error') 来捕获全局错误。

javascript
// 示例:全局错误处理器
window.onerror = function (message, source, lineno, colno, error) {
  console.log("Global error caught:", message);
  console.log("Source:", source);
  console.log("Line:", lineno);
  console.log("Column:", colno);
  console.log("Error object:", error);

  // 返回 true 表示错误已处理,不会在控制台显示
  return false;
};

// 示例:全局未捕获的 Promise 错误处理器
window.addEventListener("unhandledrejection", function (event) {
  console.log("Unhandled Promise rejection:", event.reason);
  event.preventDefault(); // 防止默认处理
});

// 测试全局错误处理器
console.log(x); // 会触发全局错误处理器

// 测试未捕获的 Promise 错误
new Promise((resolve, reject) => {
  reject(new Error("Promise rejected"));
}); // 未使用 catch,会触发 unhandledrejection 事件

常见错误及其解决方案

1. 引用未定义的变量

错误:ReferenceError: x is not defined

解决方案:在使用变量前确保它已经定义。

javascript
// 错误
console.log(x);

// 正确
var x = 10;
console.log(x);

2. 访问 null 或 undefined 的属性

错误:TypeError: Cannot read property 'x' of null/undefined

解决方案:在访问属性前检查对象是否存在。

javascript
// 错误
var user = null;
console.log(user.name);

// 正确
var user = null;
console.log(user && user.name); // 使用短路求值

// 或使用可选链操作符(ES2020+)
// console.log(user?.name);

3. 调用非函数

错误:TypeError: x is not a function

解决方案:确保你调用的是一个函数。

javascript
// 错误
var x = 10;
x();

// 正确
function x() {
  console.log("Hello");
}
x();

4. 除以零

错误:在 JavaScript 中,除以零不会抛出错误,而是返回 Infinity。

解决方案:在除法操作前检查除数是否为零。

javascript
// 不会抛出错误,但会返回 Infinity
var result = 10 / 0;
console.log(result); // Infinity

// 正确的做法
function divide(a, b) {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}

try {
  var result = divide(10, 0);
  console.log("Result:", result);
} catch (error) {
  console.log("Error:", error.message);
}

5. 无效的 JSON

错误:SyntaxError: Unexpected token x in JSON at position y

解决方案:确保 JSON 字符串格式正确。

javascript
// 错误
var jsonStr = '{ "name": "John", "age": 30, }'; // 末尾有多余的逗号
var obj = JSON.parse(jsonStr); // 会抛出 SyntaxError

// 正确
var jsonStr = '{ "name": "John", "age": 30 }';
try {
  var obj = JSON.parse(jsonStr);
  console.log("Parsed object:", obj);
} catch (error) {
  console.log("Error parsing JSON:", error.message);
}

6. 跨域请求错误

错误:Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy

解决方案:确保服务器设置了正确的 CORS 头,或者使用代理服务器。

总结

错误是编程中不可避免的一部分,了解 JavaScript 中的错误类型和处理方法是编写健壮代码的关键。通过合理使用 try-catch 语句、主动抛出错误、记录错误以及采取适当的错误处理策略,你可以编写更加可靠和用户友好的 JavaScript 代码。

记住,良好的错误处理不仅可以防止程序崩溃,还可以提供有意义的错误信息,帮助开发者和用户理解发生了什么问题以及如何解决它。