2024.05.26 - 2024.07.12 更新前端面试问题总结(17 道题)
获取更多面试相关问题可以访问
github 地址: https://github.com/pro-collection/interview-question/issues
gitee 地址: https://gitee.com/yanleweb/interview-question/issues

目录:

  • 中级开发者相关问题【共计 9 道题】

    • 779.[React] 循环渲染中 为什么推荐不用 index 做 key【热度: 320】【web 框架】【出题公司: TOP100 互联网】
    • 784.前端应用 如何做国际化?【热度: 199】【web 应用场景】【出题公司: 美团】
    • 787.flex:1 代表什么【热度: 400】【CSS】【出题公司: 百度】
    • 788.请求失败会弹出一个 toast , 如何保证批量请求失败, 只弹出一个 toast【热度: 420】【web 应用场景】【出题公司: PDD】
    • 789.css 实现翻牌效果【热度: 116】【CSS】【出题公司: 快手】
    • 790.js 如何判空? 「空」包含了:空数组、空对象、空字符串、0、undefined、null、空 map、空 set , 都属于为空的数据【热度: 640】【JavaScript】【出题公司: PDD】
    • 792.css 实现打字机效果【热度: 96】【CSS】【出题公司: TOP100 互联网】
    • 793.dom 里面, 如何判定 a 元素是否是 b 元素的子元素【热度: 400】【web 应用场景】【出题公司: TOP100 互联网】
    • 794.前端如何实现折叠面板效果?【热度: 113】【web 应用场景】【出题公司: TOP100 互联网】
  • 高级开发者相关问题【共计 8 道题】

    • 778.[React] 如何避免使用 context 的时候, 引起整个挂载节点树的重新渲染【热度: 420】【web 框架】【出题公司: TOP100 互联网】
    • 780.[微前端] 微前端架构一般是如何做 JavaScript 隔离【热度: 127】【工程化】【出题公司: 阿里巴巴】
    • 781.[微前端] Qiankun 是如何做 JS 隔离的【热度: 228】【工程化】【出题公司: 阿里巴巴】
    • 782.[微前端] 为何通常在 微前端 应用隔离, 不选择 iframe 方案【热度: 280】【工程化】【出题公司: 阿里巴巴】
    • 783.应用如何做应用灰度发布【热度: 247】【工程化】【出题公司: 腾讯】
    • 785.如何清理源码里面没有被应用的代码, 主要是 JS、TS、CSS 代码【热度: 329】【web 应用场景】【出题公司: 腾讯】
    • 786.一般是怎么做代码重构的【热度: 191】【web 应用场景】【出题公司: PDD】
    • 791.判断一个对象是否为空,包含了其原型链上是否有自定义数据或者方法。 该如何判定?【热度: 546】【JavaScript】【出题公司: PDD】

779.[React] 循环渲染中 为什么推荐不用 index 做 key【热度: 320】【web 框架】【出题公司: TOP100 互联网】

关键词:React 循环渲染问题

在 React 的循环渲染中,不推荐使用数组的index(索引)作为元素的key,主要基于以下几点理由:

  1. 列表项顺序的变化:如果列表项的顺序会发生变化(如排序、增加、删除操作),使用index作为key可能会导致性能问题和组件状态的错误。这是因为 React 依赖key来判断哪些元素是新元素、哪些被移除,以及哪些元素的位置发生了变化。当使用index作为key时,即使数据项的内容改变了,key仍然保持不变,导致 React 无法正确识别和优化渲染。

  2. 性能问题:当列表项发生变动时,如果使用index作为key,React 可能会做更多的 DOM 操作来更新视图,因为它无法准确地通过key识别哪些元素是新的,哪些元素被移动了位置。这可能导致不必要的重渲染和性能下降。

  3. 组件状态的问题:对于使用 state 的组件,如果列表项的顺序改变,使用index作为key可能会导致状态错乱。例如,当你删除一个列表项时,后面的项会移上来,它们的index改变了,如果它们有独立的状态,这时会由于index作为key使得状态与视图匹配错误。

