三剑客

div 上下居中对齐的方式

常用 Flexbox 布局

1
2
3
4
5
.parent {
display: flex;
align-items: center; /* 垂直居中 */
/* justify-content: center; 如果需要水平居中则加上这行 */
}

深拷贝与浅拷贝

浅拷贝 深拷贝
定义 在栈内存中重新开辟一块内存空间,并将拷贝对象储存在栈内存中的数据存放到其中。 会另外创造一个一模一样的对象
优缺点 浅拷贝只复制了引用,所以修改新对象会影响到原对象 修改新对象不会影响到原对象
实现方法 使用赋值运算符(=)或者Object.assign()函数进行拷贝 递归拷贝JSON.parse(JSON.stringify(obj)) 或者…扩展运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 浅拷贝
const a = [1, 2, 3];
const b = a;
b[0] = 4;
console.log(a); // [4, 2, 3]
console.log(b); // [4, 2, 3]


// 深拷贝
const a = [1, 2, 3];
const b = […a]; // 这只适用于一层深拷贝
const c = JSON.parse(JSON.stringify(a)); // 深拷贝 如果是个对象有函数或者undefind时会有问题
c[0] = 4;
console.log(a); // [1, 2, 3]
console.log(c); // [4, 2, 3]


// 深拷贝问题代码示例
const obj = {
name: '前端',
age: undefined, // 问题1:undefined
sayHi: function() { // 问题2:函数
console.log('hi');
},
address: {
city: '上海',
detail: undefined // 问题3:嵌套对象里的 undefined
}
};

const copy = JSON.parse(JSON.stringify(obj));

console.log(copy);
// 输出结果:
// {
// name: '前端',
// address: { city: '上海' } // detail 属性直接消失了!
// }
// 注意:age 和 sayHi 也完全消失了


// null值不影响
const safeObj = { a: 1, b: 'hello', c: true, d: null };
const copy = JSON.parse(JSON.stringify(safeObj)); // ✅ 完美深拷贝

cookie、sessionStorage、localStorage 的区别

cookie 是一种在客户端存储少量数据并在每个请求中发送给服务器的机制,而 session 则是在服务器端存储用户状态信息,并通过在客户端和服务器之间传递 Session ID 来保持会话状态。它们的区别在于存储的有效期和作用域的不同。为了解决 HTTP 无状态的问题,通常使用 Cookie 和 Session 等机制来在客户端和服务器之间保持状态。

特性 Cookie localStorage sessionStorage
生命周期 默认会话级(关闭浏览器即失效) 可手动设置 ExpiresMax-Age 延长 永久 除非用户手动清除或通过代码删除 会话级 仅当前标签页有效,关闭标签页即清除
存储大小 约 4KB 5-10MB(不同浏览器略有差异) 约 5MB(不同浏览器略有差异)
作用域(同源策略) 域名 + 路径(可设Path) 同源且同路径才能共享 域名 + 协议 + 端口 同源下所有标签页/窗口共享 域名 + 协议 + 端口 仅限同一标签页的同一源
与服务端交互 自动携带(每次HTTP请求都会发送,影响性能) 不自动发送(纯前端存储) 不自动发送(纯前端存储)

作用域的详细解释

  • Cookie 的作用域最窄:默认只对当前路径(/)及其子路径有效,但可以通过 PathDomain 参数扩大范围。
  • localStorage 的作用域是“同源窗口共用”:打开同一个网站的多个标签页,它们共享同一个 localStorage 数据(监听 storage 事件可实现跨标签页通信)。
  • sessionStorage 的作用域是“独立标签页”:即使用户在同一网站通过“新标签页打开链接”,新标签页也会拥有独立的 sessionStorage,与原来的互不影响。

<a>
用户 → 点击 → 跳转

<link>
浏览器 → 发现资源 → 加载/关联

<a> <link>
主要用途 页面导航 引入/声明外部资源
用户是否点击 ✅ 通常点击 ❌ 不用于点击
核心属性 href rel + href
是否显示文字
示例 <a href="https://www.baidu.com">百度</a> <link rel="stylesheet" href="style.css">

注意:这里的rel名称是固定的,常用的有如下:

rel 值 用途
icon 网站标签页图标(favicon)
stylesheet 引入 CSS 样式表

Vue 中的跨域问题

