myDocs
首页
  • JavaScript小记
  • HTML小记
  • CSS小记
  • 计算机网络
  • React小记
  • Vue小记
  • 手写js
  • 前端工程化
  • 前端性能优化
  • 实际项目开发
  • Typescript面试题
  • Nodejs面试题
  • 小程序
  • 排序
  • 算法题
  • Git小记
  • NodeJs小记
  • TypeScript小记
  • 正则表达式入门
  • Linux基本命令
  • PixiJS的基本使用
  • PixiJS实现一镜到底
  • Canvas入门
  • SVG入门
  • Echarts基本使用
  • antv G6的基础入门及树图的实际应用
  • Three.js
  • 《CSS揭秘》
  • 《Python编程:从入门到实践》
  • 低代码数据可视化平台开发记录
  • 中后台管理系统模板记录
  • 多页签开发记录
  • 浙政钉、浙里办、浙江政务服务网应用上架指南
Github
首页
  • JavaScript小记
  • HTML小记
  • CSS小记
  • 计算机网络
  • React小记
  • Vue小记
  • 手写js
  • 前端工程化
  • 前端性能优化
  • 实际项目开发
  • Typescript面试题
  • Nodejs面试题
  • 小程序
  • 排序
  • 算法题
  • Git小记
  • NodeJs小记
  • TypeScript小记
  • 正则表达式入门
  • Linux基本命令
  • PixiJS的基本使用
  • PixiJS实现一镜到底
  • Canvas入门
  • SVG入门
  • Echarts基本使用
  • antv G6的基础入门及树图的实际应用
  • Three.js
  • 《CSS揭秘》
  • 《Python编程:从入门到实践》
  • 低代码数据可视化平台开发记录
  • 中后台管理系统模板记录
  • 多页签开发记录
  • 浙政钉、浙里办、浙江政务服务网应用上架指南
Github
  • 实际项目开发

实际项目开发

umi相关⭐

umi中的基本配置项

1. 基本配置

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

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

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

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

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

2. 代码分割

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

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

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

3. 压缩

  • optimizeCss:启用 CSS 压缩。

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

    compress: true,

4. 资源加载

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

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

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

5. 分析工具

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

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

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

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

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

umi项目性能优化

去除console

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

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

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

//在生产环境中取消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中进行配置

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

//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 实现(简化版):

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,从而实现鼠标右键触发下拉菜单

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 可以帮助你更好地控制异步操作的执行顺序,避免竞态问题。

示例代码:

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):

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):

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)

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

示例代码:

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. 使用原子操作

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

示例代码:

let counter = 0;

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

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

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

Redux 示例:

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 示例:

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. 按钮禁用(最简单直接)

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

// 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. 请求锁(标志位)

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

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

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

3. 请求取消(AbortController)

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

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)

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

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)

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

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时):

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. 请求映射表(高级场景)

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

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 也有类似的虚拟列表组件。

    // 使用现有库(推荐):
    // - 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): 仅在用户滚动或交互时动态加载数据,避免一次性加载所有数据。

    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 将计算放到后台线程,避免阻塞主线程。

    // 主线程
    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 流畅。

    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.前端切片

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.上传切片

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.发送合并请求

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

断点续传

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

秒传

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

性能优化

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

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

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

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

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

    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 事件实现通讯。 示例 :

    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 发送消息) :

    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 哈希来传递参数。适用于简单场景。 示例 :

    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(移动端)。
最近更新: 2025/8/17 08:11
Contributors: csmSimona