面试中被问到过的前端八股

1、React的事件和普通事件有什么不同

✅ 1. 事件绑定方式不同

React 中使用的是 驼峰命名,并且直接传入函数引用:

 1<button onClick={handleClick}>点我</button>

原生 DOM 则是小写并用字符串或函数赋值:

 1<button onclick="handleClick()">点我</button>

或者在 JS 中绑定:

 1element.addEventListener('click', handleClick)

✅ 2. React 使用的是合成事件(SyntheticEvent)

React 实现了一套自己的事件系统:SyntheticEvent 合成事件

  • 它对原生事件进行了封装,兼容性更强。
  • 所有事件都会被委托到最外层(document),提升性能(事件委托机制)。
  • e 是一个合成事件对象,不是原生事件,但可以通过 e.nativeEvent 访问原生事件。
 1function handleClick(e) {
 2  console.log(e); 
 3  console.log(e.nativeEvent); 
 4}

✅ 3. 阻止默认行为和冒泡方式不同

React 的合成事件里不能直接使用 return false,而是要使用专门方法:

 1function handleClick(e) {
 2  e.preventDefault(); 
 3  e.stopPropagation(); 
 4}

✅ 4. 事件解绑方式不同

React 中不推荐在组件内部动态绑定/解绑事件,因为 React 会在重新渲染时自动处理。

如果真要控制事件生命周期,推荐使用 useEffect 中手动绑定原生事件:

 1useEffect(() => {
 2  const handler = () => console.log('clicked');
 3  window.addEventListener('click', handler);
 4  return () => window.removeEventListener('click', handler);
 5}, []);

2、React的事件委托机制

🧠 什么是事件委托?

事件委托的意思是:不在每个子元素上绑定事件,而是把事件绑定到父元素,利用事件冒泡机制来处理子元素的事件。

🧩 React 是怎么做事件委托的?

React 并不是在每一个 DOM 节点上都添加事件监听器,而是:

把所有事件都绑定在根节点(通常是 documentroot 容器)上。 然后:

  1. 当事件发生时,比如你点击了一个按钮;

  2. 浏览器的事件冒泡机制会把事件从按钮冒泡到 document

  3. React 统一在顶层监听到这个事件后,会:

    • 根据事件目标(event.target)和虚拟 DOM 中注册的事件,
    • 找到对应的组件方法,调用你写的事件处理函数。

🧪 举个例子
 1function App() {
 2  function handleClick() {
 3    console.log('按钮被点击了!');
 4  }
 5
 6  return <button onClick={handleClick}>点我</button>;
 7}

你写了一个 onClick,React 并不是在这个 button 上真的加了一个 click 事件,而是:

  • 把事件注册到了 document 上;
  • 通过事件冒泡捕获到点击;
  • 再根据虚拟 DOM 的结构找到这个按钮的 onClick 函数并执行。

🌟 为什么这么做?

好处是:

  • ✅ 减少真实 DOM 上的事件绑定数量;
  • ✅ 更容易做统一管理(比如事件池、清理、合成事件等);
  • ✅ 提升性能,尤其是组件多、结构复杂的情况下。

🧬 React 的合成事件(SyntheticEvent)

React 实际上不是直接处理原生事件,而是封装了一层叫做 合成事件(SyntheticEvent) 的机制:

  • 它是对原生事件的包装;
  • 提供跨浏览器一致的事件属性和行为;
  • 同时还能复用事件对象(提升性能);
  • 最终仍然会在事件执行结束后调用原生事件。

🧯 注意点

  • React 的事件是 冒泡阶段触发 的;
  • 如果你使用了 stopPropagation(),事件就不会继续冒泡;
  • 如果你使用 nativeEvent,那是原生事件对象(需要小心使用);

3、React中如何避免不必要的render

🧠 1. 使用 React.memo(函数组件)

函数组件来说,React.memo 是最常用的优化方式。

 1const MyComponent = React.memo((props) => {
 2  console.log("render");
 3  return <div>{props.name}</div>;
 4});
  • 默认情况下,React.memo 会浅比较 props,如果 props 没变化,就跳过渲染。
  • 如果你需要自定义比较逻辑,可以传第二个参数:
 1React.memo(Component, (prevProps, nextProps) => {
 2  return prevProps.id === nextProps.id; // 返回 true 表示“跳过更新”
 3});

🧠 2. 使用 useMemo 缓存计算结果

当你有复杂计算或生成的变量,用 useMemo 可以避免每次 render 都重新计算:

 1const expensiveValue = useMemo(() => {
 2  return computeExpensiveValue(a, b)
 3}, [a, b])
  • 只有当依赖项变了才会重新计算。

