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 组件实例销毁之后
activated
keep-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
)
处理了:
props
methods
data
(使用defineReactive
)computed
watch
使这些属性具备响应式能力。
③ 编译模板(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