vue3vite.config.js 中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default defineConfig({
server: {
host: '0.0.0.0', // 允许通过IP地址访问
port: 92, // 设置你想要的端口号
allowedHosts: ['pna.vrmmo.online'], // 允许通过域名访问
proxy: {
'/api/paper': {
target: 'http://localhost:8092',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
'/api': {
target: 'http://localhost:8092',
changeOrigin: true
}
}
}
})

浏览器的同源策略会阻止前端(localhost:92)直接请求后端(localhost:8092),产生跨域。Vite 开发服务器提供了 代理(proxy) 功能,让前端请求先发到 Vite 自身,再由 Vite 转发给后端,从而绕过浏览器的跨域限制。

流程

  1. 前端请求 http://localhost:92/api/xxx
  2. Vite 开发服务器拦截该请求
  3. 转发到 http://localhost:8092/api/xxx(服务器之间不存在跨域)
  4. 将后端响应返回给前端

/api/paper 单独配置了 rewrite 去掉 /api 前缀,说明 paper 模块的后端接口路径不带 /api 前缀。

如何优化网站性能

  1. 许多的小图片整合到一张大图片中(精灵图)减少网页的http请求,提升网页加载速度。
  2. 利用浏览器缓存。
  3. 图片lazyload 懒加载 提高用户体验。

浏览器缓存参考 面经-计算机网络 中的强缓存和协商缓存

JS 中操作数组的方法

顺序 方法名 功能 返回值 是否改变原数组 版本
1 push() (在结尾)向数组添加一或多个元素 返回新数组长度 Y ES5-
2 unshift() (在开头)向数组添加一或多个元素 返回新数组长度 Y ES5-
3 pop() 删除数组的最后一位 返回被删除的数据 Y ES5-
4 shift() 移除数组的第一项 返回被删除的数据 Y ES5-
5 reverse() 反转数组中的元素 返回反转后数组 Y ES5-
6 sort() 以字母顺序(字符串Unicode码点)对数组进行排序 返回新数组 Y ES5-
7 splice() 在指定位置删除指定个数元素,再增加任意个数元素(实现数组任意位置的增删改) 返回删除的数据所组成的数组 Y ES5-
8 concat() 通过合并(连接)现有数组来创建一个新数组 返回合并之后的数组 N ES5-
9 join() 用特定的字符,将数组拼接形成字符串(默认”,”) 返回拼接后的新数组 N ES5-
10 slice() 截切指定位置的数组 被截切的元素形成的新的数组 N ES5-
11 toString() 将数组转换为字符串 新数组 N ES5-
12 valueOf() 查询数组原始值 数组的原始值 N ES5-
13 indexOf() 查询某个元素在数组中第一次出现的位置 存在该元素,返回下标,不存在返回 -1 N ES5-
14 lastIndexOf() 反向查询数组某个元素在数组中第一次出现的位置 存在该元素,返回下标,不存在返回 -1 N ES5-
15 forEach() (迭代)遍历数组,每次循环中执行传入的回调函数 无/(undefined) N ES5-
16 map() (迭代)遍历数组,每次循环时执行传入的回调函数,根据回调函数的返回值,生成一个新的数组 有/自定义 N ES5-
17 filter() (迭代)遍历数组,每次循环时执行传入的回调函数,回调函数返回一个条件,把满足条件的元素筛选出来放到新数组中 满足条件的元素组成的新数组 N ES5-
18 every() (迭代)判断数组中所有的元素是否满足某个条件 全都满足返回true 只要有一个不满足返回false N ES5-
19 some() (迭代)判断数组中是否存在,满足某个条件的元素 只要有一个元素满足条件就返回true,都不满足返回false N ES5-
20 reduce() (归并)遍历数组,每次循环时执行传入的回调函数,回调函数会返回一个值,将该值作为初始值prev,传入到下一次函数中 最终操作的结果 N ES5-
21 reduceRight() (归并)用法同reduce,只不过是从右向左 同reduce N ES5-
22 includes() 判断一个数组是否包含一个指定的值 是返回 true,否则 false N ES6
23 Array.from() 接收伪数组,返回对应的真数组 对应的真数组 N

VUE

对于MVVM的理解?

MVVM 是 Model-View-ViewModel 的缩写,MVVM 的设计原理是基于 MVC 的;它主要的优势在于 View 和 Model 之间是双向数据传递的,视图改变数据就可以改变,数据改变了视图也跟着改变。

对比项 MVC MVVM
通信方向 用户操作 → Controller → Model → View(单向为主) View ↔ ViewModel ↔ Model(双向自动同步)
Controller/ViewModel职责 Controller手动控制数据流向和视图更新。 ViewModel通过数据绑定自动同步,无需手动操作。
代码耦合度 View和Model之间可能直接关联,耦合较高。 View和Model完全隔离,只通过ViewModel通信,耦合度低。
适用场景 后端开发、小型前端项目。 复杂的前端单页应用(SPA),如Vue、React(类MVVM)、Angular。

Vue2/3的生命周期

Vue 的生命周期是指一个 Vue 实例从创建、挂载、更新到销毁的完整过程。在这个过程中,Vue 提供了一系列的生命周期钩子函数,让开发者可以在特定的阶段执行自己的逻辑。

Vue 2 的生命周期(共 8 个阶段,11 个钩子)

阶段 钩子函数 触发时机 能否操作DOM 适用场景
创建阶段 beforeCreate 实例初始化后,数据观测和事件配置之前 ❌ 否 很少使用,可用于插件初始化
created 实例创建完成,数据观测、计算属性、方法已配置,但尚未挂载 ❌ 否 最常用:发起 Ajax 请求、初始化数据
挂载阶段 beforeMount 模板编译完成,即将挂载到DOM之前 ❌ 否 很少使用
mounted 实例已挂载到DOMel 被新生成的 vm.$el 替换 ✅ 是 操作 DOM、使用第三方库(如 ECharts)初始化图表
更新阶段 beforeUpdate 数据变化后,虚拟DOM重新渲染和打补丁之前 ❌ 否 在更新前访问现有 DOM 状态,如获取滚动位置
updated 虚拟DOM重新渲染并完成DOM更新后 ✅ 是 执行依赖于 DOM 的操作,但注意避免在此修改数据(死循环)
销毁阶段 beforeDestroy 实例销毁之前this 仍可访问) ✅ 是 清除定时器、取消订阅、解绑事件监听
destroyed 实例销毁完成后,所有指令解绑,事件监听移除 ✅ 是 收尾清理工作

