前置知识: JavaScript

错误边界与全局错误捕获

00:00
2 min Advanced 2026/6/14

JavaScript错误边界与全局错误捕获:ErrorBoundary、window.onerror、unhandledrejection。

1. JavaScript 错误类型

1.1 错误分类

错误类型触发时机捕获
语法错误解析否(无法运
运行时错误执行
异步错误setTimeout/Promise特殊处理
资源加载错误img/script 加载失败特殊处理

1.2 错误对象

try {
  undefinedFunction();
} catch (error) {
  console.log(error instanceof Error); // true
  console.log(error instanceof TypeError); // true
  console.log(error.message); // "undefinedFunction is not defined"
  console.log(error.stack); // 完整调用栈
}

2. 全局错误捕获

2.1 window.onerror

window.onerror = (message, source, lineno, colno, error) => {
  console.log('全局错误:', { message, source, lineno, colno, error });

  // 上报错误
  reportError({
    type: 'runtime',
    message,
    source,
    line: lineno,
    column: colno,
    stack: error?.stack,
  });

  return true; // 阻止默认错误处理(控制台输出)
};

2.2 window.addEventListener(‘error’)

window.addEventListener(
  'error',
  (event) => {
    // 捕获运行时错误和资源加载错误
    if (event.target instanceof HTMLElement) {
      // 资源加载错误
      console.log('资源加载失败:', event.target.src || event.target.href);
    } else {
      // 运行时错误
      console.log('运行时错误:', event.message);
    }
  },
  true
); // 使用捕获阶段

2.3 unhandledrejection

window.addEventListener('unhandledrejection', (event) => {
  console.log('未处理的 Promise 拒绝:', event.reason);

  reportError({
    type: 'unhandledrejection',
    message: event.reason?.message || String(event.reason),
    stack: event.reason?.stack,
  });

  event.preventDefault(); // 阻止控制台警告
});

2.4 完整的全局错误监控

class ErrorMonitor {
  constructor() {
    this.errors = [];
    this.init();
  }

  init() {
    // 运行时错误
    window.addEventListener(
      'error',
      (event) => {
        if (event.target && event.target !== window) {
          this.track({
            type: 'resource',
            message: `Failed to load: ${event.target.src || event.target.href}`,
            tagName: event.target.tagName,
          });
        } else {
          this.track({
            type: 'runtime',
            message: event.message,
            filename: event.filename,
            line: event.lineno,
            column: event.colno,
            stack: event.error?.stack,
          });
        }
      },
      true
    );

    // Promise 拒绝
    window.addEventListener('unhandledrejection', (event) => {
      this.track({
        type: 'unhandledrejection',
        message: event.reason?.message || String(event.reason),
        stack: event.reason?.stack,
      });
      event.preventDefault();
    });
  }

  track(error) {
    this.errors.push({
      ...error,
      timestamp: Date.now(),
      url: location.href,
      userAgent: navigator.userAgent,
    });
    this.report(error);
  }

  report(error) {
    // 发送到错误收集服务
    navigator.sendBeacon('/api/errors', JSON.stringify(error));
  }
}

3. React Error Boundary

3.1 类组件实现

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('ErrorBoundary caught:', error, errorInfo);
    reportError({
      type: 'react',
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback || (
          <div role="alert">
            <h2>出错了</h2>
            <p>{this.state.error.message}</p>
            <button onClick={() => this.setState({ hasError: false })}>重试</button>
          </div>
        )
      );
    }
    return this.props.children;
  }
}

// 使用
<ErrorBoundary fallback={<ErrorPage />}>
  <App />
</ErrorBoundary>;

3.2 函数式 Error Boundary(react-error-boundary)

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>出错了: {error.message}</p>
      <button onClick={resetErrorBoundary}>重试</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        /* 重置状态 */
      }}
      onError={(error, info) => reportError(error, info)}
    >
      <Dashboard />
    </ErrorBoundary>
  );
}

3.3 细粒度错误边界

function Dashboard() {
  return (
    <div>
      <ErrorBoundary FallbackComponent={WidgetError}>
        <ChartWidget />
      </ErrorBoundary>
      <ErrorBoundary FallbackComponent={WidgetError}>
        <TableWidget />
      </ErrorBoundary>
      <ErrorBoundary FallbackComponent={WidgetError}>
        <StatsWidget />
      </ErrorBoundary>
    </div>
  );
}
// 一个 Widget 崩溃不影响其他 Widget

4. Vue 错误处理

4.1 全局错误处理器

app.config.errorHandler = (err, instance, info) => {
  console.error('Vue Error:', err);
  reportError({
    type: 'vue',
    message: err.message,
    stack: err.stack,
    component: instance?.$options?.name,
    info,
  });
};

4.2 errorCaptured 钩子

export default {
  name: 'ErrorBoundary',
  data: () => ({ error: null }),
  errorCaptured(err, vm, info) {
    this.error = err;
    reportError({ type: 'vue-captured', message: err.message, info });
    return false; // 阻止错误继续向上传播
  },
  render() {
    return this.error ? this.$slots.fallback?.() || h('div', '出错了') : this.$slots.default?.();
  },
};

5. 错误上报最佳实践

5.1 错误聚合

function aggregateErrors(errors) {
  const groups = new Map();
  errors.forEach((error) => {
    const key = `${error.type}:${error.message}:${error.source}:${error.line}`;
    const group = groups.get(key) || { ...error, count: 0, firstSeen: error.timestamp };
    group.count++;
    group.lastSeen = error.timestamp;
    groups.set(key, group);
  });
  return Array.from(groups.values());
}

5.2 Source Map 还原

// 使用 source-map 库还原压缩后的错误位置
import { SourceMapConsumer } from 'source-map';

async function getOriginalPosition(sourceMap, line, column) {
  const consumer = await new SourceMapConsumer(sourceMap);
  const pos = consumer.originalPositionFor({ line, column });
  consumer.destroy();
  return pos;
}

5.3 采样策略

function shouldReport(error) {
  // 1% 采样率
  if (Math.random() > 0.01) return false;

  // 忽略已知无害错误
  const ignoredMessages = ['ResizeObserver loop', 'Script error'];
  if (ignoredMessages.some((msg) => error.message?.includes(msg))) return false;

  return true;
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式