Skip to content

实际项目开发(待整理) ​

更新: 12/19/2025字数: 0 字阅读: 0 分钟

⭐umi相关 ​

umi中的基本配置项 ​

1. 基本配置 ​

  • publicPath:指定打包后的资源路径。这对于部署在子目录的应用非常重要。

    js
    publicPath: '/my-app/',
  • outputPath:指定输出目录,默认为 dist。

    js
    outputPath: 'build',
  • base:路由前缀,默认为/

    要在非根目录下部署 umi 项目时,你可以使用 base 配置。

    base 配置允许你为应用程序设置路由前缀。比如有路由 / 和 /users,设置 base 为 /foo/ 后就可通过 /foo/ 和 /foo/users 访问到之前的路由。

2. 代码分割 ​

  • chainWebpack:允许使用 Webpack Chain API 来进一步配置 Webpack。

    js
    chainWebpack(memo) {
      memo.module
        .rule('images')
        .use('url-loader')
          .loader('url-loader')
          .options({
            limit: 10240, // 10kb
            name: 'static/[name].[hash:8].[ext]',
          });
    },
  • dynamicImport:控制动态导入的行为,可以开启或关闭代码分割。

    js
    dynamicImport: {
      loading: '@/components/PageLoading/index',
      webpackChunkName: true,
    },

3. 压缩 ​

  • optimizeCss:启用 CSS 压缩。

    js
    optimizeCss: {
      enabled: true,
    },
  • compress:启用 JavaScript 压缩。

    js
    compress: true,

4. 资源加载 ​

  • proxy:配置代理服务器,这对于开发阶段的 API 调用很有用。

    js
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
      },
    },
  • extraBabelPlugins:添加额外的 Babel 插件。

    js
    extraBabelPlugins: [
      [
        'import',
        { libraryName: 'antd', style: 'css' },
      ],
    ],

5. 分析工具 ​

  • analysis:开启 Webpack Bundle Analyzer 来分析打包后的文件大小。

    js
    analysis: true,
  • disableCSSnano:禁用 CSS 压缩工具 CSSnano。

    js
    disableCSSnano: true,
  • hash:为输出文件添加哈希值。

    js
    hash: true,
  • define:定义全局变量。

    js
    define: {
      'process.env': {
        NODE_ENV: '"production"',
      },
    },

umi项目性能优化 ​

去除console ​

具体操作:https://juejin.cn/post/7103831294013865992

1.安装babel-plugin-transform-remove-console插件

2.在config.ts或.umirc.ts中进行配置

js
//在生产环境中取消console
extraBabelPlugins: [isProd ? 'transform-remove-console' : '']
//或者
//在所有环境中取消console
extraBabelPlugins: [transform-remove-console]

开启gzip压缩 ​

具体操作:https://juejin.cn/post/7103831294013865992

gzip压缩是前端性能优化的一种手段,减少build之后包的体积,加快首屏渲染速度

1.安装 compression-webpack-plugin 插件

2.在config.ts或.umirc.ts中进行配置

js
chainWebpack: function (config, { webpack }) {
    config.merge({
      optimization: {
        splitChunks: {
          chunks: 'all',
          minSize: 1000,
          minChunks: 2,
          automaticNameDelimiter: '.',
          cacheGroups: {
            vendor: {
              name: 'vendors',
              test({ resource }) {
                return /[\\/]node_modules[\\/]/.test(resource)
              },
              priority: 10,
            },
          },
        },
      },
    })
    //在生产环境开启gzip压缩
    if (isProd) {
      // Gzip压缩
      config.plugin('compression-webpack-plugin').use(CompressionPlugin, [
        {
          test: /\.(js|css|html)$/i, // 匹配
          threshold: 10240, // 超过10k的文件压缩
          deleteOriginalAssets: false, // 不删除源文件
        },
      ])
    }
  }

3.还需要后端在nginx中配置 gzip_static on

js
//nginx 配置
gzip_static  on  //检测是否存在gzip文件,有,则返回给客户端

umi中useRequest的底层是什么 ​

Umi.js 中的 useRequest 是一个基于 React Hooks 的功能,用于简化发起网络请求的操作,并且自动处理请求的状态(如 loading、error)。useRequest 的底层实现主要依赖于几个关键部分:

  1. React Hooks:

    • useEffect:用于监听请求的触发条件,并在条件变化时执行请求。
    • useState:用于管理请求的状态(如 loading 状态、错误信息等)。
  2. 请求处理:

    • Umi 的 request 方法:如前所述,这是基于 fetch API 的封装,负责实际的网络请求。
  3. 状态管理:

    • 通过 useState 创建的状态来跟踪请求的进度(如 loading、error、data)。

具体来说,useRequest 的工作流程大致如下:

  1. 初始化状态:使用 useState 初始化请求的状态,如 loading、error 和 data。
  2. 注册请求函数:通过 useCallback 创建一个请求函数,这个函数会在每次调用时返回一个新的函数实例,保证每次请求都是独立的。
  3. 监听依赖项变化:使用 useEffect 监听依赖项的变化,当依赖项改变时触发请求。
  4. 处理请求结果:
    • 请求成功时更新数据状态。
    • 请求失败时处理错误,并更新错误状态。
  5. 清理副作用:在组件卸载或不再需要请求时,取消请求,防止内存泄漏。