此外,Vue 2 还提供了 3 个特殊场景的钩子:

钩子函数 触发时机
activated keep-alive 缓存的组件激活时触发
deactivated keep-alive 缓存的组件停用时触发
errorCaptured 当捕获到来自子孙组件的错误时触发

Vue 3 的生命周期

Vue 3 保留了 Vue 2 的核心生命周期,但在命名和用法上做了调整:

Vue 2 钩子 Vue 3 钩子(Options API) 变化说明
beforeCreate beforeCreate 名称不变
created created 名称不变
beforeMount beforeMount 名称不变
mounted mounted 名称不变
beforeUpdate beforeUpdate 名称不变
updated updated 名称不变
beforeDestroy beforeUnmount ⚠️ 重命名,语义更准确
destroyed unmounted ⚠️ 重命名,语义更准确
activated activated 配合 <KeepAlive> 使用
deactivated deactivated 配合 <KeepAlive> 使用
errorCaptured errorCaptured 名称不变

注:beforeCreatecreated 在 Vue 3 中仍然保留,但官方更推荐使用 setup() 替代。

Vue组件间的参数传递 和父子组件方法调用

Vue的路由实现:hash模式 和 history模式区别,上线有什么区别。

在 Vue Router 中,hash 模式history 模式是两种前端路由实现方式,它们的核心区别在于如何管理 URL 以及如何与服务器交互

对比维度 Hash 模式(默认模式) History 模式
URL 格式 带有 # 符号,如 /#/about #,如 /about
浏览器支持 兼容所有现代浏览器(包括 IE) 需要支持 HTML5 History API(IE10+)
服务器配置 无需特殊配置,所有请求都返回 index.html 需要服务器配置,所有路由路径都要指向 index.html

Vue路由的钩子函数 (路由守卫)

Vue-router的跳转原理

vuex是什么?怎么使用?哪种功能场景使用它?

对keep-alive 的了解?

Vue深度监听(watch)

Vue怎么缓存 keep-alive

缓存了 突然需要发送请求换数据怎么办

v-if和v-for哪个优先级更高? 如果两个同时出现,应该怎么优化得到更好的性能?

📌 在 Vue 2 中

v-for 的优先级高于 v-if

这意味着当它们同时出现在同一个元素上时:

1
2
3
4
<!-- Vue 2 -->
<div v-for="item in list" v-if="item.isActive" :key="item.id">
{{ item.name }}
</div>

实际执行逻辑相当于:

1
2
3
4
5
6
// 先遍历整个 list,再对每个 item 进行 v-if 判断
list.map(item => {
if (item.isActive) {
// 渲染这个 item
}
})

