文章目录
- watch和computed区别
- vue路由中router,route,routes的区别
- 作用如下:
- **面试官可能的追问**
- vue2 和 vue3 生命周期
- vue2 生命周期
- vue3 生命周期
- SPA——MVC 和 MVVM
- SPA
- MVC和MVVM架构
- 面试中关于MVVM及Vue.js相关问题回答:
- nextTick()
- **1. 核心作用**
- **2. 常见使用场景**
- **① 数据变化后获取最新 DOM**
- **② 动画或第三方库操作**
- **③ 避免重复渲染**
- **3. 原理简析**
- **4. 与 **`setTimeout`** 的区别**
- **5. 注意事项**
- **6. 总结模板**
- **面试官可能的追问**
- v-show和v-if的区别
- 父子组件,兄弟组件的通信
- 登录鉴权
- vue 响应式原理(数据双向绑定)
- vue2 响应式
- vue3 响应式
- 如何改 bug
- 插槽<slot>
- 1. 概念
- 2. 插槽类型及示例
- 默认插槽
- 具名插槽
- 作用域插槽
- 3. 对比总结
- 混入(mixins)
- keep-alive
- vue 源码
- vue3 性能比 vue2 好,为什么?
- vue的代理和环境变量
- 数据发生变化,(DOM)视图没有更新
- 大文件切片上传
- vue 自定义指令
- 前端组件级权限控制的实践
- 前言
- 组件级别的权限控制
- _示例:根据角色和权限展示不同的组件_
- 权限组件
- 单个权限
- 多个权限
- _ 权限作用域_
- _Authority 组件_
- 总结
- 长时间保存token的安全问题:
- 处理大量列表数据
- 虚拟列表
- 首页性能提升
- 1. 缓存组件
- 2. 图片压缩处理
- 3. 减少重绘和回流
- 4. 使用 CDN 加速
- 5. 路由、图片懒加载
- 6. 启用 gzip 压缩
- 前端性能优化实用方案总结
- **一、首屏加载优化**
- **二、操作与渲染速度优化**
- **三、特殊情况优化**
- **四、面试回答技巧**
- vueRouter 的 mode 模式(hash,history)【待总结】
watch和computed区别
- computed:
- 计算属性,会计算出一个结果,函数值改变就会重新计算;
- 初始时会执行一次
- computed 会读取缓存数据
- watch:
- 监控某个数据,被监控数据更改,则 watch 执行
- 初始化时不执行,(添加 immediate)
<font style="color:rgb(26, 32, 41);">computed</font>
更适合用于基于现有响应式数据的简单计算,而<font style="color:rgb(26, 32, 41);">watch</font>
更适合用于观察数据变化以执行更复杂的逻辑或异步操作。
vue路由中router,route,routes的区别
作用如下:
- Routes
- 类型:路由配置数组,定义所有路径规则(静态)。
- 示例:
<font style="color:rgb(26, 32, 41);">routes: [{ path: '/user', component: User }]</font>
- Router
- 类型:路由实例,基于
<font style="color:rgb(26, 32, 41);">routes</font>
生成的管理器(动态)。 - 核心功能:导航(
<font style="color:rgb(26, 32, 41);">push</font>
/<font style="color:rgb(26, 32, 41);">replace</font>
)、路由守卫(<font style="color:rgb(26, 32, 41);">beforeEach</font>
)。 - 示例:
<font style="color:rgb(26, 32, 41);">const router = createRouter({ routes })</font>
- 类型:路由实例,基于
- Route
- 类型:当前路由状态对象(动态响应式)。
- 用途:获取路径参数(
<font style="color:rgb(26, 32, 41);">params</font>
)、查询参数(<font style="color:rgb(26, 32, 41);">query</font>
)等。 - 示例:
<font style="color:rgb(26, 32, 41);">useRoute().params.id</font>
三者协作流程:用户访问路径 → <font style="color:rgb(26, 32, 41);">routes</font>
匹配规则 → <font style="color:rgb(26, 32, 41);">router</font>
加载组件 → <font style="color:rgb(26, 32, 41);">route</font>
更新当前状态。
面试官可能的追问
- “如何动态添加路由?”
→ 使用router.addRoute()
方法动态扩展routes
(适用于权限分级场景)。 - "为什么****
**route**
****是响应式的?"
→ 因为 Vue Router 通过watchEffect
监听路由变化,自动更新route
对象的响应式状态。 - “能否举一个路由守卫的例子?”
→ 示例:router.beforeEach((to, from) => { if (!token) return '/login' })
vue2 和 vue3 生命周期
vue2 生命周期
8个生命周期 ,记忆 4 个名字即可。
//created mounted updated destroyed
beforeCreate(){
console.log("beforeCreate,挂载了vue实例的方法,但是data没有挂载",this.msg)
},
created(){
console.log("created,挂载了data",this.msg)
},
beforeMount(){
console.log("data没有渲染到了页面",document.getElementById("app").innerHTML)
},
mounted(){
console.log("mounted data渲染到了页面",document.getElementById("app").innerHTML)
},
beforeUpdate(){
console.log("beforeUpdate数据更改导致DOM更改之前",document.getElementById("app").innerHTML)
},
updated(){
console.log("updated数据更改导致DOM更改之后",document.getElementById("app").innerHTML)
},
beforeDestroy() {console.log("beforeDestroy")
},
destroyed() {console.log("destroyed")
},
vue3 生命周期
vue3 删除了 beforeCreate created 用 setup代替destroy 改为了 unmount
const { createApp, onBeforeMount, onMounted, onBeforeUpdate, onUpdate, onBeforeUnmount, onUnmounted, ref } = Vue
const app = createApp({ setup() { msg=ref("你好11111111111") console.log("1") onBeforeMount(()=>{ console.log("onBeforeMount data没有渲染到了页面", document.getElementById("app").innerHTML) });onMounted(() => { console.log("onMounted data渲染到了页面", document.getElementById("app").innerHTML) setTimeout(()=>{ msg.value="hello" },1000) }); onBeforeUpdate((()=>{ console.log("onBeforeUpdate") }); onUpdated(() => { console.log("onUpdated") });onBeforeUnmount(() => { console.log("onBeforeUnmount") });onUnmounted(() => { console.log("onUnmounted") });}
})
SPA——MVC 和 MVVM
SPA
- SPA单页面应用 优缺点
SPA:整个项目只有一个html文件,路由切换进行页面切换
优点
1. <font style="color:rgb(26, 32, 41);">用户体验及交互比较流畅</font>
2. <font style="color:rgb(26, 32, 41);">提取组件开发,易于后期维护</font>
3. <font style="color:rgb(26, 32, 41);">减轻服务器压力(因为使用浏览器 js 渲染)</font>
缺点
1. <font style="color:rgb(26, 32, 41);">不利于SEO优化(搜索引擎优化)搜索引擎爬虫只会爬取html,不会爬取js</font>
2. <font style="color:rgb(26, 32, 41);">第一次进入比较慢(已有按需加载策略)</font>
MVC和MVVM架构
3. <font style="color:rgb(26, 32, 41);">MVC:View接收用户行为通知Controller,Controller通知Model进行数据更新;</font>
Model通知View 进行页面更新;
Model和View进行了交互。
4. <font style="color:rgb(26, 32, 41);">MVVM:数据双向的绑定用户行为更改数据</font>
数据可以主动触发视图更新双向绑定通过ViewModel进行交互
Model和View不直接进行了交互
面试中关于MVVM及Vue.js相关问题回答:
1. 什么是MVVM?
MVVM(Model-View-ViewModel)是一种前端架构模式,核心是双向数据绑定。它通过ViewModel连接View(视图)和Model(数据模型),实现数据变化自动同步到视图,视图交互自动更新数据,开发者只需关注业务逻辑,无需手动操作DOM。
2. 为什么会出现MVVM?
- 传统MVC的局限性:早期Web应用中,View层简单,MVC能高效处理。但随着前端复杂化,DOM操作繁琐、数据同步冗余,维护成本剧增。
- 痛点解决:
- 手动DOM操作性能差,代码冗余;
- Model与View需双向同步,开发效率低;
- MVVM通过自动双向绑定,解放开发者,专注业务逻辑。
3. MVVM与MVC、MVP的区别?
- MVC:控制器(Controller)主导,View被动接收数据,需手动更新DOM。
- MVP:Presenter作为中间层,View与Model不直接交互,但仍需手动同步数据。
- MVVM:ViewModel通过双向绑定自动同步数据,View与Model完全解耦,代码更简洁。
4. Vue.js如何实现MVVM?
- 核心机制:
- 数据劫持:使用
Object.defineProperty
监听数据变化,结合观察者模式触发更新。 - 双向绑定:通过指令(如
v-model
)实现视图与数据的自动同步。
- 数据劫持:使用
- 优势:
- 轻量级,API简洁易上手;
- 专注ViewModel层,解耦业务逻辑与视图;
- 生态完善(如Vue CLI、Vuex),适合大型项目。
5. 总结MVVM的价值?
- 提高开发效率:减少DOM操作,自动处理数据同步。
- 增强可维护性:代码结构清晰,逻辑分层明确。
- 优化用户体验:高效的数据更新机制提升页面响应速度。
面试加分点:
- 举例说明:用Vue的
v-model
绑定输入框,数据修改自动同步到Model。 - 对比React:React采用单向数据流(更灵活),Vue通过双向绑定简化常见场景。
- 提到Vue3的改进:使用Proxy替代Object.defineProperty,性能更优,支持数组监听。
回答时保持自信,逻辑清晰,结合实际项目经验说明MVVM带来的收益,能让面试官更直观感受你的理解深度。
nextTick()
1. 核心作用
nextTick
** 的作用是:在下次 DOM 更新循环结束后执行延迟回调。**
- 适用场景:当你需要在数据变化后,等待 DOM 完全更新后再执行操作(例如获取新渲染的 DOM 节点、计算布局等)。
2. 常见使用场景
① 数据变化后获取最新 DOM
// Vue3 语法
import { ref, nextTick } from 'vue';export default {setup() {const count = ref(0);function handleClick() {count.value += 1;// 数据变化后,DOM 还未更新nextTick(() => {console.log(document.querySelector('.count').textContent); // 输出最新值});}return { count, handleClick };}
};
② 动画或第三方库操作
在修改数据后,通过 nextTick
确保第三方库(如 D3.js)操作的是最新的 DOM 结构。
③ 避免重复渲染
在复杂的组件中,强制同步某些操作到下一个 DOM 更新周期。
3. 原理简析
- Vue 的异步更新机制:
数据变化后,Vue 会将 DOM 更新操作放入一个队列(微任务队列),并在当前事件循环结束时批量处理。 nextTick
** 的本质**:
将回调函数追加到同一个微任务队列的末尾,确保在当前所有 DOM 更新完成后执行。
4. 与 setTimeout
的区别
特性 | nextTick | setTimeout |
---|---|---|
延迟机制 | 基于微任务队列(优先级高于主线程) | 基于宏任务队列(浏览器事件循环) |
执行时机 | 紧随当前 DOM 更新周期之后 | 至少等待 1ms 后执行 |
性能开销 | 更轻量,适合频繁操作 | 较大,不适合高频调用 |
5. 注意事项
- Vue2 vs Vue3:
- Vue2 中
this.$nextTick()
是实例方法,Vue3 需要通过import { nextTick }
使用。
- Vue2 中
- 错误使用场景:
不要滥用nextTick
,仅在必要时(如依赖 DOM 状态的操作)使用。 - 组合式 API:
在setup()
中直接使用nextTick()
,无需通过this
。
6. 总结模板
Vue3 的 nextTick
用于在 DOM 更新完成后 执行回调函数,解决数据变化后操作旧 DOM 的问题。
- 典型场景:获取动态更新的 DOM 值、第三方库集成、动画同步。
- 原理:利用微任务队列,确保回调在当前事件循环的末尾执行。
- 优势:相比
setTimeout
更高效,延迟更低。 - 示例:
// 数据变化后立即触发更新,nextTick 回调在 DOM 变动后执行
count.value++;
nextTick(() => console.log('DOM 已更新'));
面试官可能的追问
- “为什么
nextTick
要设计成异步?”
→ 异步更新能提高性能,避免不必要的重复渲染(批量处理多个数据变更)。 - “能否解释微任务队列的作用?”
→ 微任务优先级高于主线程任务,确保nextTick
的回调在当前事件循环内完成。 - “如果在
nextTick
中再次修改数据,会怎样?”
→ 新的数据变更会被合并到同一个更新周期,不会额外触发多次nextTick
。
v-show和v-if的区别
<font style="color:rgb(26, 32, 41);">v-if</font>
是“真正”的条件渲染,元素可能根本不会被渲染。<font style="color:rgb(26, 32, 41);">v-show</font>
总是会被渲染并保留在DOM中,只是简单地切换CSS的<font style="color:rgb(26, 32, 41);">display:none</font>
属性。- 如果需要非常频繁地切换,则使用
<font style="color:rgb(26, 32, 41);">v-show</font>
较好;如果在运行时条件很少改变,则使用<font style="color:rgb(26, 32, 41);">v-if</font>
较好。
父子组件,兄弟组件的通信
- 父子组件:props 和 emit
- 兄弟组件:全局状态管理 pinia
登录鉴权
两种类型,我做的是第一种
- 角色和自定义权限:
- 角色:即职位,不同的职位在系统中是由固有权限的。
- 自定义权限:除了职位之外还有一些特殊的权限,也需要划分。
- 角色和数据字典的实现:
- 数据字典:所有权限下在数据字典中预先定义好,等待角色的匹配。
- 角色:即职位,不同的角色对应数据字典中匹配的内容,用户一旦分配了角色,就具有了相应的数据字典中的权限。
vue 响应式原理(数据双向绑定)
vue2 响应式
响应式/数据双向绑定原理:(回答)【下面是 vue2】
主要
数据劫持
- 利用defineProperty或proxy为所有的数据添加get和set方法
- 在
<font style="color:rgb(26, 32, 41);">_proxyData</font>
中,为每个数据属性定义了 getter 和 setter。- 用订阅发布模式,观察数据,只要数据有变化,则通知所有订阅者更新自己的模板。这个实现分为:依赖收集和派发更新这两步。
依赖收集
1. <font style="color:rgb(26, 32, 41);">当数据属性被访问时,getter 被触发,进行依赖收集。</font> 2. <font style="color:rgb(26, 32, 41);">在 </font>`<font style="color:rgb(26, 32, 41);">_compile</font>`<font style="color:rgb(26, 32, 41);"> 方法中,为每个插值表达式创建了一个 Watcher(订阅者),并将该 Watcher 添加到 </font>`<font style="color:rgb(26, 32, 41);">_dep</font>`<font style="color:rgb(26, 32, 41);"> 对象(订阅者列表)中。</font>
派发更新
3. <font style="color:rgb(26, 32, 41);">当数据属性值发生变化时,setter 被触发,调用</font><font style="color:rgb(26, 32, 41);"> </font>`<font style="color:rgb(26, 32, 41);">_notify</font>`<font style="color:rgb(26, 32, 41);"> </font><font style="color:rgb(26, 32, 41);">方法。</font> 4. `<font style="color:rgb(26, 32, 41);">_notify</font>`<font style="color:rgb(26, 32, 41);"> 方法遍历订阅者列表,调用每个订阅者的 </font>`<font style="color:rgb(26, 32, 41);">update</font>`<font style="color:rgb(26, 32, 41);"> 方法,从而更新视图。</font>
- 对文本框监听,达到双向绑定的效果(问双向绑定再答这点)
- 补充:vue3 的区别
- Vue 3使用Proxy实现数据劫持,支持set和get。
- 区别:
- defineProperty:不支持新属性set/get。
- Proxy:支持为新属性添加set/get。
创建MyVue实例,指定根节点ID和数据对象。
MyVue类实现:
- 数据存储在this.$data。
- 实现数据劫持,递归处理所有属性,绑定set和get。
- 代理数据,将数据作为属性绑定到MyVue实例。
- 模板编译:解析{{}},按节点类型替换文本。
- 实施发布订阅模式:
- 数据变动时创建订阅者。
- Dep管理订阅者列表,触发watcher回调。
- 在get方法中,将watcher添加到订阅者数组。
- 数据更新时,通过notify通知所有watcher,更新DOM。
- 输入框变化处理:
- 绑定v-model值。
- 监听input事件,同步更新模板数据。
补充:
- Vue 3使用Proxy实现数据劫持,支持set和get。
- 区别:
- defineProperty:不支持新属性set/get。
- Proxy:支持为新属性添加set/get。
示例代码:
// MyVue类
class MyVue {constructor(options) { this.$el = document.getElementById(options.el); this.$data = options.data; this._proxyData(this.$data);this._compile(this.$el);}// 数据代理,将data的属性代理到M yVue实例上_proxyData(data) { Object.keys(data).forEach(key => { Object.defineProperty(this, key, {enumerable: true,//可枚举configurable: true,//可配置get() {return data[key];},set(newValue) {data[key] = newValue; // 当数据变化时,通知订阅者this._notify(key);}});});}// 编译模板,替换插值表达式(匹配插值表达式,提取变量名,将插值表达式替换为对应的数据值)_compile(el) {const nodes = el.childNodes;nodes.forEach(node => {if (node.nodeType === 3) { // 文本节点const textContent = node.textContent;const reg = /\{\{(.+?)\}\}/g;if (reg.test(textContent)) { const key = RegExp.$1.trim();node.textContent = textContent.replace(reg, this[key]);//通过正则表达式进行匹配,将{{variable}}替换为数据值// 创建watcher,订阅数据变化new Watcher(this, key, (newValue) => {node.textContent = textContent.replace(reg, newValue); });}}});}// 通知所有订阅者数据已更新_notify(key) {const watchers = this._dep[key];if (watchers) {watchers.forEach(watcher => watcher.update());}}// 添加订阅者$watch(key, callback) {if (!this._dep[key]) {this._dep[key] = [];}this._dep[key].push(new Watcher(this, key, callback)); }
}// 订阅者类
class Watcher {constructor(vm, key, callback) {this.vm = vm;this.key = key;this.callback = callback;// 触发依赖收集this.vm[this.key];}// 当数据变化时,更新视图update() {this.callback(this.vm[this.key]);}
}// 使用MyVue
const app = new MyVue({el: 'app',data: {message: 'Hello, MyVue!' }
});// 模拟数据变化
setTimeout(() => {app.message = 'Hello, World!'; // 这将触发视图更新
}, 2000);
vue3 响应式
以下是针对Vue3响应式系统的结构化面试回答建议,结合原理、实现和设计思想,适合中高级前端岗位面试:
Vue3响应式系统核心回答模板:
Vue3的响应式系统基于Proxy实现,通过依赖收集(track)和触发更新(trigger)的机制实现数据的自动响应。其核心可分为三个部分:
1. 核心机制
- 响应式对象:使用
Proxy
代理目标对象,拦截get
/set
/deleteProperty
等操作 - 依赖收集:在
get
操作时,通过track
函数收集当前运行的effect
(副作用) - 触发更新:在
set
操作时,通过trigger
函数通知所有关联的effect
重新执行
2. 核心实现三要素
a. 依赖关系(targetMap): - 存储结构:WeakMap →(key:) target(原始对象)└→(value:) Map →(key:) key(属性名)└→(value:) Set(存储对应的effect集合)- 作用:追踪依赖关系,性能优化(只有属性被访问时才会被添加到targetMap)响应式对象(reactiveMap):- 存储结构:WeakMap →(key:) target(原始对象)└→(value:) Proxy (代理对象)- 作用:reactiveMap帮助Vue3避免重复包装同一个对象使用WeakMap避免内存泄漏,当目标对象不再被引用时自动回收b. 副作用管理(effect): - 通过`effect(fn)`注册副作用函数- 执行时通过全局`activeEffect`标记当前effect- 支持嵌套effect(通过effect栈实现)c. Proxy拦截器: - get:触发`track`,递归处理嵌套对象- set:值变化时触发`trigger`- deleteProperty:处理属性删除的响应
3. 关键升级(对比Vue2)
- 性能优化:
- Proxy直接代理对象,无需递归初始化(懒代理)
- 精准触发更新(基于属性key的依赖收集)
- 原生支持Map/Set等ES6数据结构
- 能力增强:
- 支持数组索引修改、length修改的监听
- 支持动态添加/删除属性(无需Vue.set/delete)
- 更好的TypeScript类型支持
4. 相关API实现原理
- ref:通过
.value
访问时触发依赖收集,返回带有__v_isRef
标记的对象 - computed:基于effect的懒求值实现,带有缓存机制
- watch:基于scheduler调度器的effect封装
5. 设计亮点
- 响应式系统独立为
@vue/reactivity
包,可单独使用 - 基于ES6数据结构的响应式存储(Map/Set的代理处理)
- 批量更新优化(通过调度器合并多次修改触发的effect)
6. 回答示例(口语化精简版)
"Vue3的响应式系统基于Proxy实现,通过三个核心步骤:
- 用Proxy代理对象,在get时收集依赖(当前运行的effect)
- 在set时对比新旧值,触发关联effect重新执行
- 通过WeakMap→Map→Set的三层结构高效管理依赖关系
对比Vue2的Object.defineProperty方案,它解决了数组监听、动态属性添加等问题,性能更好且支持更多数据结构。"
高频追问问题预备答案:
- 为什么用Proxy代替defineProperty?
- 直接监听对象而非属性,无需递归初始化
- 支持数组索引修改、length变化、对象属性增删
- 更好的性能表现(尤其在大型对象上)
- 如何处理嵌套对象的响应式?
“在Proxy的get拦截中,当发现属性值是对象时,会递归调用reactive()
进行代理,并返回代理后的对象,实现深层响应” - WeakMap的作用是什么?
“WeakMap的键是原始对象(不被强引用),当原始对象不再被使用时,能自动垃 p 圾回收关联的依赖映射,避免内存泄漏”
是依赖收集的容器:键是对象,值是一个映射(键是属性,值是副作用函数集合)(WeakMap 结构:target(目标对象) -> Map<key(属性), Set(副作用函数集合)>) - Vue3如何处理数组的push/pop等方法?
“重写了数组的变异方法(如push/pop),在方法执行后手动触发trigger,同时代理的数组的length属性变更也能被监听到” - 如何避免重复触发effect?
- 在trigger时使用Set结构自动去重
- 调度器(scheduler)支持批量更新(如多次同步修改合并为一次更新)
加分项:
- 提到响应式系统的
调度器(scheduler)
机制(用于控制effect执行时机) - 解释
Ref
的实现原理(通过对象包装+.value
的getter/setter) - 讨论响应式系统的边界情况(如循环依赖、大对象性能问题)
- 提及源码中的
createReactiveObject
函数(代理对象缓存机制)
通过这种结构化的回答,既能展示对原理的深度理解,又能体现系统性思维,适合中高级岗位面试。建议根据实际项目经验补充具体使用场景或优化实践。
// 保存当前激活的 effect
let activeEffect = null// 依赖收集的容器(WeakMap 结构:target(目标对象) -> Map<key(属性), Set<effect>(副作用函数集合)>)
const targetMap = new WeakMap()// 响应式处理函数
function reactive(target) {// 使用 Proxy 拦截并响应对象属性的变化return new Proxy(target, {get(target, key, receiver) {// 调用 track 函数进行依赖收集track(target, key)// 如果属性值是对象,则递归进行响应式处理if (typeof target[key] === 'object' && target[key] !== null) {return reactive(target[key])}// 获取属性值return Reflect.get(target, key, receiver)},set(target, key, value, receiver) {// 获取旧值const oldValue = target[key]// 设置新值const result = Reflect.set(target, key, value, receiver)// 如果新旧值不相等,则调用 trigger 函数触发更新if (oldValue !== value) {trigger(target, key)}return result}})
}// 依赖收集函数
function track(target, key) {// 如果没有激活的 effect,则不进行依赖收集if (!activeEffect) returnlet depsMap = targetMap.get(target)if (!depsMap) {console.log("没有depMap");// 如果 target 上没有依赖映射,则创建并添加到 targetMaptargetMap.set(target, (depsMap = new Map()))}let dep = depsMap.get(key)if (!dep) {console.log("没有dep");// 如果 key 上没有依赖集合,则创建并添加到 depsMapdepsMap.set(key, (dep = new Set()))}// 将当前激活的 effect 添加到依赖集合中dep.add(activeEffect)console.log("1targetMap:",targetMap);console.log("1depsMap:",depsMap);console.log("1dep:",dep);
}// 触发更新函数
function trigger(target, key) {// 获取 target 对应的依赖映射const depsMap = targetMap.get(target)if (!depsMap) returnconst dep = depsMap.get(key)if (dep) {// 遍历依赖集合并执行每个 effectdep.forEach(effect => effect())}console.log("2targetMap:",targetMap);console.log("2depsMap:",depsMap);console.log("2dep:",dep);
}// 副作用函数
function effect(fn) { //fn是副作用函数,会立即执行即使没有依赖// 将当前函数设置为激活的 effectactiveEffect = fn// 执行 effect,触发依赖收集fn() //会立即执行// 重置激活的 effectactiveEffect = null
}// 使用示例
const state = reactive({ count: 0, nested: { a: 1 } })// 创建并执行一个响应 count 变化的 effect
effect(() => {console.log('count changed:', state.count)
})// 创建并执行一个响应 nested.a 变化的 effect
// effect(() => {
// console.log('nested.a changed:', state.nested.a)
// })// 修改 count,触发相应的 effect
state.count++
// 修改 nested.a,触发相应的 effect
state.nested.a++
如何改 bug
- 入职:
有人和你交接工作:很重要:- git 把项目拉下来 跑起来
- axios的测试文件 测通接口
- 快速熟悉项目 按照路由表说一遍
不要相信 :老板说熟悉几天代码。第二天 老板喊改几个bug
- **配置类型bug
例如: no found …这就是加载的方式不对 import
**看变量名,地址有没有写对 - 版本问题:
例如:出现./element-plus/lib/index.js 4592
解决办法:1. 修改 package 版本,直接修改大版本,比修改小版本快(修改后是重新安装依赖就好了吗?)。若这个未能解决,去 git 拉取早期项目 的版本(没有试过)
- **代码 BUG:**拷贝一份,逐层(按照模块)去还原,保证每一次粘贴还原的正确性,若还原有错修复即可。
其他方法:查资料、看视频、问 AI。
插槽
:::info
- 解释插槽的概念:插槽用于将内容插入到指定位置,样式和状态由父组件控制,内容由子组件控制。
- 介绍插槽的类型:
- 默认插槽:简单的内容传递。
- 具名插槽:多个插槽,允许灵活控制内容位置。
- 作用域插槽:父组件可以访问子组件中的数据。
:::
1. 概念
- 作用:父组件传递内容到子组件的指定位置,子组件控制位置,父组件控制样式和状态。
- 特点:内容由父组件定义,子组件决定渲染位置。
2. 插槽类型及示例
默认插槽
- 用途:单一内容传递,无复杂结构。
- 子组件:
<template><div class="modal"><slot>默认提示(父组件未传内容时显示)</slot></div>
</template>
- 父组件:
<Modal><p>这是父组件插入的内容</p>
</Modal>
具名插槽
- 用途:多个插槽,精准控制不同位置。
- 子组件:
<template><div class="card"><slot name="header"></slot><slot></slot> <!-- 默认插槽 --></div>
</template>
- 父组件:
<Card><template v-slot:header><h2>卡片标题</h2></template><p>卡片内容(插入默认插槽)</p>
</Card>
- 简写:
<font style="background-color:rgb(252, 252, 252);">#header</font>
代替<font style="background-color:rgb(252, 252, 252);">v-slot:header</font>
。
作用域插槽
- 用途:父组件访问子组件内部数据。
- 子组件:
<template><ul><li v-for="item in items" :key="item.id"><slot :item="item">{{ item.name }}</slot></li></ul>
</template>
- 父组件:
<List :items="items"><template v-slot:default="slotProps">{{ slotProps.item.name }}(ID: {{ slotProps.item.id }})</template>
</List>
- 解构:
<template #default="{ item }">{{ item.name }} - {{ item.price }}
</template>
3. 对比总结
类型 | 特点 | 语法示例 |
---|---|---|
默认插槽 | 单一内容,无命名 | <font style="background-color:rgb(252, 252, 252);"><slot></font> |
具名插槽 | 多插槽,需命名 | <font style="background-color:rgb(252, 252, 252);"><slot name="header"></font> |
作用域插槽 | 子组件向父组件传递数据 | <font style="background-color:rgb(252, 252, 252);"><slot :data="childData"></font> |
总结:插槽实现组件内容分发,默认插槽处理简单内容,具名插槽管理多位置,作用域插槽打通父子数据传递。
混入(mixins)
keep-alive
<keep-alive>
是一个用于缓存组件状态的内置组件,它可以避免重复渲染,并可以直接在模板中使用。
- 优势
- 在组件切换过程中,能够将组件状态保存在内存中,从而防止DOM的重复渲染,显著提升应用性能。
- 使用方法
- 在
<router-view>
的插槽中使用<keep-alive>
来包裹组件。
<router-view v-slot="{ Component }"><keep-alive><component :is="Component" /></keep-alive>
</router-view>
- 缓存单个页面
include
: 通过指定组件的name
属性来缓存特定的页面。exclude
: 用于排除指定的name
属性页面,其他页面将被缓存。max
: 设置缓存组件的最大数量。
- 新增钩子函数
activated
: 组件被激活时调用。deactivated
: 组件停用时调用。
- 生命周期钩子顺序
- 进入页面时:
created
activated
- 离开页面时:
deactivated
→ 离开上一个组件 s
vue 源码
vue3 性能比 vue2 好,为什么?
答:
- 性能提升:兼容vue2,且从打包、渲染、内存使用上均有所提升****
- 定义变量和方法:通过setup来定义数据和方法,读取和调用更快****
- 双向数据绑定:使用Proxy替换了defineProperty****
- 性能优化:vue3在依赖收集、程序运行时方面做了性能优化,另外改进了响应式系统,还加入了异步渲染和懒加载的特性
- 性能提升:
vue3支持vue2向下兼容
官方测试:vue3比vue2打包快了41% ; 首次渲染55% 内存使用50%
vue3 在一些api 和基础的方法 上进行优化 - 定义变量和数据:
vue2 data数据 methods方法
vue3 setup函数 变量产生程序副作用进行优化 - 双向绑定:
vue2 Object.defineProperty+发布订阅模式实现的缺点:数组和新增属性需要开销额外代码
vue3 proxy api 数据进行代理 整个对象(包括数组)响应式观测 数据发生变化不需要额外开销,直接响应,vue3的响应式系统效率高
小结:底层设计有明显差异,vue3数据绑定 函数 方法定义 比vue2提升了很多
中大型项目 vue3比较合适
vue的代理和环境变量
答题攻略:
代理:在接口数据请求中,由于常常违反同源策略,所以在开发和生产中,都需要配置代理
创建vue.config.js,添加devServer配置项以及其参数
环境变量:开发中使用开发环境,打包上线后使用生产环境
- 开发环境:.env.development
- 生产环境:.env.production
调用:process.env.环境变量名称
注意:vue可以自动识别开发和生产环境
数据发生变化,(DOM)视图没有更新
产生原因:
vue2 中 defineProperty() 进行的数据劫持,而它不能对后边更新的数据进行操作
解决:
- this.$set()
- this.$forceUpdate() 强制更新
- 深拷贝
大文件切片上传
字节跳动面试官:请你实现一个大文件上传和断点续传这段时间面试官都挺忙的,频频出现在博客文章标题,虽然我不是特别想蹭热度, - 掘金
全网最全面的"大文件上传" - 前端:Vue3+TS+Vite, 后端:node+express背景 一般情况下,上传文 - 掘金
“保姆级”大文件上传教程 :切片上传 + 断点续传 + 秒传 + 暂停上传-CSDN博客
vue 自定义指令
在按钮级的权限管理时会用到,需要搭配字典使用。
详细再学习
前端组件级权限控制的实践
前言
在后台系统开发中,前端处理权限控制完全是气氛组,最终的权限校验还是要依赖后端的处理。前端的权限控制分为三个主要级别:
权限控制一般有三个级别
- 页面级:通过路由守卫和动态路由来限制访问,用户没有权限无法看到这个菜单或页面,细粒度较低。
- 组件级:根据用户角色和权限显示不同的组件或内容。例如,管理员可以看到更多的数据,而普通用户只能看到基本信息。这种方法具有一定的侵入性。
- 代码级:精确到函数调用权限控制,虽然不常见,但某些场景下可能会用到。
组件级别的权限控制
在组件级别,我们的目标是尽量减少权限控制对组件的侵入性。理想情况下,权限控制应该只影响组件的可见度或统一状态,而不是内部逻辑。真正的权限校验仍然需要由后端进行。
示例:根据角色和权限展示不同的组件
假设我们有以下组件按钮:
<div class="btn-container"><a-button type="primary">新增用户</a-button><a-button type="primary">查看用户</a-button> <a-button type="primary">修改用户</a-button> <a-button type="primary" danger>删除用户</a-button> <a-button type="primary" danger>禁用用户</a-button>
</div>Copy
如果权限仅影响组件的可见度,我们可以使用权限组件或自定义指令来包裹这些操作按钮。
权限组件
我们可以创建一个 **Authority**
组件来控制按钮的显示:
<Authority permissions="sys:user:add"><a-button type="primary">新增用户</a-button>
</Authority>Copy
单个权限
只有当用户拥有 **sys:user:add**
权限时,按钮才会显示。
如果用户没有某个权限,那么就不会显示出来。
可以看到,添加按钮没了。因为 Visitor 角色没有 **sys:user:add**
权限。
多个权限
如果某个按钮需要多个权限才能显示:
<Authority :permission="['sys:user:view', 'sys:user:update']"><a-button type="primary" danger>禁用用户</a-button>
</Authority>Copy
我们之所以能够减少权限对组件的侵入性是因为权限关联到了组件的统一状态,都是可见和不可见的区别。
_ 权限作用域_
如果你希望组件能够自行处理权限控制,可以使用 **Authority**
组件的作用域插槽:
<Authority><template #default="{ userPermissions }"><a-button :disabled="userPermissions.includes('sys:user:add')">新增用户</a-button></template>
</Authority>Copy
Authority 组件
**Authority**
组件的实现如下,接收权限 **props**
并根据权限计算属性控制插槽内容的显示:
<template><slot v-if="showSlot" :userPermissions="permissions"><!-- 按钮 --></slot>
</template><script setup lang="ts">
interface Props {permission: string[];
}
const props = withDefault(defineProps<Props>(), {permission: () => [],
});// 使用 pinia 来存放后端返回的权限
const { permissions } = useAuth();const showSlot = computed(() => {// 没有传入权限,直接显示if (!props.permission) return true;if (!permissions) {return false;}// 如果插槽按钮需要多个权限才能显示if (Array.isArray(props.permission)) {// 判断父组件传过来的 permission 是不是当前用户权限拥有的return props.permission.every(p => permissions.include(p));} else {// 如果父组件传过来单个权限return permissions.value.includes(props.permission);}
});
</script>Copy
总结
尽管前端的权限控制可以提升用户体验,但真正的安全保障仍然需要依靠后端来进行权限验证如使用 Token 守卫和角色权限守卫。前端的控制只是辅助工具(完全是气氛组!)。
长时间保存token的安全问题:
问题: token 本地存储 header-auth…都可以查看到
解决token泄露:
- 加密存储token crypto.js md5…
- 令牌绑定:token+设备/用户的特定信息,泄露了 也没办法在其他设备上使用
- 定期更换token 有效期 自动更换token
- 手机验证码登录
处理大量列表数据
1️⃣分页 2️⃣上拉加载 3️⃣虚拟列表
https://www.bilibili.com/video/BV1dT421k7DK?t=317.8&p=49
虚拟列表
使用vue-virtual-scroller
库
在 Vue3 + Element Plus 中实现虚拟列表处理大规模数据,可通过以下步骤完成:
一、技术方案选型
推荐库:vue-virtual-scroller
(轻量级、高性能)
npm install vue-virtual-scroller
二、基础实现示例
<template><RecycleScroller class="scroller":items="largeList" <!-- 数据源数组 -->:item-size="32" <!-- 单项高度(固定值更高效) -->:key-field="'id'" <!-- 唯一标识字段 -->v-slot="{ item }" <!-- 作用域插槽 -->><div class="list-item"><el-card :title="item.title" style="height: 32px; margin-bottom: 8px;">{{ item.content }}</el-card></div></RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
// 示例数据(实际应从API获取)
const largeList = Array.from({ length: 10000 }, (_, i) => ({
id: i,
title: 列表项 ${i}
,
content: 这是第${i}个虚拟列表项
}))
</script>
<style>
.scroller {
height: 600px; /* 容器高度 /
overflow-y: auto; / 启用滚动 */
}
.list-item {
transition: transform 0.3s ease;
}
</style>
三、进阶优化技巧
1. 动态高度处理(适用于不固定高度项)
<template>
<RecycleScroller
:items=“largeList”
v-slot=“{ item, index }”
>
<div
class=“dynamic-item”
:ref=“setItemRef” <!-- 用于测量实际高度 -->
>
{{ item.content }}
</div>
</RecycleScroller>
</template>
<script setup>
import { onMounted, ref } from ‘vue’
import { RecycleScroller } from ‘vue-virtual-scroller’
const itemRefs = ref([])
const dynamicItemHeights = ref([])
onMounted(() => {
// 动态计算所有项的高度
itemRefs.value.forEach((el, index) => {
dynamicItemHeights.value[index] = el.offsetHeight
})
})
// 在RecycleScroller中动态设置itemSize
</script>
<style>
.dynamic-item {
min-height: 32px; /* 保证最小高度 */
}
</style>
2. 滚动事件监听
<template>
<RecycleScroller
@scroll=“handleScroll”
>
<!-- 列表内容 -->
</RecycleScroller>
</template>
<script setup>
import { defineProps } from ‘vue’
const handleScroll = (event) => {
const { scrollTop, scrollHeight, clientHeight } = event.target
const isLoading = scrollTop + clientHeight >= scrollHeight - 100
if (isLoading) {
loadMoreData() // 加载更多数据
}
}
</script>
四、与 Element Plus 组件集成
1. 在 el-table
中使用虚拟列表
<template>
<el-table
v-virtual-scroll=“{ itemHeight: 50 }”
:data=“tableData”
>
<!-- 列定义 -->
</el-table>
</template>
<script setup>
import { ElTable } from ‘element-plus’
import { vVirtualScroll } from ‘element-plus/virtual-scroll’
defineComponent({
components: { ElTable },
directives: { vVirtualScroll }
})
</script>
2. 自定义 el-tree
虚拟滚动
<template>
<el-tree
:data=“treeData”
:virtual-scroll=“{ nodeHeight: 32 }”
node-key=“id”
>
</el-tree>
</template>
<script setup>
import { ElTree } from ‘element-plus’
import { vVirtualScroll } from ‘element-plus/virtual-scroll’
defineComponent({
components: { ElTree },
directives: { vVirtualScroll }
})
</script>
五、性能对比数据
组件类型
渲染1万条数据耗时
内存占用
普通渲染
~800ms
~120MB
Virtual Scroller
~120ms
~35MB
六、注意事项
- 强制固定尺寸:尽可能为列表项设置固定高度/宽度
- 避免DOM操作:在虚拟列表区域内不要使用
v-if
频繁切换节点 - 事件委托:将事件监听绑定到容器而非单个列表项
- 懒加载组件:对于复杂子组件使用
defineAsyncComponent
七、推荐生产方案组合
npm install vue-virtual-scroller element-plus
<template>
<RecycleScroller
class=“h-screen”
:items=“data”
:item-size=“48”
>
<el-card
v-if=“item.visible”
:key=“item.id”
>
<!-- 复杂内容 -->
</el-card>
</RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from ‘vue-virtual-scroller’
import { onMounted } from ‘vue’
// 按需加载数据
const data = ref(await fetchLargeData())
</script>
通过合理使用虚拟滚动技术,可以显著提升大数据量场景下的渲染性能,建议在数据量超过 1000 条时优先采用该方案。
首页性能提升
1. 缓存组件
Vue 提供了组件级缓存的功能,特别是使用 <font style="color:rgb(26, 32, 41);">keep-alive</font>
标签时,可以缓存不活动的组件实例,而不是销毁它们。这对于那些不需要频繁重新渲染的组件(如静态内容的页面)非常有用。
<keep-alive><component :is="currentComponent" />
</keep-alive>
使用 <font style="color:rgb(26, 32, 41);">keep-alive</font>
可以减少组件的创建和销毁过程,从而提高性能。
2. 图片压缩处理
在 Vue 应用中,图片压缩可以通过以下方式实现:
- 在构建过程中使用图像压缩工具,如
<font style="color:rgb(26, 32, 41);">image-webpack-loader</font>
。 - 使用现代图像格式,如 WebP,它通常比 JPEG 或 PNG 格式更小,但质量相似。
// 在 Vue 组件中使用 WebP 格式图片
<img :src="image.webp" alt="描述" />
3. 减少重绘和回流
在 Vue 中,减少重绘和回流可以通过以下方式:
- 使用
<font style="color:rgb(26, 32, 41);">v-show</font>
而不是<font style="color:rgb(26, 32, 41);">v-if</font>
来切换元素的显示和隐藏,因为<font style="color:rgb(26, 32, 41);">v-show</font>
只会触发重绘,而<font style="color:rgb(26, 32, 41);">v-if</font>
可能会触发回流。 - 避免在循环中使用复杂的表达式或方法。
- 使用
<font style="color:rgb(26, 32, 41);">requestAnimationFrame</font>
来批量更新 DOM。
<!-- 使用 v-show 来切换显示 -->
<div v-show="isShown">内容</div>
- 使用
transform
代替位置尺寸变化
使用 CSS 的 transform
属性(如 translate
, scale
, rotate
)可以避免触发重排和重绘,因为 transform
操作是在合成器线程上执行的,而不是主线程。
**示例:**假设你想移动一个元素,而不是改变其 top
或 left
属性,可以使用 transform
:
/* 不推荐:会触发重排 */
.box {position: absolute;top: 50px;left: 100px;
}
/* 推荐:不会触发重排 */
.box {position: absolute;transform: translate(100px, 50px);
}
在上面的例子中,使用 transform: translate(100px, 50px);
代替直接修改 top
和 left
属性,可以避免重排。
4. 使用 CDN 加速
使用 CDN 加速可以减少资源的加载时间。在 Vue 应用中,可以将静态资源部署到 CDN 上,并在应用配置中指定资源链接。
// 在 Vue 应用的配置文件中设置 publicPath
module.exports = {publicPath: 'https://your-cdn-url.com/'
}
5. 路由、图片懒加载
Vue 路由和图片的懒加载可以通过以下方式实现:
- 使用 Vue Router 的路由懒加载功能。
const Home = () => import('./components/Home.vue')
- 对于图片,可以使用
<font style="color:rgb(26, 32, 41);">v-lazy</font>
指令或第三方库来实现懒加载。
<img v-lazy="imageSrc" alt="描述" />
6. 启用 gzip 压缩
启用 gzip 压缩通常是在服务器配置中完成的。在 Vue 应用部署时,确保服务器(如 Nginx 或 Apache)配置了 gzip 压缩。
# 在 Nginx 配置文件中启用 gzip
gzip on;
gzip_types text/plain application/xml application/javascript text/css;
通过以上方法,可以在 Vue.js 应用中显著提升首页的性能。这些优化措施可以减少应用的加载时间,提高用户体验,并有助于搜索引擎优化。
前端性能优化实用方案总结
一、首屏加载优化
首屏加载速度直接影响用户体验,优化核心是减少资源体积和缩短白屏时间。
- 减少首屏资源体积
- 打包工具压缩:利用 Webpack/Vite 等工具的代码压缩(如 Terser)、Tree Shaking(剔除未使用代码)。
- 异步加载非关键资源:对体积大且非首屏必需的功能(如图片压缩库)使用动态导入(
import()
)或懒加载。 - 升级支持 Tree Shaking 的库:替换老版本第三方库为新版本(如将
xlsx
库从 0.1 升级到 0.18),减少无用代码打包。 - 避免过度依赖第三方库:简单功能尽量自行实现(如日期格式化),减少引入大体积库。
- 避免大资源内联为 Base64:仅对小图标使用 Base64,大图片通过
<img>
标签异步加载。
- 优化加载过程体验
- 骨架屏与 Loading 遮罩:在 HTML 中预渲染页面结构骨架,减少用户等待焦虑。
- 合并小接口请求:将多个小接口合并(如用户信息与配置信息),减少 HTTP 握手次数。
- 并行请求首屏数据:避免串行请求,利用 Promise.all 或并发加载。
二、操作与渲染速度优化
针对页面交互卡顿和重复渲染问题,优化核心是减少 DOM 操作和计算复杂度。
- 减少 DOM 操作
- 虚拟滚动与分批次渲染:对长列表(如 10,000+ 条数据)使用虚拟滚动(如
vue-virtual-scroller
),仅渲染可视区域 DOM。 - 避免频繁操作样式:使用 CSS3 动画替代 JS 动画,减少重绘与回流。
- 虚拟滚动与分批次渲染:对长列表(如 10,000+ 条数据)使用虚拟滚动(如
- 框架级优化(Vue/React)
- **合理使用
v-show
与 **v-if
:v-show
:频繁切换显隐的组件(如弹窗),通过display
控制。v-if
:一次性渲染的组件(如条件提示),减少初始 DOM 节点数。
- **列表循环添加唯一 **
key
:避免 Diff 算法性能损耗。 keep-alive
** 缓存组件**:对频繁切换的页面(如 Tab 页)进行缓存,减少重复渲染。- 依赖收集优化(Vue3):利用响应式系统的精准更新,避免无关组件渲染。
- **合理使用
- 请求与数据策略
- 区分接口粒度:增删改查后仅更新必要数据,避免全量请求(如删除后只刷新列表,不重新请求分类)。
- 数据缓存策略:
- 持久化缓存:不变数据(如配置项)或定期失效数据(如 Token)存 localStorage。
- 内存缓存队列:高频接口数据缓存到内存对象,设置上限防止内存泄漏(如最多缓存 10 条)。
- 其他优化
- 精简复杂运算:避免前端大规模计算(如排序 10W 条数据),优先后端处理。
- 代码逻辑简化:减少嵌套循环、冗余条件判断,优化算法复杂度。
三、特殊情况优化
- 首屏数据分批加载:先渲染首屏内容(如 900px 区域),滚动时加载后续数据。
- 死循环监控:通过性能分析工具(如 Chrome DevTools)检测代码块执行时间,避免阻塞主线程。
四、面试回答技巧
- 结构化回答:按「首屏优化 → 渲染优化 → 数据策略」分层说明。
- 结合项目实例:如:“在 XX 项目中,通过将
xlsx
库升级至支持 Tree Shaking 的版本,首屏 JS 体积从 670KB 降至 91KB。” - 强调收益:量化优化效果(如白屏时间从 5s 降至 1s)。
总结:性能优化需结合具体场景,优先解决瓶颈问题(如首屏资源体积),再逐步细化到代码和框架层。保持对工具链(Webpack/Vite)和浏览器机制的理解,是持续优化的关键。