示例代码

假设有一个简单的 useRequest 实现(简化版):

jsx
import { useState, useEffect, useCallback } from 'react';
import { request } from '@umijs/max';

function useRequest(url, options = {}) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  const fetchData = useCallback(async () => {
    try {
      setLoading(true);
      const response = await request(url, options);
      setData(response);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [url, options]);

  useEffect(() => {
    fetchData();
  }, [fetchData]); // 依赖项中包含 fetchData,确保每次依赖项改变时重新请求

  return { loading, error, data, refetch: fetchData };
}

MFSU是什么 ​

MFSU 是一种基于 webpack5 新特性 Module Federation(模块联邦) 的打包提速方案。

Module Federation 是 Webpack 5 引入的一项新特性,它允许你将应用拆分成多个可以互相共享模块的“远程”应用。这些应用可以独立部署,并且在运行时按需加载彼此的模块。

其核心的思路是通过分而治之,将应用源代码的编译和应用依赖的编译分离,将变动较小的应用依赖构建为一个 Module Federation 的 remote 应用,以免去应用热更新时对依赖的编译。

开启mfsu可以大幅减少启动项目和热更新所需的时间。在生产模式,也可以通过提前编译依赖,大幅提升部署效率。

MFSU 的底层实现 ​

1. Webpack Module Federation ​

Module Federation 提供了以下关键特性:

  • Remote Entry Files:每个应用会生成一个或多个远程入口文件(remote entry file),其中包含模块的元数据和加载地址。
  • Shared Modules:定义哪些模块可以被共享,并且可以被其他应用使用。
  • Exposure and Consumption:定义模块的暴露和消费方式。
2. @umijs/plugin-mfsu ​

Umi.js 提供了一个名为 @umijs/plugin-mfsu 的插件,它负责配置和管理 Module Federation 的相关设置。这个插件为 Umi.js 添加了对 Module Federation 的支持,并提供了许多便捷的功能,如自动代码分割、动态加载子应用等。

总结

MFSU 的底层实现主要依赖于 Webpack Module Federation 和 Umi.js 的 @umijs/plugin-mfsu 插件。通过这些技术,Umi.js 能够支持微前端架构,使得应用可以被拆分成多个独立部署的子应用,并且在运行时动态加载和组合这些子应用。这种架构模式不仅提高了应用的可维护性和灵活性,还可以显著改善应用的性能和加载速度。

为什么比vite还快 ​

主要是因为它在微前端场景下进行了专门的优化,尤其是在动态加载和代码分割方面。

  1. 模块联邦:MFSU 利用了 Module Federation,这使得微前端架构下的子应用可以动态加载,减少了初始加载时间。
  2. 代码分割:MFSU 提供了更细粒度的代码分割,使得每个子应用都可以作为一个独立的模块加载,而不是一次性加载整个应用。
  3. 全局状态管理:MFSU 支持全局状态管理,这在微前端场景下非常有用,可以减少状态同步的复杂性。

在某些特定场景下,如:

  • 大规模应用:对于非常大的应用,MFSU 的模块联邦和代码分割策略可以显著提高应用的加载速度。
  • 高并发请求:在需要频繁加载不同子应用的情况下,MFSU 的按需加载策略可以减少不必要的资源加载。

相比之下,Vite 更适合快速开发和迭代,特别是在较小规模的应用中。

如果你的应用是一个大型微前端项目,MFSU 可能更适合你;如果你的应用规模较小,或者更关注开发体验,那么 Vite 可能是一个更好的选择。

umi和next的区别 ​

  1. 定位与背景
  • Next.js 由 Vercel 维护,专注于 React 生态的服务端渲染(SSR)和静态站点生成(SSG),适合构建高性能、SEO 友好的 Web 应用(如博客、电商、企业官网等)。
  • Umi 由蚂蚁金服(Ant Group)开发,定位于企业级中后台前端解决方案,提供开箱即用的约定式路由、插件体系、数据流集成等,适合快速开发复杂的中后台系统。
  1. 核心功能对比
功能Next.jsUmi
路由文件系统路由(基于 pages 目录)约定式路由(基于文件结构)或配置式路由
数据获取getServerSideProps/getStaticProps客户端获取(或结合插件扩展服务端能力)
SSR/SSG 支持原生支持需通过插件(如 @umijs/plugin-ssr)
插件系统有限(依赖社区插件)核心特性,高度可扩展
状态管理需自行集成(如 Redux、Zustand)内置集成 Dva、Umi Hooks 等
构建工具Webpack(默认)或 TurbopackWebpack(基于 Umi 封装)
国际化需自行配置内置插件 @umijs/plugin-locale
  1. 适用场景
  • Next.js
    • 需要 SEO 优化的内容型网站(如博客、官网)。
    • 混合渲染应用(SSR + SSG + CSR)。
    • 面向全球用户的国际化项目(社区生态更广泛)。
  • Umi
    • 企业级中后台系统(如管理后台、数据面板)。
    • 需要快速搭建且维护成本低的内部项目。
    • 依赖 Ant Design Pro 生态的项目。
  1. 生态与社区
  • Next.js
    • 国际化社区活跃,插件和教程丰富。
    • 深度集成 Vercel 部署平台。
  • Umi
    • 国内生态为主,与 Ant Design Pro 深度绑定。
    • 适合需要对接阿里系技术栈(如微前端 qiankun)的项目。
  1. 开发体验
  • Next.js
    • 灵活但配置成本较高(如自定义 Webpack)。
    • 适合对 React 生态有经验的开发者。
  • Umi
    • 开箱即用,通过配置或插件简化开发流程。
    • 适合追求快速上手的团队(尤其是 Ant Design 用户)。

总结选择建议

  • 选 Next.js 如果:需要 SEO/SSR、面向全球用户、或构建内容密集型应用。
  • 选 Umi 如果:开发中后台系统、追求开箱即用、或依赖 Ant Design 生态。

实际项目相关 ​

⭐权限设计方案 ​

三种前后端权限管理方案:

1、通过用户角色来过滤菜单(前端方式控制),路由在前端配置,通过API返回角色过滤

2、通过后台来动态生成路由表(后台方式控制)

3、通过后台返回所有权限集合(包括菜单和按钮),前端固定路由,进行过滤

多页签中的右键选中,是如何做到把浏览器原生右键打开的弹窗覆盖掉的? ​

自定义右键菜单具体实现

1、禁用浏览器弹出默认菜单的行为,通过阻止contextMenu事件的默认行为,并同时触发自定义菜单的显示

2、通过Dropdown实现菜单样式,触发器trigger配置contextMenu,从而实现鼠标右键触发下拉菜单

React
const setTab = useMemoizedFn((tab, key, index) => (
  <span onContextMenu={(event) => event.preventDefault()}>
      <Dropdown overlay={setMenu(key, index)} trigger={['contextMenu']}>
        <span className={styles.tabTitle}>{tab}</span>
      </Dropdown>
  </span>
));

竞态问题(TODO) ​

AbortController

https://juejin.cn/post/6970710521104302110

竞态问题(Race Condition)是指在并发编程中,当多个操作顺序不确定时,可能导致意外的结果。在前端开发中,竞态问题通常出现在异步操作中,比如 AJAX 请求、事件监听器的回调、定时器等。下面详细介绍如何解决前端开发中的竞态问题。

常见的竞态问题场景 ​

  1. 多次请求同一资源:例如,当用户快速点击按钮时,可能会触发多次 AJAX 请求。
  2. 状态更新冲突:多个异步操作可能同时更新同一个状态,导致数据不一致。
  3. 异步回调顺序:多个异步操作的回调函数执行顺序不确定,可能导致逻辑错误。

解决竞态问题的方法 ​

1. 使用 Promises 和 async/await ​

Promises 和 async/await 可以帮助你更好地控制异步操作的执行顺序,避免竞态问题。

示例代码:

javascript
async function fetchData(url) {
  try {
    const response = await fetch(url);
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

// 使用 async/await 控制异步操作顺序
(async function() {
  await fetchData('https://api.example.com/data1');
  await fetchData('https://api.example.com/data2');
})();
2. 使用防抖(Debounce)和节流(Throttle) ​

防抖(Debounce)和节流(Throttle)技术可以减少不必要的重复操作,防止在短时间内触发多次异步请求或事件处理。

防抖(Debounce):

javascript
function debounce(func, wait) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), wait);
  };
}

