# 1. Vue 介绍
Vue 是一个渐进式 JS 框架,用于构建用户界面。 它的核心特点是响应式数据绑定和组件化开发,同时提供了从视图层到全栈生态的渐进式解决方案。
Vue 的设计借鉴了 MVVM 的思想,尤其是数据驱动视图和双向绑定的特性,但它的组件化架构和灵活性超出了传统 MVVM 的严格定义。
- 数据绑定机制
- ViewModel层:
Vue
的响应式系统(如data()
或reactive()
)充当ViewModel
,自动同步View
和Model
。 - 模板语法:
{{}}
和v-model
实现了双向绑定(类似 MVVM 的Data Binding
)
- ViewModel层:
- 与经典 MVVM 的区别
- 灵活性:Vue 允许直接操作 DOM(如
ref
),而经典 MVVM 要求完全通过 ViewModel 交互。 - 职责范围:Vue 的组件系统包含逻辑(Methods)和视图(Template),比 MVVM 的 ViewModel 更复杂。
- 灵活性:Vue 允许直接操作 DOM(如
# Vue 和 React 的区别:
相同点:
使用 Virtual DOM
中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数
都支持服务器端渲染
都有支持native
的方案:Vue的weex
、React的React native
都有自己的构建工具:Vue的vue-cli、React的Create React App
不同点:
特性 | React | Vue (MVVM) |
---|---|---|
数据绑定 | 单向 | 双向 |
状态更新 | 显式调用 setState | 自动响应式 |
组件通信 | Props + 回调 | Props + 事件 + v-model |
语法 | JSX 语法 | .vue文件 |
在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树
# MVVM 与 MVC 区别:
MVVM 与 MVC 两者之间最大的区别就是:MVVM 实现了对 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素来改变 View 的变化,而是改变其属性后,该属性对应的 View 层数据会自动改变。
# 2. Vue 指令
# 一、数据绑定指令
# 1、v-bind(属性绑定)
作用:动态绑定 HTML 属性或组件 props 简写为一个冒号【 :】
- Q: v-bind 和普通属性有什么区别?
- A: v-bind 动态绑定属性值(响应式更新),普通属性是静态字符串。
<1>对象语法:
<div :class="{'is-active':isActive, 'text-danger':hasError}"></div>
<!-- data: { isActive: true, hasError: false } -->
2
<2>数组语法:
<p :class="[{'is-active':activeClass},errorClass]">12345</p>
<!-- data: { activeClass: false, errorClass: 'text-danger' } -->
2
// 三目运算符绑定
<div :class="[loginActive ? 'activeTab' : '','tabpane']"></div>
2
<3>直接绑定数据对象:
<div :class="classObject">12345</div>
<!-- data: { classObject:{ 'is-active': false, 'text-danger':true } } -->
2
<4>样式三元运算:
:style="{'color': countdown.status ? 'blue' : '#FCB11C'}" ;
# 2、v-model(双向绑定)
表单输入双向绑定(语法糖)。 v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值,因为它选择 Vue 实例数据做为具体的值。
- 作用:表单输入双向绑定(语法糖)。
- 原理:v-bind + v-on 的组合。
<input v-model="message">
<select v-model="selected">
<option value="A">A</option>
</select>
2
3
4
- 修饰符:
.lazy
:转为 change 事件后更新.trim
:自动去除首尾空格
# 二、条件渲染指令
# 1、v-if 和 v-show 的区别
相同点:
v-if 与 v-show 都可以动态控制 dom 元素显示隐藏
不同点:
v-if 显示隐藏是将 dom 元素整个添加或删除,会触发完整的生命周期钩子
而 v-show 隐藏则是为该元素添加 css--display:none
,此时 dom 元素还在
TIP
- 如果需要非常频繁地切换,则使用 v-show 较好
- 如果在运行时条件很少改变,则使用 v-if 较好
# 三、列表渲染指令
作用:遍历数组或对象。 语法:(item, index) in items 或 (value, key) in object。
有下面两种遍历形式
<!-- 使用in,index是一个可选参数,表示当前项的索引 -->
<div v-for="(item,index) in items" :key="item.id"></div>
2
<!-- 使用of -->
<div v-for="item of items"></div>
2
- 关键点:
- 必须加
:key
:帮助 Vue 高效更新虚拟 DOM(唯一标识)。 - 避免
v-for
和v-if
同级使用:优先级问题(Vue 2 中v-for
优先,Vue 3 中v-if
优先)。
- 必须加
当 v-for 和 v-if 处于同⼀个节点时, v-for 的优先级⽐ v-if 更⾼,这意味着 v-if 将分别重复 运⾏于每个 v-for 循环中。如果要遍历的数组很⼤,⽽真正要展示的数据很少时,这将造成很⼤的性 能浪费。这种场景建议使⽤ computed ,先对数据进⾏过滤
# 四、事件处理指令
v-on 主要用来监听 dom 事件,以便执行一些代码块。表达式可以是一个方法名
简写为:【 @ 】
<button @click="handleClick">点击</button>
<input @input="onInput">
2
- 修饰符:
.stop
:阻止事件冒泡.prevent
:阻止默认行为.once
:只触发一次
<form @submit.prevent="onSubmit"></form>
# 五、特殊指令
# 1. v-slot(插槽)
作用:定义具名插槽或作用域插槽。
简写:#
<!-- 子组件 -->
<slot name="header" :user="user"></slot>
<!-- 父组件 -->
<template #header="{ user }">
<div>用户名: {{ user.name }}</div>
</template>
2
3
4
5
6
7
# 2. v-pre
作用:跳过该元素及其子元素的编译。
<div v-pre>{{ 这里不会编译 }}</div>
核心应用场景
- 静态内容性能优化
<template>
<!-- 包含大量静态内容的区块 -->
<article v-pre>
<h1>用户协议</h1>
<p>本协议是您与...(数千字静态文本)</p>
<!-- 节省编译开销 -->
</article>
</template>
2
3
4
5
6
7
8
- 性能影响:
- 编译时间减少 30-50%(对于大型静态内容)
- 虚拟DOM diff 时直接跳过比较
- 第三方模板集成
<template>
<!-- 集成 Handlebars 模板 -->
<script type="text/x-handlebars-template" v-pre>
{{#each users}}
<div class="user">{{name}}</div>
{{/each}}
</script>
</template>
2
3
4
5
6
7
8
- 优势:
- 避免 Vue 尝试编译其他模板语法
- 保持第三方模板的原始结构
# 3. v-cloak
作用:解决初始化时模板闪烁问题。
配合 CSS:
[v-cloak] { display: none; }
<div v-cloak>{{ message }}</div>
# 4. v-once
作用:使元素及其子元素只渲染一次,后续数据变化时跳过更新。
核心应用场景
- 大型列表的基准项
<template>
<ul>
<li v-for="item in list" :key="item.id">
<!-- 列表项标题永不变化 -->
<h3 v-once>{{ item.title }}</h3>
<!-- 可能变化的内容 -->
<p>{{ item.dynamicContent }}</p>
</li>
</ul>
</template>
2
3
4
5
6
7
8
9
10
# 其他指令
# 1. v-text
<span v-text="msg"></span>
v-text 主要用来更新 textContent,可以等同于 JS 的 text 属性
{{}}
是它的另一种写法, v-text与{{}}
等价,{{}}
叫模板插值,v-text叫指令
区别:在渲染数据比较多的时候,{{}}
可能会把大括号显示出来,俗称屏幕闪动
# 2. v-html
双大括号的方式会将数据解释为纯文本,而非 HTML 为了输出真正的 HTML,可以用 v-html 指令 它等同于 JS 的 innerHtml 属性
<div v-html="rawHtml"></div>
这个 div 的内容将会替换成属性值 rawHtml,直接作为 HTML 进行渲染
- 在⽹站上动态渲染任意 HTML,很容易导致 XSS 攻击。所以只能在可信内容上使⽤ v-html,且永远不 能⽤于⽤户提交的内容上
# 自定义指令:
有的情况下,需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
directives: { // 局部注册指令
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
<input v-focus> // 在元素上使用指令
2
3
4
5
6
7
8
9
10
指令注意事项:
WARNING
- 1、当 v-for 和 v-if 处于同一节点,v-for 的优先级比 v-if 更高,永远不要把 v-if 和 v-for 同时用在同一个元素上
- 2、如果一组 v-if + v-else 的元素类型相同,最好使用 key
# 3.父子组件间通信
# 3-1、 父组件 传数据 子组件
parent.vue:
<children v-bind:toChildData="parentSourceData"></children>
children.vue:
props: ["toChildData"];
# 3-2、 父组件 调用 子组件 方法
parent.vue:
<children ref="refChildrenName"></children>
method:
this.$refs.refChildrenName.子组件的方法名();
# 3-3、 子组件 传数据 父组件
parent.vue:
<children v-on:fromChild="parentMethod"></children>
children.vue:
this.$emit("fromChild", args);
# 3-4、 子组件 调用 父组件方法
parent.vue:
<children></children>
children.vue:
this.$parent.parentMethod(args);
或者在子组件里用$emit 向父组件触发一个事件,父组件监听这个事件就行了
<child @fatherMethod="fatherMethod"></child>
childMethod() { this.$emit('fatherMethod'); }
2
3
# 3-5、 子组件使用.sync 修饰符
# 4.组件上总是必须用 key
key 的作用是为了在 diff 算法执行时更快的找到对应的节点,提高 diff 速度。
key 一般用每个元素对应固定不变的值,如 id,对于数组索引 index 由于在插入数据时会变化,不推荐使用
注意:有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误,常见于 v-for 循环渲染。
# 5.Proxy 与 Object.defineProperty 对比
Object.defineProperty 虽然已经能够实现双向绑定了,但是他还是有缺陷的
1、只能对属性进行数据劫持,所以需要深度遍历整个对象
2、对于数组不能监听到数据的变化
# Proxy 优势
1、Proxy 的第二个参数可以有 13 种拦截方法,比 Object.defineProperty() 要更加丰富
2、Proxy 能够原生支持监听数组变化,并且可以直接对整个对象进行拦截
# Proxy 缺点
1、Proxy 的兼容性不如 Object.defineProperty(),不能使用 polyfill 来处理兼容性
所以在 Vue3.0 中使用 Proxy 代替 Object.defineProperty
# 备注:vue2 对数组的监听:
Vue 包含一组观察数组的变异方法(改变原数组),所以它们也将会触发视图更新。这些方法如 push()等 7 种:
对于非变异方法如 slice(),可以用新数组替换旧数组 arr = arr.slice(1)
由于 JavaScript 的限制,Vue 不能检测以下变动的数组
1、直接设置一个项时,例如:vm.items[indexOfItem] = newValue
2、修改数组的长度时,例如:vm.items.length = newLength 或者 vm.items.length++
解决方法:
对于第一种:Vue.set(vm.items, indexOfItem, newValue) 或者 vm.$set(vm.items, indexOfItem, newValue)
对于第二种:vm.items.splice(newLength)
# 6.组件的 data 必须是一个函数
这是为了 确保每个组件实例维护独立的数据副本 ,当 data 是对象时,所有组件实例将共享同一个数据对象,导致状态污染。
# 注: 在 Vue 的根实例上 data 直接使用对象是可以的
因为只存在一个这样的实例,根实例不会被复用,不存在共享问题。
new Vue({
data: {
foo: "bar"
}
});
2
3
4
5
# 常见问题
- 避免箭头函数:
// ❌ 避免使用箭头函数
data: () => ({ count: 0 })
// ✅ 使用普通函数
data() {
return { count: 0 }
}
2
3
4
5
6
7
原因:箭头函数没有自己的 this,会继承父级上下文。
- 复杂数据初始化
data() {
return {
complexData: this.initComplexData()
}
},
methods: {
initComplexData() {
// 复杂的初始化逻辑
return { /*...*/ }
}
}
2
3
4
5
6
7
8
9
10
11
- 为什么 React 组件可以用对象,而Vue 必须用函数?
React 组件状态存储在 this.state
中,由 React 内部管理
Vue 的响应式系统需要确保每个实例有独立的数据引用
# 7.keep-alive
keep-alive 是 Vue 内置的抽象组件,用于缓存不活动的组件实例,避免重复渲染和销毁带来的性能损耗。
<!-- 只缓存指定组件 -->
<keep-alive include="CompA,CompB">
<component :is="currentView"></component>
</keep-alive>
<!-- 排除特定组件 -->
<keep-alive exclude="CompC">
<component :is="currentView"></component>
</keep-alive>
<!-- 正则匹配 -->
<keep-alive :include="/CompA|CompB/">
<component :is="currentView"></component>
</keep-alive>
<!-- 最大缓存数限制 -->
<keep-alive :max="5">
<router-view></router-view>
</keep-alive>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 主要特性
- 组件缓存:保留组件状态(data)和 DOM 结构
- 生命周期钩子:激活/停用时触发特定钩子
- 条件缓存:可控制哪些组件需要缓存
首次渲染:正常触发 created/mounted/activated 等钩子
activated: 首次加载组件 或者 keep-alive 组件激活时调用,(数据刷新、事件监听恢复)
deactivated: keep-alive 组件停用时调用(清除定时器、暂停动画)
# 完整生命周期调用顺序

# 首次加载会触发 activated
初始化逻辑放置:
- 只与DOM相关的初始化 →
mounted
- 需要每次显示都执行的逻辑 →
activated
- 只需执行一次的初始化 →
created
- 只与DOM相关的初始化 →
注意事项
- 不缓存大量组件:可能引起内存问题
- 不缓存包含定时器的组件:需在 deactivated 中清除
- 不缓存具有全局状态的组件:可能导致状态混乱
- 需要唯一key:确保正确匹配缓存
# 8.方法、计算属性、侦听属性
特性 | 方法(methods) | 计算属性(computed) | 侦听属性(watch) |
---|---|---|---|
定义方式 | 函数集合 | 基于依赖的缓存属性 | 侦听特定数据变化 |
调用方式 | 需要主动调用 | 像属性一样访问 | 自动触发 |
缓存 | 无 | 有(依赖不变不重新计算) | 无 |
异步操作 | 支持 | 不支持 | 支持 |
适用场景 | 事件处理/需要主动触发的逻 | 依赖其他数据的派生数据 | 数据变化时需要执行复杂操作/异步 |
# 一、方法(methods)详解
基本特性
- 定义:包含业务逻辑的函数集合
- 调用:必须通过
this.methodName()
调用 - 无缓存:每次调用都会重新执行
注意事项
- 避免箭头函数:会丢失 this 绑定
- 模板中使用:可以传递参数
@click="methodName(param)"
- 性能考虑:频繁调用的复杂逻辑不适合放在
methods
- 避免在模板中直接调用复杂方法
# 二、计算属性(computed)详解
核心机制
- 响应式依赖追踪:自动收集依赖的响应式数据
- 惰性求值:只有依赖变化时才重新计算
- 缓存机制:依赖未变化时直接返回缓存值
注意事项
- 避免在计算属性中进行昂贵操作
# 三、侦听器(watch)详解
核心机制
- 响应数据变化:执行副作用操作
- 深度监听:可监听对象内部变化
- 立即执行:配置
immediate: true
- 异步支持:适合执行异步操作
注意事项
- 对频繁变化的数据使用防抖(debounce)
# 9.过滤器
过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Vue3移除了过滤器,可以用过 方法(Methods)、计算属性(Computed)、app.config.globalProperties、自定义指令(Directives)来实现。
# 10.Vue ref
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;
如果用在子组件上,引用就指向组件实例。
<p ref="p">hello</p>
<child-component ref="child"></child-component>
2
3
TIP
- 当 v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。
- 除了可以获取dom元素,还能获取子组件中的data和去调用子组件中的方法
# 11.Vue hooks
# 1、内部监听生命周期函数
mounted() {
// 监听窗口发生变化,resize组件
window.addEventListener('resize', this.$_handleResizeChart)
// 通过hook监听组件销毁钩子函数,并取消监听事件
this.$once('hook:beforeDestroy', () => {
window.removeEventListener('resize', this.$_handleResizeChart)
})
}
2
3
4
5
6
7
8
# 2、外部监听生命周期函数
<template>
<!--通过@hook:updated监听组件的updated生命钩子函数-->
<!--组件的所有生命周期钩子都可以通过@hook:钩子函数名 来监听触发-->
<custom-select @hook:updated="$_handleSelectUpdated" />
</template>
<script>
import CustomSelect from '../components/custom-select'
export default {
components: {
CustomSelect
},
methods: {
$_handleSelectUpdated() {
console.log('custom-select组件的updated钩子函数被触发')
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 12. Vue.nextTick 的原理和用途
# 核心用途
# (1) 获取更新后的 DOM
Vue 的数据更新是 异步 的,直接修改数据后立即访问 DOM
,可能获取的是 旧值。使用 nextTick
可以确保在 DOM
更新后 再执行操作:
this.message = "更新后的消息"; // 修改数据
this.$nextTick(() => {
console.log(this.$el.textContent); // 获取更新后的 DOM 内容
});
2
3
4
# (2) 在组件渲染完成后操作
例如,在 mounted 钩子中访问 DOM 元素:
mounted() {
this.$nextTick(() => {
const el = document.getElementById("my-element");
console.log(el.offsetHeight); // 确保 DOM 已渲染
});
}
2
3
4
5
6
# (3) 解决 v-if 切换后的 DOM 操作
当 v-if
切换显示时,DOM
不会立即更新,nextTick
可以确保操作在 DOM
更新后执行:
this.showModal = true; // 显示模态框
this.$nextTick(() => {
this.$refs.modal.focus(); // 确保模态框已渲染
});
2
3
4
# 实现原理
# (1) Vue 的异步更新机制
Vue 的数据更新是 批量异步 的(类似 Promise.then
或 setTimeout
),目的是优化性能(避免频繁 DOM 操作)。
当数据变化时,Vue 不会立即更新 DOM,而是把更新任务推入 异步队列,并在下一个 事件循环(Event Loop
) 中执行。
# (2) nextTick 的实现方式
Vue 内部使用 微任务(Microtask
) 或 宏任务(Macrotask
) 来执行回调:
- 优先使用
Promise.then
(微任务)(现代浏览器支持) - 如果不支持
Promise
,则降级到MutationObserver
(微任务) - 再不支持,则使用
setImmediate(Node.js)
或setTimeout
(宏任务)
# (3) nextTick 和 setTimeout(fn, 0) 的区别?
nextTick | setTimeout(fn, 0) |
---|---|
优先使用 微任务(Promise/MutationObserver) | 使用 宏任务 |
确保在 Vue DOM 更新后 执行 | 不保证在 Vue 更新后执行 |
更高效,适合 Vue 内部更新 | 可能比 nextTick 慢 |
# (4) Vue3 的$nextTick() 能结合 async/await
<template>
<div>
<button @click="addItem">添加一项并打印列表高度</button>
<ul ref="list">
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue';
const items = ref(['苹果', '香蕉', '橙子']);
const list = ref(null); // 获取列表 DOM 的引用
const addItem = async () => {
// 1. 添加新项(触发异步 DOM 更新)
items.value.push('芒果');
// 2. 👇 关键对比点:尝试直接读取高度(可能错误!)
console.log('无 nextTick 高度:', list.value.scrollHeight); // ❌ 可能是旧值
// 3. 等待 DOM 更新完成
await nextTick();
// 4. 此时读取正确高度
console.log('有 nextTick 高度:', list.value.scrollHeight); // ✅ 保证是新值
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 13. Vue单向数据流
子组件不可以修改父组件传递的Prop
- 定义:
- 数据从父组件流向子组件,子组件不能直接修改父组件传递的数据,必须通过事件通知父组件自行更新(通过$emit 或者 通过.sync修饰符 )。
- 类比:
- 父组件:像公司的老板(拥有数据)。
- 子组件:像员工(接收老板分发的任务,但不能直接修改老板的决策,只能反馈建议)。
# 为什么 Vue 要这样设计?
- 可维护性
- 数据变更源头唯一(父组件),避免子组件随意修改导致的混乱。
- 数据流清晰,便于调试(变更来源可追溯)。
- 防止意外副作用
- 如果子组件能直接修改父组件数据,多个子组件同时修改时可能引发冲突。
- 与 React/Angular 的统一
- 主流框架均采用单向数据流,降低开发者跨框架学习成本。
# 常见误区与纠正
# 误区 1:v-model 是双向绑定,违反单向数据流?
纠正:v-model
是语法糖,本质仍是单向数据流 + 事件通知:
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
2
3
# 误区 2:子组件修改对象类型的 Prop 属性不算破坏单向流?
纠正:直接修改对象属性(如 props.user.name = 'new'
)技术上可行,但违背设计原则。正确做法:
// 子组件中
emit('update-user', { ...props.user, name: 'new' }); // 通知父组件更新
2
# 14. Vue的性能优化
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连⽤
<!-- 错误用法:同时使用v-if和v-for -->
<ul>
<li v-for="item in list" v-if="item.active" :key="item.id">
{{ item.name }}
</li>
</ul>
2
3
4
5
6
- key保证唯⼀
<!-- 动态业务数据使用唯一ID -->
<div v-for="item in items" :key="item.id"></div>
<!-- 简单列表可用index -->
<div v-for="(item, index) in simpleList" :key="index"></div>
2
3
4
- 使⽤路由懒加载、异步组件
// 路由懒加载
const User = () => import('./User.vue')
// 条件懒加载
components: {
HeavyComponent: () => import('./HeavyComponent.vue')
}
2
3
4
5
6
7
8
- 合理使用 keep-alive
<keep-alive :include="['Home', 'User']" :max="5">
<router-view></router-view>
</keep-alive>
2
3
- 大型数据优化-虚拟滚动
import { RecycleScroller } from 'vue-virtual-scroller'
export default {
components: { RecycleScroller }
}
2
3
4
- 图⽚懒加载
# 其他
Vue 不能挂载在 body、html 这样的根节点上
router.push 只能跳转路由内部的页面,要想跳转到外部页面,可以使用 window.location.href