因此,推荐的做法是使用唯一的、稳定的标识符(如数据库中的 id 或者具有唯一性的 hash 值等)作为key,这样无论数据如何变化,每个元素的key都是稳定的,可以帮助 React 更准确、更高效地进行 DOM 的比对和更新。

784.前端应用 如何做国际化?【热度: 199】【web 应用场景】【出题公司: 美团】

关键词:国际化

前端应用实现国际化(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属性来自动选择最合适的语言版本。

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

787.flex:1 代表什么【热度: 400】【CSS】【出题公司: 百度】

关键词: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,内容区则会自动放大占满剩余空间。

以下是一个简单的示例代码,展示了flex: 1的效果:

 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <style>
 5      .container {
 6        display: flex;
 7        width: 300px;
 8        height: 200px;
 9        background-color: lightblue;
10      }
11
12      .item1 {
13        background-color: lightcoral;
14        flex: 1;
15      }
16
17      .item2 {
18        background-color: lightgreen;
19        flex: 1;
20      }
21    </style>
22  </head>
23
24  <body>
25    <div class="container">
26      <div class="item1">item1</div>
27      <div class="item2">item2</div>
28    </div>
29  </body>
30</html>

在上述代码中,.container是一个 flex 容器,它包含两个子元素.item1.item2,并且都将flex属性设置为1。当调整.container的宽度时,.item1.item2会等比例地增大或缩小,以占满剩余空间。

788.请求失败会弹出一个 toast , 如何保证批量请求失败, 只弹出一个 toast【热度: 420】【web 应用场景】【出题公司: PDD】

关键词:单例 toast

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

  1. 设置全局标志位:定义一个全局变量(如 isToastShown)来表示是否已经弹出过 toast。在请求失败的处理逻辑中,首先检查该标志位。如果尚未弹出 toast,则进行弹出操作,并设置标志位为 true;如果标志位已经为 true,则直接忽略后续的弹出操作。
  2. 使用防抖或节流函数:防抖(debounce)或节流(throttle)函数可以限制某个函数在一定时间内的执行次数。将弹出 toast 的操作封装在防抖或节流函数中,确保在短时间内的多个请求失败时,不会频繁弹出 toast。
  3. 集中处理错误:将所有请求的错误集中处理,而不是在每个请求的 catch 块中直接弹出 toast。例如,把所有请求的 Promise 添加到一个数组中,然后使用 Promise.all() 或其他类似方法来统一处理这些 Promise 的结果。如果所有请求都失败了,再弹出一个 toast。

以下是使用全局标志位和集中处理错误的示例代码:

 1let isToastShown = false; // 全局标志位
 2
 3function makeRequests() {
 4  const requests = [fetchPost(), fetchComments()]; // 多个请求的 Promise
 5
 6  Promise.all(requests)
 7  .then(() => {
 8      // 所有请求成功的处理逻辑
 9    })
10  .catch(errors => {
11      if (!isToastShown) { // 检查标志位
12        notify(errors[0]); // 弹出 toast
13        isToastShown = true; // 设置标志位为 true
14      }
15    });
16}
17
18function fetchJSON(url, input) {
19  return fetch(url, input)
20  .then(res => {
21      if (res.ok) {
22        return res.json();
23      }
24      const err = new HttpError(res);
25      if (!isToastShown) { // 检查标志位
26        notify(err); // 弹出 toast
27        is toastShown = true; // 设置标志位为 true
28      }
29      throw err;
30    });
31}

在上述代码中,定义了一个全局变量 isToastShown 来标记是否已经弹出过 toast。在 fetchJSON 函数中,当请求失败时,如果 isToastShownfalse,则弹出 toast 并设置其为 true。在 makeRequests 函数中,使用 Promise.all 来处理多个请求。如果所有请求都失败(即 errors 数组有内容),并且 isToastShownfalse,则弹出 toast 并设置标志位。

这样,无论有多少个请求失败,都只会弹出一个 toast。当有新的批量请求时,记得在请求开始前将 isToastShown 重置为 false

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

789.css 实现翻牌效果【热度: 116】【CSS】【出题公司: 快手】

关键词:css 动效应用

主要是考察几个属性的使用

  • transform: rotateY 用于 Y 轴旋转
  • transition 用于过度动画

还有一个要点:

  • 翻转卡牌的时候,正面在上, 要将背面隐藏; 背面在上, 要将正面隐藏;

效果如下:

实现比较简单, 直接贴代码

 1<!DOCTYPE html>
 2<html lang="en">
 3  <head>
 4    <style>
 5      .card {
 6        display: flex;
 7      }
 8
 9      .flip-card {
10        float: left;
11        position: relative;
12        height: 36vmin;
13        width: calc(40vmin / 1.4);
14        background-color: white;
15        padding: 20px;
16        border-radius: calc(40vmin / 20);
17        box-shadow: 0 calc(40vmin / 40) calc(40vmin / 10) 0 rgba(0, 0, 0, 0.6);
18        overflow: hidden;
19        transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
20        transform: rotateY(0deg);
21      }
22
23      .label:hover .flip-card {
24        transform: rotateY(180deg);
25        background-color: black;
26        transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
27      }
28
29      .label:hover .flip-front {
30        opacity: 0;
31        display: none;
32        transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
33      }
34
35      .label:hover .flip-end {
36        opacity: 1;
37        display: block;
38        transform: rotateY(180deg);
39        color: white;
40        font-size: 20px;
41        transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
42      }
43
44      .flip-front {
45        width: 100%;
46        height: 100%;
47        opacity: 1;
48        cursor: pointer;
49      }
50
51      .flip-end {
52        width: 100%;
53        height: 100%;
54        opacity: 0;
55        display: none;
56        cursor: pointer;
57      }
58
59      .label {
60        background-color: white;
61        border-radius: calc(40vmin / 20);
62      }
63    </style>
64  </head>
65  <body>
66    <div class="card">
67      <div class="label">
68        <div class="flip-card">
69          <div class="flip-front">我是正面</div>
70          <div class="flip-end">
71            在上述代码中,我们创建了一个带有 card 类的容器,内部有一个 card-inner 元素,它包含了 card-front(正面)和
72            card-back(背面)两个元素。 当鼠标悬停在 card 元素上时,通过 :hover 选择器将 card-inner 元素绕 Y 轴旋转 180
73            度,实现翻牌效果。
74          </div>
75        </div>
76      </div>
77    </div>
78  </body>
79</html>

790.js 如何判空? 「空」包含了:空数组、空对象、空字符串、0、undefined、null、空 map、空 set , 都属于为空的数据【热度: 640】【JavaScript】【出题公司: PDD】

关键词:判断

以下是一个 JavaScript 方法,用于校验您提到的各种“为空”的场景:

 1function isEmpty(value) {
 2  // 空字符串
 3  if (typeof value === "string" && value.trim() === "") {
 4    return true;
 5  }
 6  // 空数组
 7  if (Array.isArray(value) && value.length === 0) {
 8    return true;
 9  }
10  // 空对象(不包括 `null`)
11  if (typeof value === "object" && value !== null && Object.keys(value).length === 0) {
12    return true;
13  }
14  // 数字 0
15  if (typeof value === "number" && value === 0) {
16    return true;
17  }
18  // `undefined`
19  if (typeof value === "undefined") {
20    return true;
21  }
22  // `null`
23  if (value === null) {
24    return true;
25  }
26  // 空 `Map`
27  if (value instanceof Map && value.size === 0) {
28    return true;
29  }
30  // 空 `Set`
31  if (value instanceof Set && value.size === 0) {
32    return true;
33  }
34
35  return false;
36}

您可以使用这个方法来检测各种值是否为空,例如:

 1const emptyStr = "";
 2const emptyArr = [];
 3const emptyObj = {};
 4const zero = 0;
 5const undef = undefined;
 6const nullVal = null;
 7const emptyMap = new Map();
 8const emptySet = new Set();
 9
10console.log(isEmpty(emptyStr));
11console.log(isEmpty(emptyArr));
12console.log(isEmpty(emptyObj));
13console.log(isEmpty(zero));
14console.log(isEmpty(undef));
15console.log(isEmpty(nullVal));
16console.log(isEmpty(emptyMap));
17console.log(isEmpty(emptySet));

792.css 实现打字机效果【热度: 96】【CSS】【出题公司: TOP100 互联网】

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

主要是对 css 动画的一个实际应用考察

以下是一个使用 CSS 实现简单打字机效果的示例代码:

 1<!DOCTYPE html>
 2<html lang="en">
 3  <head>
 4    <style>
 5      .typewriter {
 6        width: 300px;
 7        border-right: 4px solid black;
 8        animation: typing 4s steps(30), blink 0.5s step-end infinite;
 9        white-space: nowrap;
10        overflow: hidden;
11      }
12
13      @keyframes typing {
14        from {
15          width: 0;
16        }
17        to {
18          width: 300px;
19        }
20      }
21
22      @keyframes blink {
23        50% {
24          border-color: transparent;
25        }
26      }
27    </style>
28  </head>
29
30  <body>
31    <p class="typewriter">这是一个打字机效果的文本</p>
32  </body>
33</html>

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

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

  • typing 动画用于模拟文字逐个出现的效果,从宽度为 0 逐渐增加到 300pxsteps(30) 表示分 30 步完成动画,使文字出现有逐个显示的效果。

  • blink 动画用于模拟光标闪烁效果,每 0.5s 闪烁一次,在 50% 进度时,光标(通过右边框实现)变为透明来模拟闪烁。

793.dom 里面, 如何判定 a 元素是否是 b 元素的子元素【热度: 400】【web 应用场景】【出题公司: TOP100 互联网】

关键词:dom.contains 方法

在 DOM(文档对象模型)中,要判断元素 a 是否是元素 b 的子元素,您可以使用以下的 JavaScript 代码:

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

可以这样使用上述函数:

 1const elementA = document.getElementById("elementA");
 2const elementB = document.getElementById("elementB");
 3
 4if (isChildElement(elementA, elementB)) {
 5  console.log("元素 A 是元素 B 的子元素");
 6} else {
 7  console.log("元素 A 不是元素 B 的子元素");
 8}

794.前端如何实现折叠面板效果?【热度: 113】【web 应用场景】【出题公司: TOP100 互联网】

关键词:transition 过度动画

以下是在您给出的折叠面板示例基础上添加动画效果的版本:

HTML 结构(与您之前给出的相同)

 1<div class="accordion">
 2  <div class="accordion-item">
 3    <div class="accordion-header">标题 1</div>
 4    <div class="accordion-content">
 5      <p>这是内容 1</p>
 6    </div>
 7  </div>
 8  <div class="accordion-item">
 9    <div class="accordion-header">标题 2</div>
10    <div class="accordion-content">
11      <p>这是内容 2</p>
12    </div>
13  </div>
14  <!-- 可以添加更多的折叠项 -->
15</div>

CSS 样式

 1.accordion {
 2  width: 300px;
 3}
 4
 5.accordion-item {
 6  border: 1px solid #ccc;
 7  margin-bottom: 5px;
 8}
 9
10.accordion-header {
11  background-color: #f1f1f1;
12  padding: 10px;
13  cursor: pointer;
14}
15
16.accordion-content {
17  padding: 10px;
18  overflow: hidden;
19  max-height: 0;
20  transition: max-height 0.3s ease;
21}
22
23.accordion-item.open.accordion-content {
24  max-height: 500px; /* 您可以根据内容的实际高度进行调整 */
25}

JavaScript 功能

 1const accordionHeaders = document.querySelectorAll(".accordion-header");
 2
 3accordionHeaders.forEach((header) => {
 4  header.addEventListener("click", () => {
 5    const item = header.parentElement;
 6    item.classList.toggle("open");
 7  });
 8});

这样,当点击折叠面板的标题时,内容会以平滑的动画效果展开或收起。您可以根据实际需求调整动画的时长和内容展开的最大高度等参数。

778.[React] 如何避免使用 context 的时候, 引起整个挂载节点树的重新渲染【热度: 420】【web 框架】【出题公司: TOP100 互联网】

关键词:React Context 渲染问题

要避免在 React 开发中使用 context 时引起整个挂载节点树的重新渲染,可以采取以下方法:

  1. React Context 数据分割:把提供 context value 的部分提取到单独的组件中,并且仅在该组件中修改 context value。这样,当 context value 变化时,只有真正使用该 context 的消费组件会重新渲染,而非所有挂载节点都会重新渲染。

假设我们有一个应用,需要管理主题颜色和用户信息两个不同的数据。

首先,创建两个 Context:

 1import React from "eact";
 2
 3// 创建主题颜色 Context
 4const ThemeContext = React.createContext({ theme: "light" });
 5
 6// 创建用户信息 Context
 7const UserContext = React.createContext({ user: null });

在顶层组件中,提供这两个 Context 的 Provider,并设置相应的值:

 1class App extends React.Component {
 2  state = {
 3    theme: "dark",
 4    user: { name: "John Doe", age: 25 },
 5  };
 6
 7  render() {
 8    return (
 9      <ThemeContext.Provider value={this.state.theme}>
10        <UserContext.Provider value={this.state.user}>
11          <Toolbar />
12        </UserContext.Provider>
13      </ThemeContext.Provider>
14    );
15  }
16}

然后,在需要使用主题颜色的组件中,可以通过以下方式获取:

 1class ThemedButton extends React.Component {
 2  static contextType = ThemeContext;
 3
 4  render() {
 5    const theme = this.context;
 6    return <Button theme={theme} />;
 7  }
 8}

在需要使用用户信息的组件中,同样方式获取:

 1class UserProfile extends React.Component {
 2  static contextType = UserContext;
 3
 4  render() {
 5    const user = this.context;
 6    return (
 7      <div>
 8        <p>用户名:{user.name}</p>
 9        <p>年龄:{user.age}</p>
10      </div>
11    );
12  }
13}

在上述例子中,我们将主题颜色和用户信息分割到不同的 Context 中。ThemeContext 用于传递主题相关的数据,UserContext 用于传递用户相关的数据。这样,不同的组件可以根据自己的需求订阅相应的 Context,获取所需的数据,而不会相互干扰。每个组件只需要关注自己所使用的 Context,提高了代码的可读性和可维护性。同时,当某个 Context 的数据发生变化时,只有订阅了该 Context 的组件才会重新渲染,避免了不必要的重新渲染。

  1. 对消费组件使用 React.memo() 进行包裹:React.memo 可以对函数组件进行浅比较,如果组件的 props 没有变化,就不会触发重新渲染。通过将消费 context 的组件用 React.memo() 包裹,可以避免不必要的重新渲染。

例如,假设有一个 ContextProvider 组件提供 context value,以及一个使用该 context 的子组件 ConsumerComponent,优化后的代码可能如下所示:

 1const ContextProvider = ({ children }) => {
 2  // 管理 context value 的状态
 3  const [value, setValue] = useState(/* 初始值 */);
 4
 5  return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
 6};
 7
 8const ConsumerComponent = React.memo(({ contextValue }) => {
 9  // 仅根据 context value 进行渲染或处理逻辑
10  return <div>{/* 使用 context value 的相关逻辑 */}</div>;
11});

在上述示例中,ContextProvider 负责管理 context value 的状态变化,而 ConsumerComponent 是使用 context 的消费组件,并通过 React.memo() 进行了包裹。这样,当 value 发生变化时,只有 ConsumerComponent 会根据浅比较来决定是否重新渲染,而不是整个挂载节点树都重新渲染。

通过以上方式,可以减少使用 context 时不必要的重新渲染,提高应用的性能。但具体的优化策略还需要根据项目的实际情况进行选择和调整。同时,还需注意避免在 context 中传递过于复杂或频繁变化的数据,以减少不必要的渲染次数。

780.[微前端] 微前端架构一般是如何做 JavaScript 隔离【热度: 127】【工程化】【出题公司: 阿里巴巴】

关键词:JS 隔离

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

  • iframe:最直接的隔离方式是将子应用运行在iframe中。这种方式提供了良好的隔离性,因为iframe内部有自己独立的执行环境,包括 JavaScript 运行环境和 DOM 环境。但iframe的使用可能会导致性能问题,且父子通信复杂。

  • JavaScript Sandboxing:通过创建一个独立的 JavaScript 执行环境,比如使用 Web Workers,或者更高级的沙箱库(如 Google 的 Caja),以在主页环境隔离执行 JavaScript 代码。

  • 命名空间:通过命名空间(Namespace)封装每个子应用的代码,确保全局变量和函数不会与其他应用冲突。

  • 模块化:利用 ES Modules 或 CommonJS 等模块化标准,使代码封装在模块中运行,通过 import/export 管理依赖,减少全局变量的使用,从而实现隔离。

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

  • 模块联邦(Module Federation):Webpack 的模块联邦功能允许不同的前端应用共享 JavaScript 模块,同时保持应用间的隔离。它可以动态地加载另一个应用导出的模块,而不需要将它们打包进单个文件里。

  • 专门的微前端框架:如 Single-SPA、Qiankun 等,这些框架提供了一套完整的解决方案,用于管理微前端应用的加载、卸载以及相互隔离,部分内部采用了类似沙箱的技术实现隔离。

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

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

781.[微前端] Qiankun 是如何做 JS 隔离的【热度: 228】【工程化】【出题公司: 阿里巴巴】

关键词:JS 隔离

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

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

  • 快照沙箱(Snapshot Sandbox):在子应用启动时,快照并记录当前全局环境的状态,然后在子应用卸载时,恢复全局环境到启动前的状态。这种方式不会对全局对象进行真正的隔离,而是通过记录和恢复的方式避免全局环境被污染。

  • Proxy 沙箱:通过 Proxy 对象创建一个全新的全局对象代理,子应用的所有全局变量修改操作都将在这个代理对象上进行,从而不会影响到真实的全局对象。这种方式提供了更为彻底的隔离效果,是 Qiankun 中推荐的沙箱隔离方式。

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

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

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

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

782.[微前端] 为何通常在 微前端 应用隔离, 不选择 iframe 方案【热度: 280】【工程化】【出题公司: 阿里巴巴】

关键词:iframe 隔离方案弊端

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

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

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

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

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

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

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

783.应用如何做应用灰度发布【热度: 247】【工程化】【出题公司: 腾讯】

关键词:灰度发布

应用的灰度发布是将新版本逐步推出给有限的用户群体,以在完全发布之前监控其性能和搜集用户反馈的过程。这可以确保新版本的稳健性,减少因新版本可能引起的问题对所有用户的影响。以下是实现应用灰度发布的几种常见方法:

通过识别用户的 HTTP 请求头(如 User-Agent)或特定的 Cookie,决定用户请求被路由到新版本还是旧版本的应用。这种方法通常需要负载均衡器或网关支持特定路由规则。

服务网格如 Istio 提供了复杂的流量管理能力,可以在微服务架构中实现灰度发布。通过定义路由规则,Istio 可以将特定比例或特定条件的流量导向新版本服务。

功能开关允许开发者在代码中嵌入开关,根据配置动态激活或关闭某些功能。这样,新版本的功能可以被隐藏,直到你决定通过更改配置为特定用户群体开放。

通过 DNS 管理,将部分用户的请求解析到部署了新版本应用的服务器上。这种方法简单,但切换和回退可能不如其他方法灵活。

对于前端应用或静态资源,可以通过 CDN 配置,将部分用户的请求路由到包含新版本资源的 CDN 上。通过调整 CDN 的缓存规则控制版本切换。

将灰度发布作为 A/B 测试的一部分,使用专门的 A/B 测试平台来控制哪些用户看到新版本。这种方法不仅可以实现灰度发布,还能搜集用户反馈和使用情况数据。

在支持容器编排(如 Kubernetes)的环境中,可以通过部署新版本的 Pod 副本,并逐步增加新版本副本的数量,同时减少旧版本副本的数量实现灰度发布。

在实施灰度发布时,应该配合监控和日志记录工具,以便快速识别并解决新版本可能引入的问题。同时,在决定完全推出新版本之前,逐渐增加访问新版本的用户比例,确保在所有阶段都能够保持应用的稳定性和高性能。

785.如何清理源码里面没有被应用的代码, 主要是 JS、TS、CSS 代码【热度: 329】【web 应用场景】【出题公司: 腾讯】

关键词:代码清理

清理源码中未被应用的 JavaScript (JS)、TypeScript (TS) 和 CSS 代码的关键在于合理利用工具和策略,来识别和移除这些废弃的代码。下面是一份指南,帮助你高效完成这一任务:

对于 JavaScript 和 TypeScript

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

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

 1{
 2  "compilerOptions": {
 3    "noUnusedLocals": true,
 4    "noUnusedParameters": true
 5  }
 6}

3. 利用 Webpack 的 Tree Shaking

  • 确保在生产模式下使用 Webpack,它自带 Tree Shaking 功能,可以去除死代码(未被使用的代码)。

  • 使用 ES6 模块语法(即importexport),因为 Tree Shaking 仅支持静态导入。

  • PurgeCSS分析你的内容和 CSS 文件,去除不匹配的选择器。非常适用于清楚在 HTML 或 JS 文件中未引用的 CSS 代码。

  • 可以通过 Webpack、Gulp 或 PostCSS 等多种方式与 PurgeCSS 集成。

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

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

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

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

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

786.一般是怎么做代码重构的【热度: 191】【web 应用场景】【出题公司: PDD】

关键词:代码重构

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

  1. 明确重构目标

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

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

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

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

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

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

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

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

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

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

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

重构时:

  • 将数据处理、数据获取和数据展示的功能分别提取到不同的函数中。

  • 将数据从全局变量改为使用模块内部的私有变量或通过参数传递。

  • 对数据处理函数进行优化,去除重复的代码逻辑。

  • 为新的函数和模块添加必要的注释和文档说明。

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

791.判断一个对象是否为空,包含了其原型链上是否有自定义数据或者方法。 该如何判定?【热度: 546】【JavaScript】【出题公司: PDD】

关键词:原型链相关知识

要判断一个对象是否为空,并且考虑到对象自身及其原型链上是否有自定义数据或方法,您可以使用以下的 JavaScript 函数来实现:

 1function isObjectEmpty(obj) {
 2  // 首先获取对象自身的属性
 3  const ownProperties = Object.getOwnPropertyNames(obj);
 4
 5  // 遍历自身属性
 6  for (const property of ownProperties) {
 7    const descriptor = Object.getOwnPropertyDescriptor(obj, property);
 8    // 如果属性是数据属性并且有值,或者是方法(可调用函数),则对象不为空
 9    if (
10      (descriptor.value && descriptor.value !== null && descriptor.value !== undefined) ||
11      typeof descriptor.value === "function"
12    ) {
13      return false;
14    }
15  }
16
17  // 获取对象的原型
18  const prototype = Object.getPrototypeOf(obj);
19
20  // 如果有原型并且原型不是 `Object.prototype`(避免误判普通对象的默认方法)
21  while (prototype && prototype !== Object.prototype) {
22    const prototypeProperties = Object.getOwnPropertyNames(prototype);
23
24    // 遍历原型的属性
25    for (const property of prototypeProperties) {
26      const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
27      // 如果原型上的属性是数据属性并且有值,或者是方法(可调用函数),则对象不为空
28      if (
29        (descriptor.value && descriptor.value !== null && descriptor.value !== undefined) ||
30        typeof descriptor.value === "function"
31      ) {
32        return false;
33      }
34    }
35
36    // 继续沿着原型链向上查找
37    prototype = Object.getPrototypeOf(prototype);
38  }
39
40  // 如果以上检查都没有找到非空属性或方法,则对象为空
41  return true;
42}

可以使用这个函数来判断对象是否为空,例如:

 1function MyClass() {}
 2
 3MyClass.prototype.myMethod = function () {};
 4
 5const instance = new MyClass();
 6
 7console.log(isObjectEmpty(instance));
个人笔记记录 2021 ~ 2025