const buttonClickHandler = debounce(function() {
  console.log('Button clicked');
}, 300);

节流(Throttle):

javascript
function throttle(func, limit) {
  let inThrottle;
  return function() {
    const args = arguments;
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

const resizeHandler = throttle(function() {
  console.log('Window resized');
}, 200);
3. 使用锁机制(Lock) ​

在某些情况下,可以使用锁机制来确保同一时间内只有一个操作可以执行。这种方法适用于状态更新等场景。

示例代码:

javascript
let lock = false;

function updateState(newState) {
  if (lock) return;
  lock = true;
  // 更新状态
  console.log('State updated to:', newState);
  // 模拟异步操作
  setTimeout(() => {
    lock = false;
  }, 1000);
}

document.getElementById('button').addEventListener('click', () => {
  updateState('new state');
});
4. 使用原子操作 ​

在更新状态时,尽量使用不可分割的操作,确保状态的一致性。

示例代码:

javascript
let counter = 0;

function incrementCounter() {
  // 原子操作
  counter++;
  console.log('Counter incremented to:', counter);
}

incrementCounter();
incrementCounter();
5. 使用状态管理库 ​

状态管理库如 Redux、MobX 等可以帮助你更好地管理应用的状态,避免竞态问题。

Redux 示例:

javascript
import { createStore } from 'redux';
import { Provider } from 'react-redux';

const initialState = { count: 0 };

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
}

const store = createStore(reducer);

function App() {
  return (
    <Provider store={store}>
      <button onClick={() => store.dispatch({ type: 'INCREMENT' })}>
        Increment
      </button>
      <p>{store.getState().count}</p>
    </Provider>
  );
}
6. 使用中间件 ​

在使用 Redux 等状态管理库时,可以使用中间件如 redux-thunk 或 redux-saga 来处理异步操作,并确保操作的顺序。

Redux Thunk 示例:

javascript
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';

const initialState = { count: 0 };

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
}

const store = createStore(reducer, applyMiddleware(thunk));

function incrementAsync() {
  return (dispatch) => {
    setTimeout(() => {
      dispatch({ type: 'INCREMENT' });
    }, 1000);
  };
}

