2024.05.26 - 2024.07.12 更新前端面试问题总结(17 道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect...
gitee 地址: gitee.com/yanleweb/in...
目录:
-
中级开发者相关问题【共计 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】
中级开发者相关问题【共计 9 道题】
779.[React] 循环渲染中 为什么推荐不用 index 做 key【热度: 320】【web 框架】【出题公司: TOP100 互联网】
关键词:React 循环渲染问题
在 React 的循环渲染中,不推荐使用数组的index
(索引)作为元素的key
,主要基于以下几点理由:
-
列表项顺序的变化 :如果列表项的顺序会发生变化(如排序、增加、删除操作),使用
index
作为key
可能会导致性能问题和组件状态的错误。这是因为 React 依赖key
来判断哪些元素是新元素、哪些被移除,以及哪些元素的位置发生了变化。当使用index
作为key
时,即使数据项的内容改变了,key
仍然保持不变,导致 React 无法正确识别和优化渲染。 -
性能问题 :当列表项发生变动时,如果使用
index
作为key
,React 可能会做更多的 DOM 操作来更新视图,因为它无法准确地通过key
识别哪些元素是新的,哪些元素被移动了位置。这可能导致不必要的重渲染和性能下降。 -
组件状态的问题 :对于使用 state 的组件,如果列表项的顺序改变,使用
index
作为key
可能会导致状态错乱。例如,当你删除一个列表项时,后面的项会移上来,它们的index
改变了,如果它们有独立的状态,这时会由于index
作为key
使得状态与视图匹配错误。
因此,推荐的做法是使用唯一的、稳定的标识符(如数据库中的 id 或者具有唯一性的 hash 值等)作为key
,这样无论数据如何变化,每个元素的key
都是稳定的,可以帮助 React 更准确、更高效地进行 DOM 的比对和更新。
784.前端应用 如何做国际化?【热度: 199】【web 应用场景】【出题公司: 美团】
关键词:国际化
前端应用实现国际化(i18n)主要是为了支持多语言环境,提高用户体验。这里有几种常用的方案:
-
使用国际化库:这是最常用的方法之一,可以通过引用第三方库来管理不同语言环境的资源文件。比如:
- React :可以使用
react-intl
或react-i18next
。 - Vue :可以使用
vue-i18n
。 - Angular :可以使用
@ngx-translate/core
。
这些库允许你将文本资源分开管理,并根据用户的语言偏好动态加载相应的资源。
- React :可以使用
-
浏览器 API :利用浏览器内置的国际化 API,如
Intl
对象,来格式化日期、时间、货币等。 -
自建国际化框架:根据项目的具体需求,自定义国际化实现。这通常包括:
- 创建资源文件:为每种语言创建一个资源文件,用于存储翻译字符串。
- 语言选择功能:允许用户选择偏好的语言。
- 加载对应资源文件:根据用户的语言偏好,动态加载对应的资源文件并在界面上显示相应的文本。
-
服务端支持:有些情况下,前端应用可能需要服务端的支持来实现国际化,如动态提供不同语言的数据内容。
-
URL 路由 :在 URL 中包含语言参数,来确定显示哪种语言的内容。例如,
/en/about
显示英文版"关于"页面,而/zh/about
显示中文版。 -
浏览器语言检测 :通过检测浏览器的
navigator.language
属性来自动选择最合适的语言版本。
在实际应用中,根据项目的大小、复杂度以及特定需求,可以选择一种或多种方案结合使用,以达到最佳的国际化效果。
787.flex:1 代表什么【热度: 400】【CSS】【出题公司: 百度】
关键词:flex 布局相关属性问题
在 CSS 的弹性盒模型(Flexbox)中,flex: 1
表示子项(flex 子项)的伸缩性。
具体来说,flex: 1
是flex-grow
、flex-shrink
和flex-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
的效果:
html
<!DOCTYPE html>
<html>
<head>
<style>
.container {
display: flex;
width: 300px;
height: 200px;
background-color: lightblue;
}
.item1 {
background-color: lightcoral;
flex: 1;
}
.item2 {
background-color: lightgreen;
flex: 1;
}
</style>
</head>
<body>
<div class="container">
<div class="item1">item1</div>
<div class="item2">item2</div>
</div>
</body>
</html>
在上述代码中,.container
是一个 flex 容器,它包含两个子元素.item1
和.item2
,并且都将flex
属性设置为1
。当调整.container
的宽度时,.item1
和.item2
会等比例地增大或缩小,以占满剩余空间。
788.请求失败会弹出一个 toast , 如何保证批量请求失败, 只弹出一个 toast【热度: 420】【web 应用场景】【出题公司: PDD】
关键词:单例 toast
要确保批量请求失败时只弹出一个 toast,可以通过以下几种方式实现:
- 设置全局标志位:定义一个全局变量(如
isToastShown
)来表示是否已经弹出过 toast。在请求失败的处理逻辑中,首先检查该标志位。如果尚未弹出 toast,则进行弹出操作,并设置标志位为true
;如果标志位已经为true
,则直接忽略后续的弹出操作。 - 使用防抖或节流函数:防抖(debounce)或节流(throttle)函数可以限制某个函数在一定时间内的执行次数。将弹出 toast 的操作封装在防抖或节流函数中,确保在短时间内的多个请求失败时,不会频繁弹出 toast。
- 集中处理错误:将所有请求的错误集中处理,而不是在每个请求的 catch 块中直接弹出 toast。例如,把所有请求的 Promise 添加到一个数组中,然后使用
Promise.all()
或其他类似方法来统一处理这些 Promise 的结果。如果所有请求都失败了,再弹出一个 toast。
以下是使用全局标志位和集中处理错误的示例代码:
javascript
let isToastShown = false; // 全局标志位
function makeRequests() {
const requests = [fetchPost(), fetchComments()]; // 多个请求的 Promise
Promise.all(requests)
.then(() => {
// 所有请求成功的处理逻辑
})
.catch(errors => {
if (!isToastShown) { // 检查标志位
notify(errors[0]); // 弹出 toast
isToastShown = true; // 设置标志位为 true
}
});
}
function fetchJSON(url, input) {
return fetch(url, input)
.then(res => {
if (res.ok) {
return res.json();
}
const err = new HttpError(res);
if (!isToastShown) { // 检查标志位
notify(err); // 弹出 toast
is toastShown = true; // 设置标志位为 true
}
throw err;
});
}
在上述代码中,定义了一个全局变量 isToastShown
来标记是否已经弹出过 toast。在 fetchJSON
函数中,当请求失败时,如果 isToastShown
为 false
,则弹出 toast 并设置其为 true
。在 makeRequests
函数中,使用 Promise.all
来处理多个请求。如果所有请求都失败(即 errors
数组有内容),并且 isToastShown
为 false
,则弹出 toast 并设置标志位。
这样,无论有多少个请求失败,都只会弹出一个 toast。当有新的批量请求时,记得在请求开始前将 isToastShown
重置为 false
。
另外,如果使用的是一些前端框架或库,它们可能提供了更方便的方式来处理这种情况。例如,在 Vue.js 中,可以使用 Vuex 来管理全局状态,实现类似的功能。具体的实现方式可能会因项目的架构和使用的技术而有所不同,但基本思路是相似的。
789.css 实现翻牌效果【热度: 116】【CSS】【出题公司: 快手】
关键词:css 动效应用
主要是考察几个属性的使用
transform: rotateY
用于 Y 轴旋转transition
用于过度动画
还有一个要点:
- 翻转卡牌的时候,正面在上, 要将背面隐藏; 背面在上, 要将正面隐藏;
效果如下:
实现比较简单, 直接贴代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.card {
display: flex;
}
.flip-card {
float: left;
position: relative;
height: 36vmin;
width: calc(40vmin / 1.4);
background-color: white;
padding: 20px;
border-radius: calc(40vmin / 20);
box-shadow: 0 calc(40vmin / 40) calc(40vmin / 10) 0 rgba(0, 0, 0, 0.6);
overflow: hidden;
transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
transform: rotateY(0deg);
}
.label:hover .flip-card {
transform: rotateY(180deg);
background-color: black;
transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
}
.label:hover .flip-front {
opacity: 0;
display: none;
transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
}
.label:hover .flip-end {
opacity: 1;
display: block;
transform: rotateY(180deg);
color: white;
font-size: 20px;
transition: transform 200ms linear, box-shadow 200ms linear, background-color 200ms linear;
}
.flip-front {
width: 100%;
height: 100%;
opacity: 1;
cursor: pointer;
}
.flip-end {
width: 100%;
height: 100%;
opacity: 0;
display: none;
cursor: pointer;
}
.label {
background-color: white;
border-radius: calc(40vmin / 20);
}
</style>
</head>
<body>
<div class="card">
<div class="label">
<div class="flip-card">
<div class="flip-front">我是正面</div>
<div class="flip-end">
在上述代码中,我们创建了一个带有 card 类的容器,内部有一个 card-inner 元素,它包含了 card-front(正面)和
card-back(背面)两个元素。 当鼠标悬停在 card 元素上时,通过 :hover 选择器将 card-inner 元素绕 Y 轴旋转 180
度,实现翻牌效果。
</div>
</div>
</div>
</div>
</body>
</html>
790.js 如何判空? 「空」包含了:空数组、空对象、空字符串、0、undefined、null、空 map、空 set , 都属于为空的数据【热度: 640】【JavaScript】【出题公司: PDD】
关键词:判断
以下是一个 JavaScript 方法,用于校验您提到的各种"为空"的场景:
javascript
function isEmpty(value) {
// 空字符串
if (typeof value === "string" && 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;
}
return false;
}
您可以使用这个方法来检测各种值是否为空,例如:
javascript
const emptyStr = "";
const emptyArr = [];
const emptyObj = {};
const zero = 0;
const undef = undefined;
const nullVal = null;
const emptyMap = new Map();
const emptySet = new Set();
console.log(isEmpty(emptyStr));
console.log(isEmpty(emptyArr));
console.log(isEmpty(emptyObj));
console.log(isEmpty(zero));
console.log(isEmpty(undef));
console.log(isEmpty(nullVal));
console.log(isEmpty(emptyMap));
console.log(isEmpty(emptySet));
792.css 实现打字机效果【热度: 96】【CSS】【出题公司: TOP100 互联网】
关键词:animation 帧动画、animation steps 属性
主要是对 css 动画的一个实际应用考察
以下是一个使用 CSS 实现简单打字机效果的示例代码:
html
<!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
逐渐增加到300px
,steps(30)
表示分 30 步完成动画,使文字出现有逐个显示的效果。 -
blink
动画用于模拟光标闪烁效果,每0.5s
闪烁一次,在50%
进度时,光标(通过右边框实现)变为透明来模拟闪烁。
793.dom 里面, 如何判定 a 元素是否是 b 元素的子元素【热度: 400】【web 应用场景】【出题公司: TOP100 互联网】
关键词:dom.contains 方法
在 DOM(文档对象模型)中,要判断元素 a
是否是元素 b
的子元素,您可以使用以下的 JavaScript 代码:
javascript
function isChildElement(a, b) {
return b.contains(a);
}
可以这样使用上述函数:
javascript
const elementA = document.getElementById("elementA");
const elementB = document.getElementById("elementB");
if (isChildElement(elementA, elementB)) {
console.log("元素 A 是元素 B 的子元素");
} else {
console.log("元素 A 不是元素 B 的子元素");
}
794.前端如何实现折叠面板效果?【热度: 113】【web 应用场景】【出题公司: TOP100 互联网】
关键词:transition 过度动画
以下是在您给出的折叠面板示例基础上添加动画效果的版本:
HTML 结构(与您之前给出的相同)
html
<div class="accordion">
<div class="accordion-item">
<div class="accordion-header">标题 1</div>
<div class="accordion-content">
<p>这是内容 1</p>
</div>
</div>
<div class="accordion-item">
<div class="accordion-header">标题 2</div>
<div class="accordion-content">
<p>这是内容 2</p>
</div>
</div>
<!-- 可以添加更多的折叠项 -->
</div>
CSS 样式
css
.accordion {
width: 300px;
}
.accordion-item {
border: 1px solid #ccc;
margin-bottom: 5px;
}
.accordion-header {
background-color: #f1f1f1;
padding: 10px;
cursor: pointer;
}
.accordion-content {
padding: 10px;
overflow: hidden;
max-height: 0;
transition: max-height 0.3s ease;
}
.accordion-item.open.accordion-content {
max-height: 500px; /* 您可以根据内容的实际高度进行调整 */
}
JavaScript 功能
javascript
const accordionHeaders = document.querySelectorAll(".accordion-header");
accordionHeaders.forEach((header) => {
header.addEventListener("click", () => {
const item = header.parentElement;
item.classList.toggle("open");
});
});
这样,当点击折叠面板的标题时,内容会以平滑的动画效果展开或收起。您可以根据实际需求调整动画的时长和内容展开的最大高度等参数。
高级开发者相关问题【共计 8 道题】
778.[React] 如何避免使用 context 的时候, 引起整个挂载节点树的重新渲染【热度: 420】【web 框架】【出题公司: TOP100 互联网】
关键词:React Context 渲染问题
要避免在 React 开发中使用 context
时引起整个挂载节点树的重新渲染,可以采取以下方法:
- React Context 数据分割:把提供
context value
的部分提取到单独的组件中,并且仅在该组件中修改context value
。这样,当context value
变化时,只有真正使用该context
的消费组件会重新渲染,而非所有挂载节点都会重新渲染。
假设我们有一个应用,需要管理主题颜色和用户信息两个不同的数据。
首先,创建两个 Context:
jsx
import React from "eact";
// 创建主题颜色 Context
const ThemeContext = React.createContext({ theme: "light" });
// 创建用户信息 Context
const UserContext = React.createContext({ user: null });
在顶层组件中,提供这两个 Context 的 Provider,并设置相应的值:
jsx
class App extends React.Component {
state = {
theme: "dark",
user: { name: "John Doe", age: 25 },
};
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
<UserContext.Provider value={this.state.user}>
<Toolbar />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
然后,在需要使用主题颜色的组件中,可以通过以下方式获取:
jsx
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
const theme = this.context;
return <Button theme={theme} />;
}
}
在需要使用用户信息的组件中,同样方式获取:
jsx
class UserProfile extends React.Component {
static contextType = UserContext;
render() {
const user = this.context;
return (
<div>
<p>用户名:{user.name}</p>
<p>年龄:{user.age}</p>
</div>
);
}
}
在上述例子中,我们将主题颜色和用户信息分割到不同的 Context 中。ThemeContext
用于传递主题相关的数据,UserContext
用于传递用户相关的数据。这样,不同的组件可以根据自己的需求订阅相应的 Context,获取所需的数据,而不会相互干扰。每个组件只需要关注自己所使用的 Context,提高了代码的可读性和可维护性。同时,当某个 Context 的数据发生变化时,只有订阅了该 Context 的组件才会重新渲染,避免了不必要的重新渲染。
- 对消费组件使用
React.memo()
进行包裹:React.memo
可以对函数组件进行浅比较,如果组件的 props 没有变化,就不会触发重新渲染。通过将消费context
的组件用React.memo()
包裹,可以避免不必要的重新渲染。
例如,假设有一个 ContextProvider
组件提供 context value
,以及一个使用该 context
的子组件 ConsumerComponent
,优化后的代码可能如下所示:
jsx
const ContextProvider = ({ children }) => {
// 管理 context value 的状态
const [value, setValue] = useState(/* 初始值 */);
return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
};
const ConsumerComponent = React.memo(({ contextValue }) => {
// 仅根据 context value 进行渲染或处理逻辑
return <div>{/* 使用 context value 的相关逻辑 */}</div>;
});
在上述示例中,ContextProvider
负责管理 context value
的状态变化,而 ConsumerComponent
是使用 context
的消费组件,并通过 React.memo()
进行了包裹。这样,当 value
发生变化时,只有 ConsumerComponent
会根据浅比较来决定是否重新渲染,而不是整个挂载节点树都重新渲染。
通过以上方式,可以减少使用 context
时不必要的重新渲染,提高应用的性能。但具体的优化策略还需要根据项目的实际情况进行选择和调整。同时,还需注意避免在 context
中传递过于复杂或频繁变化的数据,以减少不必要的渲染次数。
780.[微前端] 微前端架构一般是如何做 JavaScript 隔离【热度: 127】【工程化】【出题公司: 阿里巴巴】
关键词: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 隔离时,需要根据具体项目需求、技术栈和团队的熟练度来选取合适的隔离策略,以确保子应用之间的高度独立性和可维护性。
781.[微前端] Qiankun 是如何做 JS 隔离的【热度: 228】【工程化】【出题公司: 阿里巴巴】
关键词:JS 隔离
Qiankun 是一个基于 Single-SPA 的微前端实现库,它提供了比较完善的 JS 隔离能力,确保微前端应用间的独立运行,避免了全局变量污染、样式冲突等问题。Qiankun 实现 JS 隔离的主要机制包括:
1. JS 沙箱
Qiankun 使用 JS 沙箱技术为每个子应用创建一个独立的运行环境。沙箱有以下两种类型:
-
快照沙箱(Snapshot Sandbox):在子应用启动时,快照并记录当前全局环境的状态,然后在子应用卸载时,恢复全局环境到启动前的状态。这种方式不会对全局对象进行真正的隔离,而是通过记录和恢复的方式避免全局环境被污染。
-
Proxy 沙箱 :通过
Proxy
对象创建一个全新的全局对象代理,子应用的所有全局变量修改操作都将在这个代理对象上进行,从而不会影响到真实的全局对象。这种方式提供了更为彻底的隔离效果,是 Qiankun 中推荐的沙箱隔离方式。
2. 动态执行 JS 代码
Qiankun 通过动态执行 JS 代码的方式加载子应用,避免了脚本直接在全局环境下执行可能导致的变量污染。具体来说,它可以动态获取子应用的 JS 资源,然后在沙箱环境中运行这些代码,确保代码执行的全局变量不会泄露到主应用的全局环境中。
3. 生命周期隔离
Qiankun 给每个子应用定义了一套生命周期钩子,如 bootstrap
、mount
、unmount
等,确保在应用加载、激活和卸载的过程中正确管理和隔离资源。通过在 unmount
生命周期钩子中正确清理子应用创建的全局监听器、定时器等,进一步保证了不同子应用间的独立性和隔离性。
4. 样式隔离
虽然主要针对 JS 隔离,Qiankun 也提供了样式隔离机制,通过动态添加和移除样式标签,保证子应用样式的独立性,避免不同子应用间的样式冲突。
通过以上机制,Qiankun 能够有效实现微前端架构中子应用的 JS 隔离,加强了应用间的独立性和安全性,使得不同子应用可以无缝集成在一起,同时又能够保持各自的运行环境独立不受影响。
782.[微前端] 为何通常在 微前端 应用隔离, 不选择 iframe 方案【热度: 280】【工程化】【出题公司: 阿里巴巴】
关键词: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
彻底,但在整体的应用性能、用户体验和开发效率上通常会有更好的表现。
783.应用如何做应用灰度发布【热度: 247】【工程化】【出题公司: 腾讯】
关键词:灰度发布
应用的灰度发布是将新版本逐步推出给有限的用户群体,以在完全发布之前监控其性能和搜集用户反馈的过程。这可以确保新版本的稳健性,减少因新版本可能引起的问题对所有用户的影响。以下是实现应用灰度发布的几种常见方法:
1. 基于 HTTP 头或 Cookie 的路由
通过识别用户的 HTTP 请求头(如 User-Agent)或特定的 Cookie,决定用户请求被路由到新版本还是旧版本的应用。这种方法通常需要负载均衡器或网关支持特定路由规则。
2. 使用服务网格(Service Mesh)
服务网格如 Istio 提供了复杂的流量管理能力,可以在微服务架构中实现灰度发布。通过定义路由规则,Istio 可以将特定比例或特定条件的流量导向新版本服务。
3. 功能开关(Feature Toggles)
功能开关允许开发者在代码中嵌入开关,根据配置动态激活或关闭某些功能。这样,新版本的功能可以被隐藏,直到你决定通过更改配置为特定用户群体开放。
4. DNS 路由
通过 DNS 管理,将部分用户的请求解析到部署了新版本应用的服务器上。这种方法简单,但切换和回退可能不如其他方法灵活。
5. CDN 切换
对于前端应用或静态资源,可以通过 CDN 配置,将部分用户的请求路由到包含新版本资源的 CDN 上。通过调整 CDN 的缓存规则控制版本切换。
6. A/B 测试平台
将灰度发布作为 A/B 测试的一部分,使用专门的 A/B 测试平台来控制哪些用户看到新版本。这种方法不仅可以实现灰度发布,还能搜集用户反馈和使用情况数据。
7. 容器编排和管理
在支持容器编排(如 Kubernetes)的环境中,可以通过部署新版本的 Pod 副本,并逐步增加新版本副本的数量,同时减少旧版本副本的数量实现灰度发布。
在实施灰度发布时,应该配合监控和日志记录工具,以便快速识别并解决新版本可能引入的问题。同时,在决定完全推出新版本之前,逐渐增加访问新版本的用户比例,确保在所有阶段都能够保持应用的稳定性和高性能。
785.如何清理源码里面没有被应用的代码, 主要是 JS、TS、CSS 代码【热度: 329】【web 应用场景】【出题公司: 腾讯】
关键词:代码清理
清理源码中未被应用的 JavaScript (JS)、TypeScript (TS) 和 CSS 代码的关键在于合理利用工具和策略,来识别和移除这些废弃的代码。下面是一份指南,帮助你高效完成这一任务:
对于 JavaScript 和 TypeScript
1. 使用 ESLint
- 初始化 ESLint :如果你还没有使用 ESLint,可以通过
npx eslint --init
命令来初始化配置。 - 配置规则 :确保在
.eslintrc
配置文件中启用了no-unused-vars
规则,以识别未使用的变量和函数。
json
{
"rules": {
"no-unused-vars": "warn"
}
}
- 使用 ESLint 的 --fix 选项: 虽然 ESLint 主要用于识别问题,但它的 --fix 选项可以自动修复一些问题,包括删除未使用的变量等。不过,这种方式相对保守,无法删除大块的未使用代码
2. 使用 TypeScript 编译器选项
- 对于 TypeScript 项目,可以在
tsconfig.json
文件中启用noUnusedLocals
和noUnusedParameters
选项,以识别未使用的本地变量和函数参数。
json
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
3. 利用 Webpack 的 Tree Shaking
- 确保在生产模式下使用 Webpack,它自带 Tree Shaking 功能,可以去除死代码(未被使用的代码)。
- 使用 ES6 模块语法(即
import
和export
),因为 Tree Shaking 仅支持静态导入。
对于 CSS
1. 使用 PurgeCSS
- PurgeCSS分析你的内容和 CSS 文件,去除不匹配的选择器。非常适用于清楚在 HTML 或 JS 文件中未引用的 CSS 代码。
- 可以通过 Webpack、Gulp 或 PostCSS 等多种方式与 PurgeCSS 集成。
bash
npm install purgecss
使用 PurgeCSS 时,配置你的内容文件路径(如 HTML 或 JSX 文件),它会扫描这些文件以确定哪些 CSS 选择器被使用:
javascript
// 一个基本的PurgeCSS配置例子
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
});
使用 Codemods
Codemods 是 Facebook 提出的一种工具,允许你对代码库进行大规模的自动化重构。通过编写特定的脚本,你可以自定义删除或修改未被调用的代码的逻辑。例如,使用 jscodeshift
工具可以配合具体规则进行代码修改。
注意事项
- 测试:自动删除代码后,务必执行完整的测试套件,确认改动不会影响现有功能。
- 版本控制:在进行删除操作之前,确保代码已经提交到版本控制系统,以便必要时可以恢复。
- 逐步执行:尤其是在较大或复杂的项目中,建议分步骤、逐渐移除未使用的代码,每次删除后都进行测试和评估。
使用这些策略和工具可以帮助自动化清理未使用的代码,但是请注意,完全自动化的过程可能会有风险,依然需要人工审核和测试以确保代码的质量和应用的稳定性。
786.一般是怎么做代码重构的【热度: 191】【web 应用场景】【出题公司: PDD】
关键词:代码重构
在前端项目中进行代码重构,一般可以遵循以下步骤:
-
明确重构目标
- 确定需要解决的问题,例如提高代码的可读性、可维护性、性能,或者去除重复代码等。
-
代码分析
- 对现有代码进行全面的审查和理解,包括代码结构、逻辑流程、函数和模块之间的关系等。
- 可以使用工具如 ESLint 检查代码风格和潜在问题,使用性能分析工具如 Chrome DevTools 的 Performance 面板来检测性能瓶颈。
-
制定重构计划
- 根据分析结果,确定重构的步骤和顺序。
- 将大型的重构任务分解为较小的、可管理的子任务。
-
重写代码结构
- 对模块和组件进行合理的拆分和组织,使代码结构更加清晰。
- 例如,将功能相关的代码提取到单独的函数或模块中,提高代码的内聚性和复用性。
-
优化函数和方法
- 检查函数的长度和复杂性,对过长或过于复杂的函数进行分解。
- 去除不必要的参数传递和全局变量的使用。
-
处理数据结构
- 评估数据的存储和使用方式,选择更合适的数据结构(如从数组切换到对象,或者使用 Map、Set 等)来提高数据操作的效率。
-
优化性能
- 例如,减少不必要的计算、优化 DOM 操作、合理使用缓存等。
-
测试和验证
- 对重构后的代码进行全面的单元测试、集成测试和端到端测试,确保功能的正确性和稳定性。
-
代码审查
- 邀请团队成员对重构后的代码进行审查,获取反馈和建议,进一步优化代码。
-
文档更新
- 对重构后的代码功能、接口和使用方法进行文档更新,方便其他开发人员理解和使用。
以一个简单的前端项目为例,假设有一个处理用户数据展示的模块,最初的代码可能是所有功能都写在一个大型的函数中,并且数据存储在全局变量中。
重构时:
-
将数据处理、数据获取和数据展示的功能分别提取到不同的函数中。
-
将数据从全局变量改为使用模块内部的私有变量或通过参数传递。
-
对数据处理函数进行优化,去除重复的代码逻辑。
-
为新的函数和模块添加必要的注释和文档说明。
通过这样的重构过程,可以使前端项目的代码质量得到显著提升,为后续的开发和维护提供更好的基础。
791.判断一个对象是否为空,包含了其原型链上是否有自定义数据或者方法。 该如何判定?【热度: 546】【JavaScript】【出题公司: PDD】
关键词:原型链相关知识
要判断一个对象是否为空,并且考虑到对象自身及其原型链上是否有自定义数据或方法,您可以使用以下的 JavaScript 函数来实现:
javascript
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;
}
可以使用这个函数来判断对象是否为空,例如:
javascript
function MyClass() {}
MyClass.prototype.myMethod = function () {};
const instance = new MyClass();
console.log(isObjectEmpty(instance));