问题:即使大部分 item.isActivefalsev-for 仍然会遍历整个列表,造成性能浪费


📌 在 Vue 3 中

v-if 的优先级高于 v-for

1
2
3
4
<!-- Vue 3 -->
<div v-for="item in list" v-if="item.isActive" :key="item.id">
{{ item.name }}
</div>

这会导致错误:因为 v-if 先执行时,item 变量还未定义,会抛出 item is not defined 的异常。

Vue 3 官方明确不推荐将两者同时使用,并会在控制台给出警告。


⚡ 优化方案(同时适用 Vue 2 和 Vue 3)

将需要渲染的数据提前处理好,模板中只保留 v-for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div v-for="item in activeList" :key="item.id">
{{ item.name }}
</div>
</template>

<script>
export default {
data() {
return {
list: [
{ id: 1, name: 'A', isActive: true },
{ id: 2, name: 'B', isActive: false },
{ id: 3, name: 'C', isActive: true }
]
}
},
computed: {
// ✅ 先过滤,再渲染
activeList() {
return this.list.filter(item => item.isActive)
}
}
}
</script>

优点

  • 逻辑清晰,模板简洁。
  • 只在 list 变化时重新计算,性能最优。
  • 适用于 Vue 2 和 Vue 3。

你知道vue中的key和工作原理么?说说你对它的理解

key 的核心作用是帮助 Vue 更高效、更准确地更新虚拟 DOM

简单来说,key 是 Vue 在对比新旧虚拟节点(VNode)时,判断两个节点是否相同的唯一标识

你怎么理解vue中的 diff 算法?

vue2 与vue3 的区别

  1. Vue 3在速度和体积上相较于Vue 2更小、更快。
  2. 生命周期钩子函数有所变化 , 例如beforeCreatecreatedsetup()函数替代。而其他的生命周期钩子函数则需要在前面加上on前缀,如onBeforeMountonMounted等(Composition API 是这样,Options API 钩子函数名称与 Vue 2 基本一致,前面没有 on)。
  3. 响应式系统 ,Vue 2使用Object.defineProperty()数据劫持来实现响应式数据绑定,Vue 3则使用Proxy来替代。

vue和react的区别 (三个角度分析)

  1. 设计哲学:Vue 是“模板优先”,React 是“JS 优先”。
  2. 更新机制:Vue 是响应式自动追踪依赖,React 是 setState 触发组件树重新渲染。
  3. 状态管理:Vue 数据可变,React 数据不可变(必须通过 setter)。

一句话就能回答的面试题

css只在当前组件起作用

在 Vue 单文件组件(.vue 文件)中,<style scoped> 的作用是将 CSS 样式限定在当前组件范围内,不会影响到父组件或子组件的样式,如下:

1
2
3
4
5
6
7
8
9
10
11
<style lang="css" scoped>
.box{
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>

v-if 和 v-show 区别

  1. 相同点:v-ifv-show都可以动态控制dom元素显示隐藏 。
  2. 不同点:v-if显示隐藏是将dom元素整个添加或删除;而v-show隐藏则是为该元素添加css--display:nonedom元素还在。
  3. 使用场景:一般进入页面不需要频繁显示隐藏,用v-if

$route$router的区别

vue.js的两个核心是什么?

数据驱动、组件系统

vue几种常用的指令

见面经-简历中的vue组件相关知识点

背诵技巧:A组爽(双)文是(事)薯(属)条

vue常用的修饰符?

  1. @事件名.stop —> 阻止冒泡
  2. @事件名.prevent —>阻止默认行为
  3. @事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.father {
width: 200px;
height: 200px;
background-color: pink;
margin-top: 20px;
}
.son {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
</head>
<body>
<div id="app">
<h3>v-model修饰符 .trim .number</h3>
姓名:<input v-model.trim="username" type="text"><br>
年纪:<input v-model.number="age" type="text"><br>


<h3>@事件名.stop → 阻止冒泡</h3>
<div @click="fatherFn" class="father">
<div @click.stop="sonFn" class="son">儿子</div>
</div>

<h3>@事件名.prevent → 阻止默认行为</h3>
<a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
age: '',
},
methods: {
fatherFn () {
alert('老父亲被点击了')
},
sonFn (e) {
// e.stopPropagation()
alert('儿子被点击了')
}
}
})
</script>
</body>
</html>

v-on 可以绑定多个方法吗?

可以v-on@)支持绑定多个方法,常用有以下两种方式:

方式一:对象语法(最推荐)

