2024前端面试题总结

428 阅读19分钟

1. 前端应用 如何做国际化?

关键词:国际化

前端应用实现国际化(i18n)主要是为了支持多语言环境,提高用户体验。这里有几种常用的方案:

  1. 使用国际化库:这是最常用的方法之一,可以通过引用第三方库来管理不同语言环境的资源文件。比如:

    • React:可以使用react-intlreact-i18next
    • Vue:可以使用vue-i18n
    • Angular:可以使用@ngx-translate/core

    这些库允许你将文本资源分开管理,并根据用户的语言偏好动态加载相应的资源。

  2. 浏览器 API:利用浏览器内置的国际化 API,如Intl对象,来格式化日期、时间、货币等。

  3. 自建国际化框架:根据项目的具体需求,自定义国际化实现。这通常包括:

    • 创建资源文件:为每种语言创建一个资源文件,用于存储翻译字符串。
    • 语言选择功能:允许用户选择偏好的语言。
    • 加载对应资源文件:根据用户的语言偏好,动态加载对应的资源文件并在界面上显示相应的文本。
  4. 服务端支持:有些情况下,前端应用可能需要服务端的支持来实现国际化,如动态提供不同语言的数据内容。

  5. URL 路由:在 URL 中包含语言参数,来确定显示哪种语言的内容。例如,/en/about 显示英文版“关于”页面,而 /zh/about 显示中文版。

  6. 浏览器语言检测:通过检测浏览器的navigator.language属性来自动选择最合适的语言版本。

在实际应用中,根据项目的大小、复杂度以及特定需求,可以选择一种或多种方案结合使用,以达到最佳的国际化效果。

2. flex:1 代表什么

关键词:flex 布局相关属性问题

在 CSS 的弹性盒模型(Flexbox)中,flex: 1表示子项(flex 子项)的伸缩性。

具体来说,flex: 1flex-growflex-shrinkflex-basis三个属性的简写。其默认值等同于flex: 1 1 0%,分别代表以下含义:

  • flex-grow: 1:定义项目的放大比例为 1。这意味着当弹性容器有剩余空间时,该子项将按照比例伸展以填充剩余空间。如果存在多个flex-grow: 1的子项,它们将等分剩余空间。
  • flex-shrink: 1:定义项目的缩小比例为 1。即如果空间不足,该项目将缩小。
  • flex-basis: 0%:在分配多余空间之前,计算项目是否有多余空间,这里的0%表示不考虑项目本身的大小。

flex: 1经常用于自适应布局。例如,将父容器的display设置为flex,侧边栏大小固定后,将内容区设置为flex: 1,内容区则会自动放大占满剩余空间。

3. 请求失败会弹出一个 toast , 如何保证批量请求失败, 只弹出一个 toast

关键词:单例 toast

要确保批量请求失败时只弹出一个 toast,可以通过以下几种方式实现:

  1. 设置全局标志位:定义一个全局变量(如 isToastShown)来表示是否已经弹出过 toast。在请求失败的处理逻辑中,首先检查该标志位。如果尚未弹出 toast,则进行弹出操作,并设置标志位为 true;如果标志位已经为 true,则直接忽略后续的弹出操作。

  2. 使用防抖或节流函数:防抖(debounce)或节流(throttle)函数可以限制某个函数在一定时间内的执行次数。将弹出 toast 的操作封装在防抖或节流函数中,确保在短时间内的多个请求失败时,不会频繁弹出 toast。

  3. 集中处理错误:将所有请求的错误集中处理,而不是在每个请求的 catch 块中直接弹出 toast。例如,把所有请求的 Promise 添加到一个数组中,然后使用 Promise.all() 或其他类似方法来统一处理这些 Promise 的结果。如果所有请求都失败了,再弹出一个 toast。

  4. 如果使用的是一些前端框架或库,它们可能提供了更方便的方式来处理这种情况。例如,在 Vue.js 中,可以使用 Vuex 来管理全局状态,实现类似的功能。具体的实现方式可能会因项目的架构和使用的技术而有所不同,但基本思路是相似的。

