前端面试问题(jwt/布局/vue数组下标/扁平化/菜单树形/url api/新版本)
1. jwt鉴权逻辑
前端 JWT 鉴权逻辑通常涉及在发起请求时携带 JWT,并在接收到响应后处理可能的授权问题。
1. 用户登录:
-
用户提供凭证: 用户在登录界面输入用户名和密码。
-
请求后端认证: 前端通过发送用户提供的凭证(通常是用户名和密码)到后端进行身份验证。
-
接收并存储Token: 如果身份验证成功,后端生成 JWT 并将其发送给前端。前端通常会将 JWT 存储在客户端(通常是浏览器)的本地存储(localStorage 或 sessionStorage)中。
2. 请求时的鉴权:
-
构建请求头: 在每次发送请求时,前端将存储的 JWT 添加到请求头中。
// 使用 Axios 发送请求的方式 const token = localStorage.getItem('jwtToken'); axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
-
发送请求: 发送请求到后端,后端会检查请求头中的 JWT 是否有效。
-
处理响应: 前端接收到响应后,可以根据响应状态码和内容进行相应的处理。
-
如果响应状态码为 401(未授权)或 403(禁止访问),可能表示 JWT 已过期或用户无权限,需要处理重新登录或其他操作。
-
如果响应状态码为 200,表示请求成功,前端可以继续处理返回的数据。
-
3. 处理过期的Token:
-
捕获过期错误: 前端需要捕获过期错误。当后端返回 401 状态码时,可以视为 JWT 过期。
-
刷新Token: 如果服务器支持,可以尝试使用 refresh token 来获取新的 JWT,避免用户重新登录。
注意事项:
-
安全存储: JWT 存储在前端,因此需要确保它被安全地存储。一般来说,避免将敏感信息存储在 JWT 中,因为它可以被解码。
-
定期刷新: 定期检查 JWT 是否过期,如果过期,需要进行刷新操作。
-
前端安全性: 前端只能负责存储和传递 JWT,实际的用户身份验证和授权逻辑仍然应该由后端负责。
2. 实现顶部导航+左侧菜单+右侧主内容区域布局
在前端,实现顶部导航、左侧菜单、右侧主内容区域布局通常有多种方式,取决于项目的需求和开发者的技术偏好。以下是其中三种常见的实现方式:
1. Flexbox 布局:
使用 CSS 的 Flexbox 布局是一种简单而灵活的方法,它能够轻松地实现顶部导航、左侧菜单和右侧主内容区域的布局。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flexbox Layout</title>
<style>
body {
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
header, main {
flex: 0 0 auto;
}
main {
display: flex;
}
nav {
width: 200px;
background-color: #333;
color: #fff;
}
section {
flex: 1;
padding: 20px;
}
</style>
</head>
<body>
<header>
<!-- 顶部导航 -->
<h1>顶部</h1>
</header>
<main>
<nav>
<!-- 左侧菜单 -->
<h1>左侧</h1>
</nav>
<section>
<!-- 右侧主内容区域 -->
<h1>右侧</h1>
</section>
</main>
</body>
</html>
2. Grid 布局:
CSS Grid 布局也是一种强大的布局方式,允许更复杂的网格结构。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Grid Layout</title>
<style>
body {
margin: 0;
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr;
grid-template-areas:
"header header"
"nav main";
height: 100vh;
}
header, nav, main {
padding: 20px;
}
header {
grid-area: header;
background-color: #ddd;
}
nav {
grid-area: nav;
background-color: #333;
color: #fff;
}
main {
grid-area: main;
display: flex;
}
</style>
</head>
<body>
<header>
<!-- 顶部导航 -->
<h1>顶部</h1>
</header>
<nav>
<!-- 左侧菜单 -->
<h1>左侧</h1>
</nav>
<main>
<!-- 右侧主内容区域 -->
<h1>右侧</h1>
</main>
</body>
</html>
3. Bootstrap:
使用 Bootstrap 框架是一种快速搭建响应式布局的方法。Bootstrap 提供了许多现成的组件和样式,使得实现这种布局变得非常容易。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<title>Bootstrap Layout</title>
</head>
<body>
<header class="bg-light p-3">
<!-- 顶部导航 -->
<h1>顶部</h1>
</header>
<div class="container-fluid">
<div class="row">
<nav class="col-md-2 bg-dark text-light">
<!-- 左侧菜单 -->
<h1>左侧</h1>
</nav>
<main class="col-md-10">
<!-- 右侧主内容区域 -->
<h1>右侧</h1>
</main>
</div>
</div>
</body>
</html>
4. CSS Float 布局
使用float
属性可以实现一种简单的布局,但需要注意清除浮动以避免影响后续布局。
<div class="header">顶部导航</div>
<div class="menu">左侧菜单</div>
<div class="content">右侧主内容区域</div>
<style>
.header { clear: both; }
.menu { float: left; width: 20%; }
.content { margin-left: 20%; }
</style>
3. Vue数组下标改值时响应式丢失,为什么
在Vue中,数组下标改值导致响应式丢失的原因通常是由于Vue对数组的监听机制的限制。Vue的响应式系统对于数组的变更检测有一些局限性,主要涉及到以下情况:
-
直接通过索引设置数组元素时,无法触发视图更新: Vue的响应式系统不能检测到直接通过索引设置数组元素的变化。例如,
array[index] = value
这种方式不会触发响应式更新。 -
通过
splice
方法添加或删除元素时能够触发更新: 使用Vue提供的数组变异方法(例如splice
)来添加或删除元素时,Vue能够监听到变化并触发相应的更新。
// 在Vue组件中的data
data() {
return {
myArray: [1, 2, 3]
};
},
methods: {
updateValue() {
// 这种方式修改数组的元素,不会触发响应式更新
this.myArray[0] = 99;
}
}
直接通过索引修改数组元素的值 this.myArray[0] = 99;
不会触发Vue的响应式系统。
为了确保能够触发响应式更新,可以使用Vue提供的变异方法,比如使用Vue.set
方法:
methods: {
updateValue() {
// 使用 Vue.set 来确保触发响应式更新
Vue.set(this.myArray, 0, 99);
}
}
或者使用splice
方法:
methods: {
updateValue() {
// 使用 splice 来确保触发响应式更新
this.myArray.splice(0, 1, 99);
}
}
4. 数组扁平化
-
使用递归(原生 JavaScript):
function flattenArray(arr) { return arr.reduce((acc, curr) => Array.isArray(curr) ? acc.concat(flattenArray(curr)) : acc.concat(curr), []); } const nestedArray = [[0, 1], [2, [3, 4]], [5, 6]]; const flattenedArray = flattenArray(nestedArray); console.log(flattenedArray);
-
使用
Array.flat()
方法(ECMAScript 2019):const nestedArray = [[0, 1], [2, [3, 4]], [5, 6]]; const flattenedArray = nestedArray.flat(Infinity); console.log(flattenedArray);
-
使用 lodash 库:
const _ = require('lodash'); const nestedArray = [[0, 1], [2, [3, 4]], [5, 6]]; const flattenedArray = _.flattenDeep(nestedArray); console.log(flattenedArray);
Array.flat()
方法和 lodash 的 _.flattenDeep()
都可以递归地将数组扁平化,而原生的递归方法Array.reduce()` 方法需要手动处理递归。
5. 菜单数组转换为嵌套树形结构
[{ id: 1, menu: '水果', level: 1 }, { id: 2, menu: '橘子', level: 2, parentId: 1 } ]
[{ id: 1, menu: '水果', level: 1, children: [{ id: 2, menu: '橘子', level: 2, parentId: 1 }] }]
function convertToNestedTree(menuArray) {
const idToMenuMap = {}; // 用于存储菜单项的映射,通过id快速查找
// 构建映射
menuArray.forEach(item => {
idToMenuMap[item.id] = item;
});
// 构建树形结构
const tree = [];
menuArray.forEach(item => {
if (!item.parentId) {
// 如果没有parentId,说明是根节点,直接添加到树中
tree.push(item);
} else {
// 如果有parentId,将当前项添加到父级的children数组中
const parentMenu = idToMenuMap[item.parentId];
if (parentMenu) {
if (!parentMenu.children) {
parentMenu.children = [];
}
parentMenu.children.push(item);
}
}
});
return tree;
}
const menuArray = [
{ id: 1, menu: '水果', level: 1 },
{ id: 2, menu: '橘子', level: 2, parentId: 1 },
];
const nestedTree = convertToNestedTree(menuArray);
console.log(nestedTree);//[{ id: 1, menu: '水果', level: 1, children: [{ id: 2, menu: '橘子', level: 2, parentId: 1 }] }]
6. url 参数获取的 API
在前端,使用 URLSearchParams
对象来获取 URL 参数。这是一个原生 JavaScript 对象,可用于解析 URL 查询参数。
// 假设 URL 为 https://example.com/page?name=John&age=25
const urlParams = new URLSearchParams(window.location.search);
// 获取单个参数
const name = urlParams.get('name'); // 返回 'xx'
const age = urlParams.get('age'); // 返回 '25'
// 获取所有参数
const allParams = {};
urlParams.forEach((value, key) => {
allParams[key] = value;
});
console.log(allParams);
// 输出:{ name: 'xx', age: '25' }
使用 URLSearchParams
对象从当前页面的 URL 中提取参数。
7. 新版本发布后,怎么用技术手段通知用户刷新页面
在前端中,可以使用以下几种技术手段来通知用户刷新页面以加载新版本:
-
Service Worker 和 Cache 更新:
- 使用 Service Worker 来缓存资源并控制页面加载。
- 当新版本发布时,Service Worker 可以检测到更新,然后发送消息到页面,通知用户有新版本可用。
- 页面收到消息后,可以显示一个通知或提示,引导用户刷新页面。
-
WebSocket 或 Server-Sent Events (SSE):
- 使用 WebSocket 或 SSE 与服务器建立实时通信通道。
- 当新版本发布时,服务器通过通道向客户端发送消息。
- 客户端收到消息后,可以显示通知并提示用户刷新页面。
-
Polling:
- 定期向服务器发起请求检查是否有新版本。
- 当服务器检测到新版本时,返回相应的信息。
- 页面收到信息后,显示通知并引导用户刷新页面。
-
LocalStorage 或 IndexedDB 标记:
- 在本地存储(LocalStorage)或 IndexedDB 中保存一个标记,表示当前页面的版本。
- 当新版本发布时,将新版本的标记写入本地存储或 IndexedDB。
- 页面加载时检查标记,如果检测到新版本,显示通知并引导用户刷新页面。
-
使用 Service Worker 的
skipWaiting
和clients.claim
:- 在 Service Worker 中使用
self.skipWaiting()
和clients.claim()
来立即激活新版本的 Service Worker。 - 在新版本的 Service Worker 中发送消息到页面,通知用户有新版本可用。
- 页面接收到消息后,显示通知并引导用户刷新页面。
- 在 Service Worker 中使用