绑定多个不同事件,每个事件独立处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<!-- 同时监听 click 和 mouseenter -->
<button @{ click: handleClick, mouseenter: handleEnter }>
悬停或点击
</button>
</template>

<script>
export default {
methods: {
handleClick() { console.log('点击') },
handleEnter() { console.log('鼠标进入') }
}
}
</script>

方式二:数组语法

同一个事件绑定多个处理函数,按数组顺序依次执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<!-- 点击时先执行 log,再执行 submit -->
<button @click="[log, submit]">提交</button>
</template>

<script>
export default {
methods: {
log() { console.log('记录日志') },
submit() { console.log('提交表单') }
}
}
</script>

执行顺序:数组从左到右同步执行(除非方法内有异步操作)。

vue中key 值的作用

核心作用是帮助 Vue 更高效、更准确地更新虚拟 DOM

$nextTick的使用

$nextTick能获取到最新dom是什么原理

父组件调用子组件方法

项目初始化页面闪动问题

javascript

数组快速去重,数组扁平化

1
2
3
4
5
6
7
8
let a=[1,6,8,4,9,5,3,2,5,4,8,1,2,3]
// ... 是深拷贝,对arr中的改变不影响a中的值
let arr=[...new Set(a)]
console.log(arr);// [1, 6, 8, 4, 9, 5, 3, 2]

let b=[1,1,2,2,3,[1,2,3],[1,2,[2,3]]]
b=b.flat(Infinity)
console.log(b);// [1, 1, 2, 2, 3, 1, 2, 3, 1, 2, 2, 3]

数组快速删除最后一个数据

1
array.pop()

将一个键值对对象转换成一个对象数组,每个对象包含 key 和 label 两个字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const data = {
C1: "青岛",
C3: "成都",
C31: "南昌",
C19: "哈尔"
};

// 1. Object.entries(data) 得到:
// [ ['C1', '青岛'], ['C3', '成都'], ['C31', '南昌'], ['C19', '哈尔'] ]

// 2. .map(([key, label]) => ({ key, label })) 将每个小数组映射为对象
const orderTypeOptions = Object.entries(data).map(([key, label]) => ({ key, label }));

console.log(orderTypeOptions);
// 输出:
// [
// { key: 'C1', label: '青岛' },
// { key: 'C3', label: '成都' },
// { key: 'C31', label: '南昌' },
// { key: 'C19', label: '哈尔' }
// ]

javascript的数据类型

  1. 常见的基本数据类型:Number、String 、Boolean、Null和Undefined。
  2. 引用类型 比如:Object 、Array 、Function 、Date等。

相关记载可参考:JS中的基础数据类型

怎样判断变量的类型

typeof 能够区分除数组、对象和null外的所有类型;[] {} null 都返回object

1
2
3
4
5
6
7
8
9
10
11
12
typeof 42            // "number"
typeof "hello" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof function(){} // "function"
typeof Symbol() // "symbol"
typeof BigInt(10) // "bigint"

// 局限性:
typeof [] // "object"
typeof {} // "object"
typeof null // "object" (这是一个历史 bug)

数据类型转换

运算符 名称 作用 是否触发类型转换
= 赋值运算符 将右侧的值赋给左侧的变量 ❌ 不涉及比较
== 相等运算符(宽松比较) 比较两个值是否相等(类型不同时尝试转换) 会隐式转换
=== 全等运算符(严格比较) 比较两个值是否完全相同(类型和值都必须一致) 不转换

示例对比

1
2
3
4
5
6
7
8
9
10
11
12
// 1. = 是赋值(不是比较)
let a = 10; // 把 10 赋给 a

// 2. == 会隐式转换
1 == '1' // true('1' 转为数字 1,然后比较)
true == 1 // true(true 转为数字 1)
null == undefined // true(特殊规则)

// 3. === 不转换,直接比较
1 === '1' // false(类型不同,直接返回 false)
true === 1 // false(布尔值 vs 数字)
null === undefined // false(类型不同)

vite 常见面试题

什么是vite ?它解决了什么问题 ? Vite的工作原理是什么?

Vite(法语意为”快速”,发音为 /vit/)是一个基于原生 ES Modules 的现代化前端构建工具,提供了极快的开发服务器启动速度即时的模块热更新,是 Vue 3 和许多现代前端项目的默认构建工具。

vite.config.js文件是做什么用的?

vite.config.jsVite的配置文件,允许你自定义Vite的行为,如定义入口文件、基础路径、构建输出、插件等