4. js 如何判空? 「空」包含了:空数组、空对象、空字符串、0、undefined、null、空 map、空 set , 都属于为空的数据

 function isEmpty(value) {
  // 空字符串
  if (typeof value === "" && value.trim() === "") {
 return true;
  }
  // 空数组
  if (Array.isArray(value) && value.length === 0) {
    return true;
  }
  // 空对象(不包括 `null`)
  if (typeof value === "object" && value !== null && Object.keys(value).length === 0) {
    return true;
  }
  // 数字 0
  if (typeof value === "number" && value === 0) {
    return true;
  }
  // `undefined`
  if (typeof value === "undefined") {
    return true;
  }
  // `null`
  if (value === null) {
    return true;
  }
  // 空 `Map`
  if (value instanceof Map && value.size === 0) {
    return true;
  }
  // 空 `Set`
  if (value instanceof Set && value.size === 0) {
    return true;
  }
}

5. dom 里面, 如何判定 a 元素是否是 b 元素的子元素

关键词:dom.contains 方法

function isChildElement(a, b) {
  return b.contains(a);
}

if (isChildElement(elementA, elementB)) {
  console.log("元素 A 是元素 B 的子元素");
}

6. [微前端] 微前端架构一般是如何做 JavaScript 隔离

关键词:JS 隔离

在微前端架构中,JavaScript 隔离是核心之一,用以确保各个子应用间代码运行时不互相干扰、变量不冲突,以及能够安全地卸载应用。为了实现这一目标,主要采用以下几种方法:

1. 使用沙箱技术:

  • iframe:最直接的隔离方式是将子应用运行在iframe中。这种方式提供了良好的隔离性,因为iframe内部有自己独立的执行环境,包括 JavaScript 运行环境和 DOM 环境。但iframe的使用可能会导致性能问题,且父子通信复杂。
  • JavaScript Sandboxing:通过创建一个独立的 JavaScript 执行环境,比如使用 Web Workers,或者更高级的沙箱库(如 Google 的 Caja),以在主页环境隔离执行 JavaScript 代码。

2. 命名空间和模块化:

  • 命名空间:通过命名空间(Namespace)封装每个子应用的代码,确保全局变量和函数不会与其他应用冲突。
  • 模块化:利用 ES Modules 或 CommonJS 等模块化标准,使代码封装在模块中运行,通过 import/export 管理依赖,减少全局变量的使用,从而实现隔离。

3. 状态管理隔离:

  • 虽然���要关注 JavaScript 代码的隔离,但在单页应用中,子应用间状态管理(如使用 Redux、Vuex 等状态管理库)也可能导致隔离问题。可以为每个子应用创建独立的状态树,只通过明确定义的接口来共享必要的状态信息。

4. 使用微前端框架或库:

  • 模块联邦(Module Federation) :Webpack 的模块联邦功能允许不同的前端应用共享 JavaScript 模块,同时保持应用间的隔离。它可以动态地加载另一个应用导出的模块,而不需要将它们打包进单个文件里。
  • 专门的微前端框架:如 Single-SPA、Qiankun 等,这些框架提供了一套完整的解决方案,用于管理微前端应用的加载、卸载以及相互隔离,部分内部采用了类似沙箱的技术实现隔离。

5. 服务端渲染(SSR)隔离:

  • 通过服务端渲染各个微前端应用,再将渲染好的静态 HTML 集成到主应用中。这样,每个子应用的 JavaScript 在客户端激活(Hydration)之前是隔离的。SSR 可以减少初次加载时间,同时具备部分隔离性,尤其是在初次加载阶段。

实施 JavaScript 隔离时,需要根据具体项目需求、技术栈和团队的熟练度来选取合适的隔离策略,以确保子应用之间的高度独立性和可维护性。

7. [微前端] Qiankun 是如何做 JS 隔离的

关键词:JS 隔离

Qiankun 是一个基于 Single-SPA 的微前端实现库,它提供了比较完善的 JS 隔离能力,确保微前端应用间的独立运行,避免了全局变量污染、样式冲突等问题。Qiankun 实现 JS 隔离的主要机制包括:

1. JS 沙箱

Qiankun 使用 JS 沙箱技术为每个子应用创建一个独立的运行环境。沙箱有以下两种类型:

  • 快照沙箱(Snapshot Sandbox) :在子应用启动时,快照并记录当前全局环境的状态,然后在子应用卸载时,恢复全局环境到启动前的状态。这种方式不会对全局对象进行真正的隔离,而是通过记录和恢复的方式避免全局环境被污染。
  • Proxy 沙箱:通过 Proxy 对象创建一个全新的全局对象代理,子应用的所有全局变量修改操作都将在这个代理对象上进行,从而不会影响到真实的全局对象。这种方式提供了更为彻底的隔离效果,是 Qiankun 中推荐的沙箱隔离方式。

