1. Vue 生命周期详解
beforeCreate → created → beforeMount → mounted → beforeUpdate → updated → beforeDestroy → destroyed
- beforeCreate 初始化vue实例
- created 组件实例已经完全创建 ,此时vm.$el还没有被创建
- beforeMount 组件挂载之前
- mounted 组件挂载到实例上去之后
- beforeUpdate 组件数据发⽣变化,更新之前 。在此阶段可获取到 vm.el 此阶段 vm.el 虽已完成DOM初始化,但并未挂载在 el 选项上
- updated 组件数据更新之后
- beforeDestroy 组件实例销毁之前
- destroyed 组件实例销毁之后
activatedkeep-alive 缓存的组件激活时- deactivated keep-alive 缓存的组件停⽤时调⽤
- errorCaptured 捕获一个来自子孙组件的错误时调用
总结口诀:
"挂前创后,更新前后,销毁前后"------八个阶段掌握生命周期。
数据请求在created和mouted的区别
- created 是在组件实例⼀旦创建完成的时候⽴刻调⽤,这时候⻚⾯ dom 节点并未⽣ 成; mounted 是在⻚⾯ dom 节点渲染完毕之后就⽴刻执⾏的。触发时机上 created 是⽐ mounte d 要更早的,两者的相同点:都能拿到实例对象的属性和⽅法。
- 讨论这个问题本质就是触发的时机,
放在 mounted 中的请求有可能导致⻚⾯闪动(因为此时⻚ ⾯ dom 结构已经⽣成),但如果在⻚⾯加载前完成请求,则不会出现此情况。建议对⻚⾯内容的改动 放在 created ⽣命周期当中。
2. 双向数据绑定
-
三个重要组合部分
- 数据层(Model):应⽤的数据及业务逻辑
- 视图层(View):应⽤的展示效果,各类UI组件
- 业务逻辑层(ViewModel):框架封装的核⼼,它负责将数据与视图关联起来
-
职责:
- 数据变化后更新视图
- 视图变化后更新数据
-
两个重要组成部分
- 监听器(Observer):对所有属性进行监听
- 解析器(Compiler):对每个元素节点的指令进⾏扫描跟解析,根据指令模板替换数据,以及绑定相应 的更新函数
-
实现:
jsnew Vue({ el: '#app', data: { message: 'Hello Vue!' } })- 初始化阶段
- 执行
new Vue()实例化,初始化data,调用observe()对数据属性进行劫持。 - 遍历
data对象,对每个属性使用Object.defineProperty()拦截 getter/setter,实现数据响应。
-
模板编译阶段
- Vue 内部调用
compile()对挂载元素el的 DOM 进行遍历,识别出指令(如{{message}}、v-model)。 - 创建 Watcher 实例并与 Observer 关联,建立依赖收集关系。
- Vue 内部调用
-
依赖收集 & 数据绑定
- 模板中每个绑定的地方,都会生成一个 Watcher,它会被添加到对应数据属性的 Dep(依赖收集器)中。
- 当数据变化时,Dep 会通知所有相关 Watcher 更新视图。
-
双向绑定实现
v-model指令绑定输入框,监听input事件,把用户输入同步到 Model。- Model 变化后,自动更新视图内容。
2. 常用指令
常用如:
v-if/v-else-if/v-else:条件渲染v-show:显示隐藏v-for:循环列表v-model:双向绑定v-bind:属性绑定v-on:事件监听
3. data 为什么是函数?
组件会被复用,每个实例的数据需要独立,函数返回新对象才不会共享内存。
4. v-if 和 v-show 区别
v-if是真删除/添加 DOM,性能开销大,适合偶尔显示v-show是display:none控制隐藏,适合频繁切换
5. v-for 为什么要加 key?
让 Vue 更快更精准对比新旧节点,避免复用错误,提升性能。 Vue 使用 虚拟 DOM(VNode) + diff 算法 来高效更新视图:
-
当数据变了,Vue 会:
- 重新生成一棵新的虚拟 DOM 树;
- 拿新旧两棵树进行"对比"(这一步叫 diff);
- 找出差异,最小化操作真实 DOM,提高性能!
6. v-if 和 v-for 同时用谁先执行?
v-for 优先,会先循环再判断 v-if。
- 在vue编译阶段,模板会被编译成"渲染函数"。
- v-for负责生产多个vnode(虚拟DOM)。
- v-if 负责判断是否渲染这个节点
- 也就是说要先把节点遍历出来再一个个判断v-if。想优化性能,先过滤数据,不推荐在v-for里加v-if
7. computed 和 watch 区别
| 特性 | computed | watch |
|---|---|---|
| 缓存 | ✅有缓存 | ❌无缓存 |
| 用途 | 计算派生数据 | 监听执行逻辑(如异步) |
- computed:根据已有数据推算出新的值
- watch:监听现有数据做任务
8. nextTick 原理
Vue 异步更新 DOM,nextTick 会在 DOM 更新完之后执行回调,本质是微任务(如 Promise.then())。
- vue更新DOM是异步的,当修改数据后,DOM不是马上就变,如果立即访问DOM,拿到的还是旧内容,nextTick(fn)就是告诉vue,等DOM更新完了再执行这个函数
扩展补充:js时间循环机制:同步任务->微任务->宏任务
| 类型 | 常见 API 举例 |
|---|---|
| 宏任务 | setTimeout、setInterval、setImmediate(Node.js)、UI 渲染、I/O 操作 |
| 微任务 | Promise.then/catch/finally、queueMicrotask、MutationObserver |
9. 模板编译原理
template → AST 抽象语法树 → render 函数 → 虚拟 DOM → 真正 DOM。
10. 数据响应式原理
Vue2 用 Object.defineProperty() 实现响应式。Vue3 用 Proxy 更强大。 和vue3的区别
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 模板编译 | runtime 编译 | 更强的 compile-time 优化 |
| AST 结构 | 基本节点 | 静态提升、patchFlag |
| 响应式 | Object.defineProperty | Proxy |
| Virtual DOM | 重度依赖 | 更轻量,block tree |
11. 数组响应式方法
Vue 重写了数组原型方法,如 push、splice,确保这些方法可以触发视图更新。 以下操作不能自动触发视图更新:
- 通过索引修改数组:
less
js
复制编辑
list.value[1] = 999 // ❌ 不会触发视图更新(Vue2 里不行,Vue3 可以)
-
Vue 2 的限制:数组不能通过索引添加/修改时触发响应式更新
-
Vue 3 用 Proxy 修复了这个问题
-
问题根本:
- vue2使用的是OBject。defineProperty()来实现响应式
jsObject.defineProperty(obj,'key',{ get(){}, set(){} }) // 它只能劫持对象已有的属性,对于 新增属性 或者 数组索引赋值 无能为力但是它只能劫持对象已有的属性 ,对 新增属性 或 数组索引赋值 无能为力,比如:
inijs 复制编辑 const arr = [1, 2, 3] arr[1] = 100 // 改变了数组第二项,但 Vue 2 无法拦截这个动作 arr[3] = 999 // 新增索引,Vue 2 不知道 arr.length = 1 // 改变长度,Vue 2 也不知道
Vue 2 是通过重写数组原型方法 来实现响应式(比如重写 push),但这只能拦截函数,不能拦截 arr[i] = xxx 这种直接操作。
-
Vue3 使用了ES6的 Proxy "
jsconst p = new Proxy(target,{ get(target,key,receiver){...} set(target, key, value, receiver) { ... }, deleteProperty(target, key) { ... } }) // 1. 胁持了所有属性的读取/修改 // 2. 能劫持数组索引修改 // 3. 能劫持length改变 // 4. 能劫持**in操作符**和**delete**操作 -
Proxy 是怎么解决Vue2无法响应的问题的?
js
const arr = reactive([1,2,3])
arr[1] = 100;
arr[3] = 999;
arr.length =1;
// vue3内部会对数组进行以下处理
const prosy = new Proxy(arr,{
get(target,key){
// 依赖收集,(触发track)
return Reflect.get(target,key)
}),
set(target, key, value) {
// 派发更新(触发 trigger)
const result = Reflect.set(target, key, value)
// 如果 key 是数组索引或 length,触发视图更新
return result
}
})
12. Vue2 实例挂载流程
new Vue() 调用 _init → 合并选项 → 初始化生命周期、事件、数据 → $mount() → 编译模板 → 渲染 DOM。
- 一句话:初始化数据(响应式)-> 编译模板(template)-> 渲染成虚拟DOM(vNode) -> 生成真是DOM挂载
🧩 完整流程图解(带源码点位)
以如下代码为例:
css
js
复制编辑
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
template: '<div>{{ message }}</div>'
})
🔍 详细执行步骤
① 构造函数初始化 new Vue(options)
源码入口:
scss
js
复制编辑
// src/core/instance/init.js
Vue.prototype._init = function (options) {
...
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate') // 生命周期钩子1
initInjections(vm)
initState(vm) // 核心:数据响应式处理
callHook(vm, 'created') // 生命周期钩子2
...
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 执行挂载流程
}
}
② 数据响应式系统初始化(initState)
处理了:
propsmethodsdata(使用defineReactive)computedwatch
使这些属性具备响应式能力。
③ 编译模板(vm.$mount)
ini
js
复制编辑
// src/platforms/web/runtime/index.js
const mount = Vue.prototype.$mount = function (el, hydrating) {
el = query(el) // 获取 DOM 元素
const options = this.$options
if (!options.render) {
let template = options.template
if (!template && el) {
template = el.outerHTML
}
const { render } = compileToFunctions(template)
options.render = render
}
return mountComponent(this, el)
}
如果你没有手写 render 函数,Vue 会将 template 编译为 render 函数。
④ 创建虚拟 DOM 并挂载(mountComponent)
scss
js
复制编辑
// src/core/instance/lifecycle.js
function mountComponent(vm, el) {
vm.$el = el
callHook(vm, 'beforeMount') // 生命周期钩子3
// 核心渲染逻辑
const updateComponent = () => {
vm._update(vm._render())
}
// 用 watcher 包裹渲染函数,数据变了会重新调用
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted) callHook(vm, 'beforeUpdate')
}
}, true)
vm._isMounted = true
callHook(vm, 'mounted') // 生命周期钩子4
}
⏱ 生命周期钩子触发时机:
| 生命周期钩子 | 执行时机 |
|---|---|
beforeCreate |
实例初始化前,数据还没初始化 |
created |
数据初始化完成,还没挂载 |
beforeMount |
模板已编译,挂载前 |
mounted |
挂载完成,DOM 可访问 |
beforeUpdate |
响应式数据更新前 |
updated |
DOM 更新后 |
beforeDestroy |
实例销毁前 |
destroyed |
实例销毁后 |
13. 插槽原理
插槽通过 _t() 渲染函数处理,默认插槽 fallback 生效;作用域插槽用 $scopedSlots 处理。
- 插槽是 Vue 2 编译阶段把父组件中子组件标签的内容 ,作为VNode 传入子组件 ,在子组件渲染时用
<slot>标签占位,将这些 VNode 插入到对应位置。
14. 组件和插件的区别
| 对比项 | 组件 | 插件 |
|---|---|---|
| 编写方式 | .vue 或 template 模板 |
提供 install 方法 |
| 注册方式 | components / Vue.component |
Vue.use() |
| 作用 | UI 视图 | 增强全局功能,如指令、混入、API |
15. mixin 和 extends 区别
mixins: 多来源合并,像拌饭extends: 单来源继承,像复制模板
在vue2中,两者都是用来复用组件逻辑的机制,但是他们在使用、原理、优先级方面有所不同
✅ 一、使用方法
1. mixins
javascript
js
复制编辑
// 定义一个 mixin
const myMixin = {
data() {
return {
message: 'Hello from mixin'
}
},
created() {
console.log('mixin created')
}
}
// 使用
export default {
mixins: [myMixin],
created() {
console.log('component created')
}
}
2. extends
javascript
js
复制编辑
const baseComponent = {
data() {
return {
baseMsg: 'from base'
}
},
created() {
console.log('extends created')
}
}
export default {
extends: baseComponent,
created() {
console.log('component created')
}
}
二、原理解析(合并策略)
16. Vue 如何实现双向绑定?
核心:数据响应式 + 模板绑定
- 绑定 input 值
Object.defineProperty拦截赋值- DOM 自动更新
17. 动态添加响应式属性方法
Vue.set(obj, key, val)- 或用
this.someObject = {...this.someObject, newProp: val}
18. 虚拟 DOM 是什么?
VNode 是用 JS 对真实 DOM 的描述,更新时用 diff 算法找出最小变更,避免频繁重排重绘。
19. 为什么使用虚拟 DOM?
因为操作真实 DOM 很慢,而 VNode 是 JS 对象,比较快。最后只操作需要变化的部分 DOM。
20. Vue diff 算法流程
核心逻辑:同层比较、递归处理、key辅助。使用 key 能显著减少 DOM 操作。
21. Vue 常用修饰符
.stop阻止冒泡.prevent阻止默认.capture,.once,.self,.native(只用于组件)
22. 事件绑定原理
- Vue 将事件绑定成函数缓存到
vm._events中 - 最终转成
addEventListener
23. Vue 自定义指令
通过 bind, inserted, update 等钩子,可以实现拖拽、自动聚焦等效果。
24. keep-alive 的作用
缓存组件实例,防止重复渲染,用在 tab 页、分页等场景;配合 include/exclude 更精准控制。
25. 父子组件通信方式
- 父 → 子:props
- 子 → 父:$emit
- 兄弟通信:EventBus 或 Vuex
- 深层通信:provide/inject
26. Vue-router 两种模式区别
- hash:带
#,兼容好 - history:无
#,需后端配合
27. 导航守卫用法
- 全局守卫:
beforeEach - 路由独享守卫:
beforeEnter - 组件内守卫:
beforeRouteEnter
28. SSR 是什么,有做过吗?
服务端提前把 HTML 渲染好 → 浏览器直接看完整页面,提升首屏速度 & SEO。但复杂度更高。
29. Vue3 新特性(与 Vue2 对比)
| 特性 | Vue3 提升点 |
|---|---|
| 性能 | 更快,更小 |
| Composition API | 更强的逻辑复用 |
| Fragment | 多根节点支持 |
| Teleport | 跨层级渲染,比如弹窗 |
| Proxy 响应式 | 更完善,支持动态属性添加 |
vue2的vuex, 详细说明
- 为什么要使用?
- 多个组件数据共享状态
- 组件嵌套复杂,props + emit 传递混乱
- 想统一管理数据变更、支持调试工具、状态持久化等
- 五个核心概念
- state : 统一存储的数据源
- getter : 派生状态,类似计算属性
- mutations : 同步修改state的唯一方法
- actions : 异步操作,提交mutations
- mudules : 拆分子模块,组织大型状态树
1. 示例代码
js
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.store({
state:{
count:0
},
getter :{
doubleCount(state){
retrun state.count * 2
}
},
mutations :{ // 同步改变state的唯一方法
increment(state,payload){
state.count += payload
}
},
actions:{
asyncIncrement({ commit }) {
setTimeout(() => {
commit('increment', 1)
}, 1000)
}
}
})
2. 在项目中挂载
javascript
js
复制编辑
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
🧬 在组件中使用
1. 获取 state
xml
vue
复制编辑
<template>
<div>{{ count }}</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['count'])
}
}
</script>
2. 调用 mutation(同步修改)
kotlin
js
复制编辑
this.$store.commit('increment', 1)
3. 调用 action(可异步)
kotlin
js
复制编辑
this.$store.dispatch('asyncIncrement')
4. 使用 getters
css
js
复制编辑
computed: {
...mapGetters(['doubleCount'])
}
🧱 模块化结构(modules)
javascript
js
复制编辑
// store/modules/user.js
export default {
namespaced: true,
state: {
name: 'Jack'
},
mutations: {
setName(state, name) {
state.name = name
}
}
}
javascript
js
复制编辑
// store/index.js
import Vuex from 'vuex'
import user from './modules/user'
export default new Vuex.Store({
modules: {
user
}
})
访问:
kotlin
js
复制编辑
this.$store.commit('user/setName', 'Tom')
🔍 Vuex 原理(简化说明)
state被 Vue 实例包裹为响应式(用Vue.observable()或new Vue({ data }))- 组件使用
mapState其实就是访问这个响应式对象的属性 - 所有
commit都是通过mutations中注册的函数来操作 state dispatch是一个封装,会调用actions,再通过commit修改数据- 所有状态变更都可以被 Vue DevTools 捕捉和记录
状态流动图(简化版)
bash
组件 ------ dispatch ------→ action ------ commit ------→ mutation ------→ state
组件 ←------ computed(mapState/mapGetters) ←------ state/getters