function App() {
  return (
    <Provider store={store}>
      <button onClick={incrementAsync}>
        Increment Async
      </button>
      <p>{store.getState().count}</p>
    </Provider>
  );
}

如何防止接口重复请求 ​

1. 按钮禁用(最简单直接) ​

在请求发出后禁用操作按钮,请求完成后恢复:

js
// Vue3 示例(其他框架同理)
const loading = ref(false);

const fetchData = async () => {
  if (loading.value) return; // 防止重复点击
  
  loading.value = true;
  try {
    await axios.get('/api/data');
  } finally {
    loading.value = false; // 无论成功失败都恢复按钮
  }
};

适用场景:表单提交、支付按钮等关键操作。


2. 请求锁(标志位) ​

通过标志变量控制请求状态:

js
let isFetching = false; // 全局请求锁

const fetchData = async () => {
  if (isFetching) return;
  
  isFetching = true;
  try {
    const res = await fetch('/api/data');
    // 处理数据...
  } finally {
    isFetching = false; // 解锁
  }
};

3. 请求取消(AbortController) ​

取消上一次未完成的相同请求(适用于搜索联想等场景):

js
let activeRequestController = null; // 存储当前活动的控制器

async function fetchData(url, delay = 0) {
  // 取消上一个未完成的请求
  if (activeRequestController) {
    activeRequestController.abort('取消重复请求');
  }

  // 创建新的AbortController
  const controller = new AbortController();
  activeRequestController = controller;

  try {
    // 发送请求(添加延迟参数模拟慢请求)
    const response = await axios.get(url, {
      signal: controller.signal, // 关键:传递取消信号
      params: { delay } // 模拟慢请求
    });

    // 请求完成后清除控制器
    activeRequestController = null;
    return response.data;
  } catch (error) {
    // 如果是主动取消的请求,不视为错误
    if (axios.isCancel(error)) {
      throw '请求被取消';
    }
    throw error;
  }
}

4. 防抖(Debounce) ​

合并短时间内多次触发(如搜索框输入):

js
const debounce = (fn, delay) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
};

// 使用:300ms内连续输入只发1次请求
const search = debounce((query) => {
  fetch(`/api/search?q=${query}`);
}, 300);

5. 节流(Throttle) ​

限制请求频率(如滚动加载):

js
const throttle = (fn, interval) => {
  let lastTime = 0;
  return (...args) => {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn(...args);
      lastTime = now;
    }
  };
};

// 使用:每500ms最多触发1次
const loadMore = throttle(() => {
  fetch('/api/more-data');
}, 500);

6. 请求缓存(避免重复数据) ​

缓存已请求的数据(如切换Tab时):

js
const cache = new Map(); // 简单内存缓存

const fetchData = async (id) => {
  if (cache.has(id)) {
    return cache.get(id); // 返回缓存数据
  }
  
  const res = await axios.get(`/api/data/${id}`);
  cache.set(id, res.data); // 设置缓存
  return res.data;
};

7. 请求映射表(高级场景) ​

跟踪每个请求的状态(适用于复杂并发控制):

js
const pendingRequests = new Map();

const fetchData = async (key) => {
  if (pendingRequests.has(key)) {
    return pendingRequests.get(key); // 返回已存在的Promise
  }
  
  const promise = axios.get(`/api/data?key=${key}`);
  pendingRequests.set(key, promise);
  
  try {
    const res = await promise;
    return res.data;
  } finally {
    pendingRequests.delete(key); // 清理
  }
};

⭐后端返回 10w 条数据,前端该如何处理 ​

处理后端返回的 10 万条数据时,前端需要避免直接渲染所有数据(会导致页面崩溃),而是采用性能优化策略。

1、服务器端分页和过滤 ​

  • 服务器端分页: 最理想的方法是让后端只返回当前页面所需的数据,通过接口传递分页参数(例如 page 和 limit)。这样前端只处理少量数据,减轻渲染压力。
  • 服务器端过滤和排序: 在后端做数据筛选,只返回满足条件的数据,进一步减少前端接收的数据量。

2、前端虚拟化技术 ​

  • 虚拟滚动(Virtual Scrolling): 如果一定要在前端加载大数据集,采用虚拟列表技术仅渲染当前可见区域的数据项。例如使用 React 的 react-window 或 react-virtualized;对于 Vue 也有类似的虚拟列表组件。

    js
    // 使用现有库(推荐):
    // - React: react-window / react-virtualized
    // - Vue: vue-virtual-scroller
    // - 原生:使用 IntersectionObserver 或计算滚动位置
    
    // 示例(react-window):
    import { FixedSizeList as List } from 'react-window';
    
    const items = Array.from({ length: 100000 }, (_, index) => `Item ${index}`);
    
    const Row = ({ index, style }) => (
      <div style={style}>{items[index]}</div>
    );
    
    const VirtualList = () => (
      <List height={600} itemCount={items.length} itemSize={35} width={300}>
        {Row}
      </List>
    );
  • 懒加载(Lazy Loading): 仅在用户滚动或交互时动态加载数据,避免一次性加载所有数据。

    js
    let currentPage = 1;
    const container = document.getElementById('list');
    
    container.addEventListener('scroll', () => {
      if (container.scrollTop + container.clientHeight >= container.scrollHeight) {
        loadPage(++currentPage);
      }
    });