2. 动态执行 JS 代码

Qiankun 通过动态执行 JS 代码的方式加载子应用,避免了脚本直接在全局环境下执行可能导致的变量污染。具体来说,它可以动态获取子应用的 JS 资源,然后在沙箱环境中运行这些代码,确保代码执行的全局变量不会泄露到主应用的全局环境中。

3. 生命周期隔离

Qiankun 给每个子应用定义了一套生命周期钩子,如 bootstrapmountunmount 等,确保在应用加载、激活和卸载的过程中正确管理和隔离资源。通过在 unmount 生命周期钩子中正确清理子应用创建的全局监听器、定时器等,进一步保证了不同子应用间的独立性和隔离性。

4. 样式隔离

虽然主要针对 JS 隔离,Qiankun 也提供了样式隔离机制,通过动态添加和移除样式标签,保证子应用样式的独立性,避免不同子应用间的样式冲突。

通过以上机制,Qiankun 能够有效实现微前端架构中子应用的 JS 隔离,加强了应用间的独立性和安全性,使得不同子应用可以无缝集成在一起,同时又能够保持各自的运行环境独立不受影响。

8. [微前端] 为何通常在 微前端 应用隔离, 不选择 iframe 方案

关键词:iframe 隔离方案弊端

在微前端架构中,虽然iframe能提供很好的应用隔离(包括 JavaScript 和 CSS 隔离),确保微前端应用之间不会相互干扰,但一般不把它作为首选方案,原因包括:

1. 性能开销

iframe会创建一个全新的浏览器上下文环境,每个iframe都有自己的文档对象模型(DOM)树、全局执行环境等。如果一个页面中嵌入了多个iframe,就会导致额外的内存和 CPU 资源消耗,特别是在性能有限的设备上更为显著。

2. 应用集成和交互问题

iframe自然隔离了父子页面的环境,这虽然提供了隔离,但同时也使得主应用与子应用之间的交云难度增加。虽然可以通过postMessage等 API 实现跨iframe通信,但这种方式相比于直接 JavaScript 调用来说,更为复杂,交互效率也较低。

3. UI 体验一致性

iframe中运行的应用在视觉上可能与主应用难以实现无缝集成。iframe内外的样式、字体等一致性需要额外的处理。此外,iframe可能带来额外的滚动条,影响用户体验。

4. SEO 问题

如果微前端的某些内容是通过iframe呈现的,那么这部分内容对于搜索引擎是不可见的,这可能会对应用的 SEO 产生负面影响。

5. 安全问题

虽然iframe可以提供一定程度的隔离,但它也可能引入点击劫持等安全风险。此外,过多地使用iframe也可能增加网站被恶意脚本攻击的表面。

因此,虽然iframe是一种可行的应用隔离方法,它的这些局限性使得开发者在选择微前端技术方案时,往往会考虑其他提供更轻量级隔离、更好集成与交互体验的方案,如使用 JavaScript 沙箱、CSS 隔离技术、Web Components 等。这些方法虽然隔离性可能不如iframe彻底,但在整体的应用性能、用户体验和开发效率上通常会有更好的表现。

9. iframe 和 micro app对比

相同点(微前端优点)

  1. 接入项目与使用技术栈无关

  2. 各个项目相互独立,独立开发,独立后端

  3. 增量式升级(可以一点点增加新的系统进入)

iframe优点

  1. 自带的样式、环境隔离机制使得它具备天然的沙盒机制(相互隔离)。
  2. 嵌入子应用比较简单

iframe缺点:

  1. iframe功能之间的跳转是无效的,刷新页面无法保存状态。

  2. URL的记录完全无效,刷新会返回首页。

  3. 主应用劫持快捷键操作,事件冒泡不穿透到主文档树上。

  4. 模态弹窗的背景是无法覆盖到整个应用。

  5. iframe应用加载失败,内容发生错误主应用无法感知,通信麻烦。 

  6. 同一个主域下不同子域之间的跨域请求, 比如a.com和1.a.com 之间,1.a.com和2.a.com 之间。使用document.domian都指向主域,比如document.domain="a.com"