🧠 3. 使用 useCallback 缓存函数引用

传函数作为 prop 时,如果每次都传一个新函数,会让子组件以为 props 变了导致重渲。

 1const handleClick = useCallback(() => {
 2  doSomething()
 3}, [dependency])

搭配 React.memo 使用效果最佳。

🧠 4. 使用 shouldComponentUpdate(类组件)

对于类组件,可以手动控制组件是否需要更新。

 1shouldComponentUpdate(nextProps, nextState) {
 2  return nextProps.value !== this.props.value;
 3}

也可以继承 React.PureComponent,它会帮你做浅比较。

🧠 5. 避免匿名函数和内联对象作为 props

 1// ❌ 这种每次 render 都会生成新的函数和对象
 2<MyComponent onClick={() => {}} style={{ color: "red" }} />
 3
 4// ✅ 用 useCallback 和 useMemo
 5const onClick = useCallback(() => {}, [])
 6const style = useMemo(() => ({ color: "red" }), [])
 7<MyComponent onClick={onClick} style={style} />

🧠 6. 拆分组件,让局部更新不影响整体

把组件拆小一点,避免父组件更新时,所有子组件都被迫重渲染。

🧠 7. 避免不必要的 setState

例如:

 1if (value !== newValue) {
 2  setValue(newValue);
 3}

✅ 总结:避免不必要 render 的常见方式

方法使用场景
React.memo缓存组件,props 没变就不渲染
useMemo缓存计算结果(如 filter/map/sort)
useCallback缓存函数引用,避免子组件误判
PureComponent / shouldComponentUpdate类组件控制更新
拆组件局部更新更高效
精准控制 setState减少无意义的状态更新

4、Webpack的打包过程

Webpack 的打包过程就像一个流水线,把你的项目代码“加工处理”成浏览器能直接运行的格式。我们可以从宏观上分 5 个阶段来理解整个打包流程:

1. 初始化阶段(Initialization)

  • 读取配置文件(webpack.config.js
  • 合并默认配置和用户配置
  • 初始化编译器(Compiler)对象
  • 注册插件(调用 plugins.apply()
 1module.exports = {
 2  entry: './src/index.js',
 3  output: { filename: 'bundle.js' },
 4  plugins: [new MyPlugin()]
 5}

2. 构建依赖图(Build)

  • entry 开始,调用对应的 Loader 处理模块
  • 解析依赖:比如 import, require()
  • 把依赖的模块也加入依赖图
  • 递归处理所有依赖模块

最终会生成一张完整的依赖关系图(Dependency Graph)


3. 模块转换(Transform)

  • 所有模块交给 Loader 处理(如 Babel 转换 ES6)
  • 每个模块变成 Webpack 能识别的格式(CommonJS)
 1module: {
 2  rules: [
 3    { test: /.js$/, use: 'babel-loader' },
 4    { test: /.css$/, use: ['style-loader', 'css-loader'] }
 5  ]
 6}

4. 生成 Chunk(代码块)

  • 按照依赖图,把模块组合成不同的 Chunk

    • 主 Chunk(入口)+ 异步 Chunk(懒加载)
  • 插件可以介入修改 Chunk 内容


5. 输出阶段(Emit)

  • Webpack 根据 output 设置生成最终文件

    • 比如 bundle.jsindex.html、图片等
  • 插件如 HtmlWebpackPlugin 会生成 HTML 文件并注入打包好的 JS

  • 写入硬盘或内存中(如 webpack-dev-server

 1output: {
 2  filename: '[name].[contenthash].js',
 3  path: path.resolve(__dirname, 'dist')
 4}

5、常见的状态码

🟢 1xx 信息响应类

状态码含义
100Continue,请继续请求
101Switching Protocols,协议切换

🟩 2xx 成功响应类

状态码含义
200OK,请求成功
201Created,资源创建成功(常见于 POST)
204No Content,请求成功但无返回内容(常见于 DELETE)

🟨 3xx 重定向响应类

状态码含义
301永久重定向(资源永久移动)
302临时重定向
304Not Modified,资源未修改(用于缓存)

🟥 4xx 客户端错误类

状态码含义
400Bad Request,请求语法错误
401Unauthorized,未认证(需要登录)
403Forbidden,已认证但没权限
404Not Found,资源不存在
405Method Not Allowed,不支持的请求方法

🟥 5xx 服务器错误类

状态码含义
500Internal Server Error,服务器内部出错
502Bad Gateway,网关错误
503Service Unavailable,服务器当前无法处理请求(比如宕机、限流)
504Gateway Timeout,网关超时
个人笔记记录 2021 ~ 2025