3、异步数据处理 ​

  • Web Worker: 若前端需要对数据进行复杂计算,可以使用 Web Worker 将计算放到后台线程,避免阻塞主线程。

    js
    // 主线程
    const worker = new Worker('data-worker.js');
    worker.postMessage({ action: 'sort', data: bigData });
    worker.onmessage = (e) => updateUI(e.data);
    
    // data-worker.js
    self.onmessage = (e) => {
      if (e.data.action === 'sort') {
        const sorted = e.data.data.sort(/* ... */);
        self.postMessage(sorted);
      }
    };
  • 分批渲染: 将数据分为小批次,逐步渲染到页面上,从而分摊渲染开销,保持 UI 流畅。

    js
    function renderChunk(data, chunkSize = 100) {
      let index = 0;
      function process() {
        const chunk = data.slice(index, index + chunkSize);
        chunk.forEach(item => renderItem(item));
        index += chunkSize;
        if (index < data.length) requestAnimationFrame(process);
      }
      process();
    }

4、数据缓存与状态管理 ​

  • 数据缓存: 对于已加载的数据可以使用内存缓存或者 IndexedDB 缓存,避免重复请求,提升用户体验。
  • 状态管理工具: 使用 Redux、Vuex 等状态管理工具,结合分页、懒加载策略管理大数据集的状态,避免全量数据占用内存。

一个网页,一开始很流畅,越用越卡顿,要怎么解决? ​

1.内存泄漏检查

  • 表现: 内存占用持续上升,不释放。
  • 解决方案:
    • 使用 Chrome DevTools 的 Memory 面板 ,录制快照 (Heap Snapshot),查看 DOM 节点、事件监听器是否未被正确清理。
    • 确保组件卸载时清除定时器、事件监听 (removeEventListener) 和订阅。
    • 避免闭包导致无法释放变量。

2. 不必要的状态和数据堆积

  • 表现: 前端状态或数据管理混乱,状态持续增长。
  • 解决方案:
    • 检查状态管理工具(Redux、Vuex)中的数据,避免存储大规模冗余数据。
    • 使用数据分页、懒加载策略,减少前端数据体积。
    • 清理过期缓存数据,避免 IndexedDB 或 LocalStorage 无限堆积。

3. 节流和防抖优化

  • 表现: 频繁用户交互导致过多重渲染或计算。
  • 解决方案:
    • 使用 throttle 和 debounce 控制滚动、输入等高频事件。
    • 框架层面可使用 React.memo、Vue 的 computed 属性等避免不必要渲染。

4. DOM 操作与渲染性能优化

  • 表现: 频繁重排重绘,页面渲染卡顿。
  • 解决方案:
    • 检查是否有频繁的 DOM 操作,优化为批量更新。
    • 使用虚拟 DOM 或虚拟滚动(virtual scrolling)技术。
    • 尽量避免触发 Layout Throttle 属性(如 offsetWidth、getBoundingClientRect())。

5. 垃圾回收 (GC) 问题

  • 表现: 短时间内频繁的卡顿现象。
  • 解决方案:
    • 检查大对象频繁创建导致的 GC 开销。
    • 优化对象复用策略,减少不必要的内存分配。

6. 资源管理优化

  • 表现: 资源加载越来越慢。
  • 解决方案:
    • 使用 IntersectionObserver 实现懒加载,避免图片和第三方资源过早加载。
    • 确保 WebSocket 连接、第三方 SDK 及时关闭。

7. 工具与监控

  • 工具:
    • Chrome DevTools: 内存分析(Heap)、性能分析(Performance)。
    • Lighthouse: 检查性能瓶颈。
    • 前端监控平台(如 Sentry): 收集卡顿和性能数据。

一个 web 系统,加载很慢,交给你来优化,你会怎么办? ​

1.性能分析与瓶颈定位

  • 使用 Chrome DevTools 的 Performance 和 Network 面板,分析页面加载的时间消耗,找出以下关键瓶颈:
    • 白屏时间 (First Paint)过长
    • 首屏渲染时间 (Largest Contentful Paint, LCP)过慢
    • 阻塞资源 导致延迟加载
    • API 请求过多或响应时间长

2. 资源加载优化

  • 减少 HTTP 请求数量:
    • 合并 CSS、JS 文件,或者采用 Tree Shaking 去掉无用代码。
    • 使用雪碧图(Sprites)处理小图片,或者直接改用 SVG。
  • 压缩与优化资源:
    • 压缩图片(使用 WebP),优化视频大小。
    • 压缩 JS、CSS、HTML 文件,开启 Gzip 或 Brotli 压缩。
  • Lazy Loading:
    • 延迟加载图片和视频,使用 loading="lazy" 属性。
    • 采用懒加载策略来加载非首屏模块。

3. 静态资源缓存

  • 启用浏览器缓存: 设置 Cache-Control、ETag 等响应头,缓存静态资源。
  • 使用 CDN: 静态资源分发到 CDN 节点,减少服务器负载。

4. 网络传输优化

  • 启用 HTTP/2 或 HTTP/3: 并行加载资源,降低传输延迟。
  • 减少跨域请求: 优化 API 接口分布,避免预检请求(OPTIONS)。
  • 开启 DNS 预解析: 提前解析第三方域名。