micro-app优点

  1. micro-app 是借鉴了 WebComponent 的思想,将前端封装为一个webcomponent组件 使用shadowDom隔离,从而实现微前端的组件化渲染

  2. 它是目前接入微前端成本最低的框架,并且提供了JS沙箱、样式隔离、元素隔离、预加载、资源地址补全、插件系统、数据通信等一系列完善的功能

10. 如何清理源码里面没有被应用的代码, 主要是 JS、TS、CSS 代码

关键词:代码清理

1. 使用 ESLint

  • 初始化 ESLint:如果你还没有使用 ESLint,可以通过npx eslint --init命令来初始化配置。
  • 配置规则:确保在.eslintrc配置文件中启用了no-unused-vars规则,以识别未使用的变量和函数。
{
  "rules": {
    "no-unused-vars": "warn"
  }
}
  • 使用 ESLint 的 --fix 选项: 虽然 ESLint 主要用于识别问题,但它的 --fix 选项可以自动修复一些问题,包括删除未使用的变量等。不过,这种方式相对保守,无法删除大块的未使用代码

2. 使用 TypeScript 编译器选项

  • 对于 TypeScript 项目,可以在tsconfig.json文件中启用noUnusedLocalsnoUnusedParameters选项,以识别未使用的本地变量和函数参数。
{
  "compilerOptions": {
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

3. 利用 Webpack 的 Tree Shaking

  • 确保在生产模式下使用 Webpack,它自带 Tree Shaking 功能,可以去除死代码(未被使用的代码)。
  • 使用 ES6 模块语法(即importexport),因为 Tree Shaking 仅支持静态导入。

对于 CSS

1. 使用 PurgeCSS

  • PurgeCSS分析你的内容和 CSS 文件,去除不匹配的选择器。非常适用于清楚在 HTML 或 JS 文件中未引用的 CSS 代码。
  • 可以通过 Webpack、Gulp 或 PostCSS 等多种方式与 PurgeCSS 集成。

npm install purgecss

使用 PurgeCSS 时,配置你的内容文件路径(如 HTML 或 JSX 文件),它会扫描这些文件以确定哪些 CSS 选择器被使用:

// 一个基本的PurgeCSS配置例子
new PurgecssPlugin({
  paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
});

使用 Codemods

Codemods 是 Facebook 提出的一种工具,允许你对代码库进行大规模的自动化重构。通过编写特定的脚本,你可以自定义删除或修改未被调用的代码的逻辑。例如,使用 jscodeshift 工具可以配合具体规则进行代码修改。

注意事项

  • 测试:自动删除代码后,务必执行完整的测试套件,确认改动不会影响现有功能。
  • 版本控制:在进行删除操作之前,确保代码已经提交到版本控制系统,以便必要时可以恢复。
  • 逐步执行:尤其是在较大或复杂的项目中,建议分步骤、逐渐移除未使用的代码,每次删除后都进行测试和评估。

使用这些策略和工具可以帮助自动化清理未使用的代码,但是请注意,完全自动化的过程可能会有风险,依然需要人工审核和测试以确保代码的质量和应用的稳定性。

11. 一般是怎么做代码重构的

关键词:代码重构

在前端项目中进行代码重构,一般可以遵循以下步骤:

  1. 明确重构目标

    • 确定需要解决的问题,例如提高代码的可读性、可维护性、性能,或者去除重复代码等。
  2. 代码分析

    • 对现有代码进行全面的审查和理解,包括代码结构、逻辑流程、函数和模块之间的关系等。
    • 可以使用工具如 ESLint 检查代码风格和潜在问题,使用性能分析工具如 Chrome DevTools 的 Performance 面板来检测性能瓶颈。
  3. 制定重构计划

    • 根据分析结果,确定重构的步骤和顺序。
    • 将大型的重构任务分解为较小的、可管理的子任务。
  4. 重写代码结构

    • 对模块和组件进行合理的拆分和组织,使代码结构更加清晰。
    • 例如,将功能相关的代码提取到单独的函数或模块中,提高代码的内聚性和复用性。
  5. 优化函数和方法

    • 检查函数的长度和复杂性,对过长或过于复杂的函数进行分解。
    • 去除不必要的参数传递和全局变量的使用。
  6. 处理数据结构

    • 评估数据的存储和使用方式,选择更合适的数据结构(如从数组切换到对象,或者使用 Map、Set 等)来提高数据操作的效率。
  7. 优化性能

    • 例如,减少不必要的计算、优化 DOM 操作、合理使用缓存等。
  8. 测试和验证

    • 对重构后的代码进行全面的单元测试、集成测试和端到端测试,确保功能的正确性和稳定性。
  9. 代码审查

    • 邀请团队成员对重构后的代码进行审查,获取反馈和建议,进一步优化代码。
  10. 文档更新

    • 对重构后的代码功能、接口和使用方法进行文档更新,方便其他开发人员理解和使用。

以一个简单的前端项目为例,假设有一个处理用户数据展示的模块,最初的代码可能是所有功能都写在一个大型的函数中,并且数据存储在全局变量中。

重构时:

  • 将数据处理、数据获取和数据展示的功能分别提取到不同的函数中。
  • 将数据从全局变量改为使用模块内部的私有变量或通过参数传递。
  • 对数据处理函数进行优化,去除重复的代码逻辑。
  • 为新的函数和模块添加必要的注释和文档说明。

通过这样的重构过程,可以使前端项目的代码质量得到显著提升,为后续的开发和维护提供更好的基础。

12. 判断一个对象是否为空,包含了其原型链上是否有自定义数据或者方法。 该如何判定?

function isObjectEmpty(obj) {
  // 首先获取对象自身的属性
  const ownProperties = Object.getOwnPropertyNames(obj);

  // 遍历自身属性
  for (const property of ownProperties) {
    const descriptor = Object.getOwnPropertyDescriptor(obj, property);
    // 如果属性是数据属性并且有值,或者是方法(可调用函数),则对象不为空
    if (
      (descriptor.value && descriptor.value !== null && descriptor.value !== undefined) ||
      typeof descriptor.value === "function"
    ) {
      return false;
    }
  }

  // 获取对象的原型
  const prototype = Object.getPrototypeOf(obj);

  // 如果有原型并且原型不是 `Object.prototype`(避免误判普通对象的默认方法)
  while (prototype && prototype !== Object.prototype) {
    const prototypeProperties = Object.getOwnPropertyNames(prototype);

    // 遍历原型的属性
    for (const property of prototypeProperties) {
      const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
      // 如果原型上的属性是数据属性并且有值,或者是方法(可调用函数),则对象不为空
      if (
        (descriptor.value && descriptor.value !== null && descriptor.value !== undefined) ||
        typeof descriptor.value === "function"
      ) {
        return false;
      }
    }

    // 继续沿着原型链向上查找
    prototype = Object.getPrototypeOf(prototype);
  }

  // 如果以上检查都没有找到非空属性或方法,则对象为空
  return true;
}

13. css 实现打字机效果

关键词:animation 帧动画、animation steps 属性

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      .typewriter {
        width: 300px;
        border-right: 4px solid black;
        animation: typing 4s steps(30), blink 0.5s step-end infinite;
        white-space: nowrap;
        overflow: hidden;
      }

      @keyframes typing {
        from {
          width: 0;
        }
        to {
          width: 300px;
        }
      }

      @keyframes blink {
        50% {
          border-color: transparent;
        }
      }
    </style>
  </head>

  <body>
    <p class="typewriter">这是一个打字机效果的文本</p>
  </body>
</html>

在上述代码中,.typewriter 类的元素用于实现打字机效果。

animation: typing 4s steps(30), blink 0.5s step-end infinite; 定义了两个动画:

  • typing 动画用于模拟文字逐个出现的效果,从宽度为 0 逐渐增加到 300pxsteps(30) 表示分 30 步完成动画,使文字出现有逐个显示的效果。
  • blink 动画用于模拟光标闪烁效果,每 0.5s 闪烁一次,在 50% 进度时,光标(通过右边框实现)变为透明来模拟闪烁。

14.vue项目打包部署流程

vue中npm run build对项目打包,打包后会新生成一个dist文件,直接打开是空白的,需要放到服务器上才能运行。

部署方式有:本地服务器部署、nginx服务器部署、云服务器部署三种方式。

本文只总结自己能看懂的,完整版作者在下面

作者:晴小篆
链接:juejin.cn/post/739041…