5. 渲染与框架性能优化

  • 服务端渲染(SSR)/静态生成(SSG): 减少客户端渲染时间,提升首屏性能。
  • 组件懒加载: 分离路由和组件,按需加载代码。
  • 虚拟化列表: 渲染大量数据时使用 react-window 或类似方案。

6. 后端与 API 优化

  • 数据库优化:
    • 建立索引,优化查询。
    • 数据库结果分页返回。
  • 接口合并与优化: 减少多次 API 调用,使用 GraphQL 或批量 API。
  • 缓存策略: 使用 Redis 等缓存热点数据,减轻数据库查询压力。

7. 用户体验提升

  • 骨架屏: 在加载内容前显示占位图,减少白屏时间。
  • Loading 动画: 提升用户感知体验。

8. 监控与持续优化

  • 引入性能监控工具:
    • Lighthouse 进行性能分析。
    • Sentry 捕获性能问题。
    • Web Vitals(FCP、LCP、CLS)实时监控。

如果让你从零搭建一个后台管理系统,你会怎么做? ​

1⃣ 技术选型: Vite + Vue3 + Pinia + Element Plus/Ant Design Vue(主流方案); 2⃣ 工程化: 配好ESLint/Prettier/Commit规范,拆分环境变量; 3⃣ 核心模块: 权限路由(动态路由表+按钮级权限)、请求封装(拦截器+错误统一处理)、状态管理(按模块拆分Store); 4⃣ 性能与安全: 路由懒加载、接口防抖、XSS过滤(展现全局观); 5⃣ 协作: 用GitFlow管理分支,写清晰README和CHANGELOG(工程意识)

遇到复杂Bug(如页面内存泄漏)怎么排查? ​

1⃣ 复现 & 定位: 用Chrome DevTools的Memory面板抓快照,对比增量; 2⃣ 常见嫌疑点: 检查全局事件监听、定时器、闭包引用、第三方库订阅; 3⃣ 工具验证: 用 performance.memory 或 window.performance 监控; 4⃣ 解决: 移除无用引用、解绑事件、优化组件销毁逻辑。

其他 ​

⭐什么是服务端渲染?(SSR) ​

服务端渲染(Server-Side Rendering,简称 SSR)是一种将网页内容在服务器端动态生成并发送给客户端的技术。传统的客户端渲染(Client-Side Rendering,简称 CSR)是在客户端浏览器中使用 JavaScript 动态生成页面内容。

在传统的客户端渲染中,浏览器首先下载一个空的 HTML 页面,然后通过 JavaScript 请求数据并生成页面内容。这种方式的优点是可以提供更丰富的交互和动态效果,但也存在一些缺点。例如,搜索引擎爬虫可能无法正确解析和索引页面内容,导致 SEO(搜索引擎优化)问题。同时,初始加载时用户可能会看到空白的页面或者出现闪烁的内容。

相比之下,服务端渲染通过在服务器上预先生成完整的 HTML 页面,将其发送给客户端浏览器。这样,浏览器在接收到页面时就能够立即显示完整的内容,而不需要等待 JavaScript 的下载和执行。这样可以提高页面的加载速度和首次渲染速度,并且对于搜索引擎爬虫来说更容易解析和索引页面内容,有利于 SEO。

客户端渲染:获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。

服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。

SSR 的核心优势

  1. 更快的首屏加载

    SSR 在服务器端生成 HTML,用户无需等待 JavaScript 加载完成即可看到页面内容。

  2. 更好的 SEO

    搜索引擎可以抓取服务器渲染的完整 HTML 内容,而不是空的 <div id="root"></div>。

  3. 更好的用户体验

    对于低性能设备或网络较差的用户,SSR 可以提供更快的初始渲染。

缺点:配置麻烦,增加了服务器的计算压力。

SSR 的基本原理

  1. 服务器端
    • 使用 ReactDOMServer 将 React 组件渲染为 HTML 字符串。
    • 将生成的 HTML 字符串嵌入到 HTML 模板中,并发送给客户端。
  2. 客户端
    • 客户端接收到 HTML 后,React 会“接管”页面(hydration),使其成为可交互的 SPA(单页应用)。

客户端渲染过程 ​

  1. 访问客户端渲染的网站。
  2. 服务器返回一个包含了引入资源语句和 <div id="app"></div> 的 HTML 文件。
  3. 客户端通过 HTTP 向服务器请求资源,当必要的资源都加载完毕后,执行 new Vue() 开始实例化并渲染页面。

服务端渲染过程 ​

  1. 访问服务端渲染的网站。
  2. 服务器会查看当前路由组件需要哪些资源文件,然后将这些文件的内容填充到 HTML 文件。如果有 ajax 请求,就会执行它进行数据预取并填充到 HTML 文件里,最后返回这个 HTML 页面。
  3. 当客户端接收到这个 HTML 页面时,可以马上就开始渲染页面。与此同时,页面也会加载资源,当必要的资源都加载完毕后,开始执行 new Vue() 开始实例化并接管页面。

从上述两个过程中可以看出,区别就在于第二步。客户端渲染的网站会直接返回 HTML 文件,而服务端渲染的网站则会渲染完页面再返回这个 HTML 文件。

Nuxt.js、Next.js、Nest.js的区别 ​

  • Nuxt.js 和 Next.js 都是用于构建服务器渲染应用的框架(SSR),分别基于 Vue.js 和 React。
  • Nuxt.js 适用于构建 Vue.js 应用程序,提供了默认的配置和约定,使得开发 SSR 应用更加简单。
  • Next.js 适用于构建 React 应用程序,具有出色的性能和开发体验,并支持静态生成和服务器端渲染。
  • Nest.js 是一个用于构建 Node.js 服务器端应用的框架,结合了 TypeScript 和面向对象编程的概念,提供了模块化的架构设计和丰富的功能。

什么是SEO,怎么解决SEO不友好 ​

seo是搜索引擎优化。在搜索引擎自然排名机制的基础上,对网站进行内部及外部的调整优化,改进网站在搜索引擎中的关键词自然排名,获得更多的流量

单页面的内容是根据路由变化动态生成并展示出来的,很多页面的内容是通过ajax异步获取的,网络抓取工具并不会等待异步请求完成后再行抓取页面内容

搜索引擎爬虫是不会等待异步请求数据结束后再抓取信息的

解决方式可以使用SSR(服务端渲染)或者是预渲染

⭐如何上传大文件? ​

如何实现大文件上传

大文件上传主要有以下几种方案:

切片上传

  • 将大文件分割成小块
  • 并发上传多个切片
  • 服务端合并所有切片
  • 支持断点续传和进度显示

实现步骤:

1.前端切片

js
function createFileChunk(file, size = 1 * 1024 * 1024) {
  const chunks = []
  let cur = 0
  while (cur < file.size) {
    chunks.push(file.slice(cur, cur + size))
    cur += size
  }
  return chunks
}

2.上传切片

js
async function uploadChunks(chunks) {
  const requests = chunks.map((chunk, index) => {
    const formData = new FormData()
    formData.append('chunk', chunk)
    formData.append('index', index)
    return axios.post('/upload', formData)
  })
  await Promise.all(requests)
}

3.发送合并请求

js
await axios.post('/merge', {
  filename: file.name,
  size: chunks.length,
})

断点续传

  • 记录已上传的切片
  • 重新上传时跳过已上传的部分
  • 可以通过 localStorage 存储进度
  • 使用 hash 标识文件和切片

秒传

  • 上传前先发送文件 hash
  • 服务端存在相同文件则直接返回
  • 可以使用 spark-md5 计算文件 hash

性能优化

  • 并发控制:限制同时上传的切片数
  • 切片大小:根据网络状况动态调整
  • 进度显示:计算整体上传进度
  • 错误重试:单个切片上传失败后重试

网页多标签页之间如何通讯?和 iframe 如何通讯? ​

网页多标签页和 iframe 通讯的关键考点是跨窗口和跨域通信模型的选择,以及不同场景下的适用方法。可以从以下几个方面分析:

1. 多标签页之间的通讯方法

  • BroadcastChannel API 同源的多个标签页可以使用 BroadcastChannel 进行消息广播,简单方便。 示例 :

    javascript
    const channel = new BroadcastChannel('my_channel')
    channel.postMessage('Hello from another tab!')
    channel.onmessage = (event) => {
      console.log('Received message:', event.data)
    }
  • LocalStorage + Storage 事件监听 不同标签页可以共享 localStorage,通过监听 storage 事件实现通讯。 示例 :

    javascript
    window.addEventListener('storage', (event) => {
      if (event.key === 'my_key') {
        console.log('Received message:', event.newValue)
      }
    })
    localStorage.setItem('my_key', 'Hello from another tab!')
  • Service Worker 通过 Service Worker 作为中介,实现跨标签页通讯。适合 PWA 场景。

  • WebSocket 通过服务器中转实现实时通讯,适合跨域或需要长连接的场景。

2. iframe 通讯方法

  • postMessage API 最常用的方式,可以跨域发送消息。父页面和 iframe 双向通信都支持。 示例(父页面向 iframe 发送消息) :

    javascript
    const iframe = document.querySelector('iframe')
    iframe.contentWindow.postMessage('Hello iframe!', '*')
    window.addEventListener('message', (event) => {
      console.log('Received from iframe:', event.data)
    })
  • URL Hash 传参 通过修改 iframe 的 URL 哈希来传递参数。适用于简单场景。 示例 :

    javascript
    iframe.src = 'https://example.com#message=Hello'
  • 共享 Cookie 或 LocalStorage 在同源环境下可以通过共享存储机制间接通讯。

从 0 搭建一个前端项目,需要考虑哪些方面 ​

现在创建一个 Vue React 一般都是拿脚手架 cli 一键生成项目,这是最基础的。

在实际工作中还需要考虑更多的内容。

  • 代码仓库,发布到哪个 npm 仓库(如有需要)
  • 技术选型 Vue React 等
  • 代码目录规范
  • 打包构建 webpack 等,做打包优化
  • eslint prettier commit-lint
  • husky pre-commit
  • 单元测试 + 集成测试
  • CI/CD 流程,自动测试,自动发布测试环境,自动部署
  • 开发环境,预发布环境
  • 开发文档,研发规范

如何做好技术选型 ​

当在工作中选择一个 语言/框架/工具 时,需要考虑什么?

  • 社区热门程度,搜索引擎和 StackOverflow 上能搜出多少相关资料?
  • 创办时间和发展时间,不要用太新的技术,首先要求稳
  • 看使用人数,参考 GitHub star 数量和 npm 下载量
  • 看社区生态的完善程度,第三方的 UI 、组件、插件等,都是否完善
  • 团队成员的学习成本 —— 这一点很重要,很多人会忽略

如何理解技术方案设计?是否做过技术方案设计? ​

  • state 数据结构,如会存储在 vuex 或 redux 中
  • 组件 UI 结构,嵌套关系,属性如何传递
  • 会用到哪些服务端的 API ,哪些是现有的,哪些需要新开发
  • 如有复杂逻辑,说明计算过程,时间复杂度
  • 是否有性能隐患?
  • 是否有安全隐患?

线上出了严重 bug 你该如何解决? ​

  • 回滚,及时止损 —— 这一步最重要,很多人不知道这一步!!!
  • 通知项目组成员,看谁最近有过上线?—— 线上 bug 一般是最近一次上线导致的
  • 在本地或测试环境浮现 bug,查找原因
  • 修复,测试,重新上线
  • 开复盘会议,以后如何规避此类问题 —— 复盘会议,也是很多人不知道的

另,如果你项目没有监控报警的话,最好加一个,这样线上有 bug 会及时报警。

如何保障代码质量 ​

  • 配置统一的 eslint 和 prettier 规则,规范代码格式
  • 每次代码合并,都进行 code review ,外加每周一次团队 code review
  • 编写单元测试,提交 commit 时自动触发单元测试
  • 使用 Sentry 等平台进行线上错误报警,并及时修复问题

ui组件库选型时,会考虑哪些因素 ​

1. 技术栈匹配 ​

  • 框架兼容性:是否支持项目所用前端框架(React、Vue、Angular等)。 示例:Ant Design(React)、Element UI(Vue)、NG-ZORRO(Angular)。
  • 版本适配性:是否兼容当前技术栈版本(如React 18+、Vue 3)。

2. 功能覆盖与组件丰富度 ​

  • 基础组件:按钮、表单、表格、弹窗等是否齐全。
  • 高级组件:是否包含复杂需求组件(如可编辑表格、拖拽排序、树形控件)。
  • 扩展性:是否支持自定义组件或覆盖默认行为。

3. 定制化能力 ​

  • 主题系统:是否支持通过CSS变量、主题配置文件或设计工具(如Figma插件)调整样式。
  • 样式覆盖:能否灵活修改组件样式(如通过CSS-in-JS、Sass/Less变量)。 示例:Material-UI的ThemeProvider、Ant Design的定制主题工具。

4. 文档与开发者体验 ​

  • 文档质量:是否提供清晰的API文档、示例代码和最佳实践指南。
  • 开发工具:是否有配套的CLI工具、TypeScript支持、IDE插件等。
  • 学习曲线:组件API设计是否直观,上手难度如何。

5. 性能与体积 ​

  • 包大小:是否支持按需加载(Tree-shaking)以减少最终构建体积。
  • 渲染性能:大数据量场景下的表现(如虚拟滚动、懒加载)。 示例:AG Grid针对大数据表格优化,TanStack Table轻量灵活。

6. 可访问性(A11y) ​

  • WCAG合规性:是否遵循无障碍标准(如ARIA标签、键盘导航)。
  • 屏幕阅读器支持:组件是否通过无障碍测试工具(如axe、Lighthouse)。

7. 社区与维护 ​

  • 活跃度:GitHub Stars、Issue响应速度、版本更新频率。
  • 长期维护:是否有稳定团队或公司支持(如Ant Design由蚂蚁集团维护)。
  • 生态插件:是否有第三方插件或工具链支持(如ProComponents、Storybook集成)。

8. 企业级需求 ​

  • 国际化(i18n):是否内置多语言支持。
  • 权限控制:组件是否支持动态渲染(如根据权限隐藏按钮)。
  • 安全合规:是否符合企业安全要求(如XSS防护)。

9. 设计与一致性 ​

  • 设计语言:是否符合产品品牌风格(如Material Design、Ant Design风格)。
  • 设计资源:是否提供Figma/Sketch设计文件(如IBM Carbon、Semi Design)。

10. 商业与法律因素 ​

  • 开源协议:MIT、Apache 2.0等是否允许商用(避免GPL等限制性协议)。
  • 商业支持:是否需要付费版(如DevExtreme、Kendo UI)或专业服务。

11. 团队适配性 ​

  • 熟悉度:团队是否已有相关技术经验,降低学习成本。
  • 内部规范:是否与现有代码规范、工程化流程兼容(如SSR、微前端支持)。

12. 跨端与响应式 ​

  • 移动端适配:是否提供移动端组件(如Vant、MUI Mobile)。
  • 响应式布局:是否支持不同屏幕尺寸(如Grid布局、断点系统)。

最终决策:优先选择技术栈匹配度高、社区活跃、文档完善的库,同时结合团队习惯和长期维护成本。

例如:

  • 中后台系统:Ant Design(React)、Element Plus(Vue 3)。
  • 极致性能:Headless UI(无样式)+ Tailwind CSS。
  • 跨端应用:MUI(Web)、React Native Paper(移动端)。