Vue基础
Vue基础
1、Vue的基本原理
Vue是一款用于构建用户界面的 JavaScript 框架。采用的MVVM架构(Model-View-ViewModel),通过数据驱动和组件化开发来构建用户界面。
MVVM架构
Model,数据层,存储应用的状态数据
View,视图层,即DOM,展示数据的UI
VIewModel,视图模型,Vue实例充当ViewModel,负责把View和Model绑定,实现数据的双向绑定,这样数据变化的时候,自动更新视图。视图更新输入,数据也更新。
响应式系统
这是Vue的核心,通过观察数据的变换,自动更新视图。
Vue 2.x 的实现原理:
1、 初始化响应式数据:Vue实例化时,它会遍历
data
对象的每个属性,并使用Object.defineProperty
为每个属性添加getter
和setter
。2、依赖收集:当组件的模版里使用某个数据属性,例如
{{name}}
,Vue会通过getter
读取该属性。在读取属性时,Vue会将当前的视图与该属性关联,这个过程称为依赖收集。3、触发更新:当数据属性被修改,
setter
会触发,并通知所有依赖该属性的视图进行更新。
Vue 3.x 的改进:
使用了ES6的
proxy
API 来实现响应式系统。优势在于可以拦截更多操作。当访问响应式对象的属性时,
Proxy
的get
操作会触发依赖收集。当修改响应式对象的属性时,
Proxy
的set
操作会触发更新。
Proxy
可以拦截对象的所有操作,包括新增属性、删除属性、数组操作等,而Object.defineProperty
无法监听这些操作。
模版编译
Vue的模板语法(HTML模板)是一种声明式语法,用于描述组件的结构和行为。
在Vue应用运行之前,这些模板会被编译器(Vue Complier)转换成渲染函数。
模版编译的过程:
1、解析模版:Vue编译器解析模版字符串,将其转换为一个抽象语法树(AST)。
2、优化AST:对AST抽象语法树进行优化,例如标记静态节点(不随数据变化的节点),以便在后续的渲染过程中减少不必要的操作。
3、生成渲染函数:将优化后的AST转换为一个JavaScript渲染函数。
渲染函数
纯函数,接受组件的上下文_ctx
作为参数,返回一个虚拟DOM树。当数据变化时,Vue会重新调用渲染函数,生成新的虚拟DOM树。
虚拟DOM
数据变化时,Vue的响应式系统会触发渲染函数重新执行,生成新的虚拟DOM树。然后,Vue的渲染器Render会比较新旧虚拟DOM树,使用 Diff 算法找出差异,将差异应用到真实DOM上。
组件化
Vue是基于组件开发的,每个组件都是一个独立的Vue实例,有自己的模版、数据、逻辑、样式。组件之间可以嵌套、组合,实现复杂的分层和模块化管理。
特点:
- 封装性
- 可复用性
- 可维护性
- 声明式语法
组件通信
父传子:子用props
接收
子传父: $emit
触发自定义事件
兄弟通信:事件总线(Event Bus), EventBus.$emit
、 EventBus.$on
跨层级通信:provide
和 inject
全局状态管理:Vuex Store
生命周期
Vue的生命周期阶段:
1、创建阶段(组件实例被创建,数据初始化)
- **
beforeCreate
**:在实例初始化之后被调用,此时实例的数据(data)和事件还未初始化。 - **
created
**:在实例的数据和事件初始化之后被调用,此时可以访问 data 和 methods。
2、挂载阶段(组件被插入到 DOM 中)
- **
beforeMount
**:在组件挂载之前被调用,此时模板已编译,但尚未插入到 DOM 中。 - **
mounted
**:在组件挂载到 DOM 之后被调用,此时可以访问 DOM 元素。
3、更新阶段(组件的数据或状态发生变化,导致组件重新渲染)
- **
beforeUpdate
**:在组件数据更新之前被调用,此时可以访问旧的 DOM 和数据。 - **
updated
**:在组件数据更新并重新渲染之后被调用,此时可以访问新的 DOM。
4、销毁阶段(组件被销毁,从 DOM 中移除)
- **
beforeDestroy
**(Vue 2.x)/ **beforeUnmount
**(Vue 3.x):在组件销毁之前被调用,此时可以访问组件的所有数据和方法。 - **
destroyed
**(Vue 2.x)/ **unmounted
**(Vue 3.x):在组件销毁之后被调用,此时组件已经从 DOM 中移除。
2、computed 和 watch 的区别
两种不同的响应式机制,用来处理数据变化时的逻辑。
所有响应式数据都会经过劫持,即Vue会给每个属性设置getter和setter(Vue2使用
Object.defineProperty
,Vue3使用Proxy
),以便在数据读取时收集依赖(订阅者),在数据修改时通知这些依赖进行更新。Watcher:Vue内部会为需要响应式更新的操作创建Watcher对象,负责在依赖的数据变化时触发回调(例如重新渲染视图、重新计算computed等)
computed(计算属性)
基于响应式数据动态派生的值,具有缓存机制,只有当依赖项发生变化时才会重新计算。
1 |
|
Vue实例初始化时,会遍历computed选项,对每个计算属性创建一个Watcher实例,在模版或者代码中访问计算属性时,会调用其getter。如果 computed watcher 被标记为dirty,则调用getter重新计算值,并缓存结果。如果没有变化,则直接返回缓存值。当依赖的数据变化时,对应的 getter 中调用的响应式数据触发 setter,进而通知相关的 Watcher,将其
dirty
标记为true
。
watch(侦听器)
用于监听响应式数据的变化,并在数据变化时执行特定的逻辑。当被监听的数据发生变化时,watch
中定义的回调函数会被触发。
1 |
|
定义一个watch属性时,Vue会为它创建一个普通的Watcher,这个 watcher 会在数据变化时(即 getter 求值时检测到新旧值不同)调用指定的回调函数,并传入新值和旧值。
主要区别
使用配置对象
handler:当被监视的源发生变化时,执行的回调函数,接收新值、旧值作为参数。
immdiate:指示是否在 watcher 初始化后立即调用一次回调
deep:指定是否对对象或数组进行深度侦听,从而捕捉到所有嵌套属性的变化
flush(Vue 3):控制 watcher 回调的执行时机,可选 “pre”(更新前)、”post”(更新后)或 “sync”(同步执行)
3、methods
methods
用于定义组件的方法。它们可以在模板中通过事件绑定或表达式调用,也可以在组件内部直接调用。与data或者computed不同,这些方法不会被Vue自动进行响应式处理、缓存或依赖收集。
1 |
|
创建Vue实例时,Vue会遍历配置对象中的methods,并把每个方法挂载到Vue实例上。可以通过
this.methodName
调用方法,也可以用事件绑定@click="methodName"
的形式调用方法。所有在
methods
中定义的函数上下文(this)绑定到实例上。这样方法内部的this就都指向Vue实例,从而可以访问data
、computed
、props
等属性。
4、slot
slot是一种内容分发机制(内容插槽机制),用于在组件内部预留“占位符”,让父组件能够将内容插入到子组件预留的位置,从而实现内容的分发和复用。
解决的问题:
组件封装的同时仍然允许外部传入 HTML 或其他组件,使得组件既能保持内部逻辑封装,又能灵活地展示外部内容。
插槽的类型
默认插槽:父组件没有给子组件提供具名插槽内容时,默认插槽会渲染默认内容,或者渲染父组件传入的内容。
1
<slot>默认主体内容</slot>
具名插槽:通过
name
属性指定插槽名称。1
<slot name="header">默认头部内容</slot>
父组件要使用
template
标签,并通过v-slot
指令(或简写#
)指定插槽名称:1
2
3<template v-slot:header>
<h1>这是自定义头部</h1>
</template>作用域插槽:作用域插槽允许子组件向父组件传递数据。
1
2// 子
<slot :user="user">默认内容</slot>
5、filters过滤器
是一种用于格式化数据的工具,用于在模板中对数据进行格式化。例如格式化日期、货币、字符串大小写转换等。它们可以被定义为全局过滤器或局部过滤器,在模板中通过管道符号(|
)使用。
过滤器是 Vue 2.x 中的一个特性,但在 Vue 3.x 中已被移除。
过滤器是一个函数,接收原始数据作为输入,并返回格式化后的数据。
局部过滤器:
1 |
|
全局过滤器:
1 |
|
在模板中使用过滤器
6、如何保存页面的当前的状态
页面状态是指页面在某一时刻的所有数据和配置信息的集合。
可能包括:
用户输入:如表单字段的内容。
显示内容:如列表数据、文本内容等。
加载状态:如是否正在加载数据、加载进度等。
UI 状态:如是否显示某个弹窗、是否折叠某个面板等。
路由信息:如当前页面的 URL、路由参数等。
为什么要保存
1、用户体验:当用户离开页面后再次返回时,页面能够恢复到用户离开时的状态,避免用户重新操作。
2、数据持久化:将页面状态保存到本地存储(如 localStorage
或 sessionStorage
),以便在页面刷新或关闭后仍能恢复。
3、跨页面共享:将页面状态传递给其他页面或组件,实现数据共享。
方法
1、使用浏览器存储(localStorage / sessionStorage)
localStorage:数据会一直保存,即使浏览器关闭也不丢失,除非主动清除。
sessionStorage:数据在当前会话(tab 或 window)中保存,关闭页面或浏览器后数据会丢失。
数据以键值对的形式存储,通常以字符串格式保存(需要用 JSON.stringify/JSON.parse 进行转换)。
1 |
|
2、使用 URL 参数或 Hash
将页面状态编码在 URL 的查询字符串或哈希值中。用户在刷新或分享 URL 时,状态可以通过解析 URL 得到。
1 |
|
3、 利用 History API 的 State 对象
TML5 提供了 History API(如
history.pushState
和history.replaceState
),可以将状态对象与历史记录关联。当用户使用浏览器的前进后退按钮时,可以通过history.state
获取之前保存的状态。
1 |
|
4、 在 Vue 应用中的状态管理
Vuex:集中存储应用的所有状态,通过 mutations 和 actions 修改状态,并且可以利用插件(如 vuex-persistedstate)将状态持久化到 localStorage。
Pinia:Vue 3 推荐的状态管理库,具有类似 Vuex 的特性,也支持状态持久化。
1、安装插件
2、在VueX store中配置
3、在组件中使用
7、常见的事件修饰符及其作用
事件修饰符,是一种语法糖,用于在模板中对事件处理器进行预处理,从而实现简单的事件行为操作。
.stop:调用
event.stopPropagation()
,阻止事件向父级元素冒泡。点击事件只在当前元素上触发,不再传递给父组件或父元素时使用。1
<button @click.stop="handleClick">点击我</button>
.prevent:调用
event.preventDefault()
,阻止事件的默认行为(如提交表单、链接跳转)。1
2
3
4<form @submit.prevent="handleSubmit">
<input type="text">
<button type="submit">提交</button>
</form>.stop.prevent:同时阻止事件冒泡和默认行为,可以连写。
1 |
|
.capture:在事件捕获阶段触发事件处理器,而不是等待事件冒泡到目标元素时使用。
1
2
3<div @click.capture="handleCapture">
<button @click="handleButtonClick">点击按钮</button>
</div>.self:只有事件的目标(event.target)是绑定事件的元素本身时才触发处理器。
1
2
3
4
5<!-- 只有点击 div 自身才触发 -->
<div @click.self="handleSelfClick" style="padding: 20px; border: 1px solid #ccc;">
<button>点击我不会触发 div 的事件</button>
<p>点击空白区域才触发 div 的事件</p>
</div>.once:事件处理器只触发一次,触发后自动解绑。
1
<button @click.once="handleOnce">只点击一次有效</button>
.passive(vue3):告诉浏览器,这个事件监听器不会调用
event.preventDefault()
。监听器是“被动”的(passive),浏览器可以放心地继续执行默认操作(如页面滚动)。.native:用于自定义组件上,想监听该组件根元素的原生 DOM 事件时使用。
区分自定义事件和原生事件:
创建一个自定义组件时,可以在组件内部通过$emit('click')
发出一个事件。父组件如果写<my-component @click="handler">
,默认是监听这个自定义事件,而不是组件根元素的 DOM 事件。使用
.native
修饰符时,Vue 会将事件监听器直接绑定到自定义组件的根 DOM 元素上(即组件模板的最外层元素),从而捕获该 DOM 元素上触发的原生事件。在 Vue 3.x 中,
.native
修饰符已被移除,Vue 3 提供了更灵活的事件监听机制,可以直接在自定义组件上监听原生事件。Vue 3 会自动将@click
绑定到<CustomButton>
的根元素上,无需.native
修饰符。
8、v-if、v-show、v-html 指令的原理
1、v-if:决定是否在DOM中创建、销毁元素。
当条件为 true 时,Vue 会生成该元素的虚拟 DOM,并挂载到真实 DOM 上。
当条件为 false 时,该元素及其子节点完全不会出现在 DOM 中,也不会占用任何内存。
使用场景:切换频率低、大量DOM节点。使用 v-if 可以避免不必要的渲染开销。但由于每次切换都需要重新创建或销毁 DOM,所以切换频繁时性能可能不如 v-show。
2、v-show:基于 CSS 的显示隐藏。
无论条件是否为 true,都始终会在 DOM 中渲染该元素。只是通过修改元素的 CSS 属性(
display
)来控制元素的可见性。相当于display 属性被设置为none
。
3、v-html:将一个字符串解析为 HTML,然后插入到元素内部。
会将绑定的字符串直接设置为元素的
innerHTML
,从而渲染出相应的 HTML 内容。
9、 v-model 是如何实现的
v-model实现了数据的双向绑定。
语法糖:在表单模板中写 <input v-model="message">
实际上是 Vue 内部自动转换成类似下面的代码:
1 |
|
父组件使用 v-model:本质通过prop和$.emit实现。在自定义组件上使用 v-model
时,Vue 会默认将其绑定到组件的 value
prop 上,并监听组件发出的 input
事件。
10、data为什么是一个函数而不是对象
让每个组件都有自己独立的数据副本,而不是共享一个对象,有利于组件的复用!
组件数据隔离:
将 data
定义为函数可以确保每个组件实例拥有独立的数据副本,避免状态共享带来的副作用。‘
根实例特殊性:
由于根实例只有一个,所以可以将 data
定义为对象;但组件(可复用、多实例)必须使用函数返回数据对象。
1 |
|
11、对keep-alive的理解
包装器组件,不渲染实际的DOM元素,而是作为一个抽象组件,通过内部机制缓存子组件的实例状态。
缓存动态组件的实例,避免反复创建与销毁。
<keep-alive>
内部会维护一个缓存对象,当子组件第一次渲染时,会把其 VNode 存储起来;当切换到其他组件时,被缓存的组件不会调用销毁钩子(如beforeDestroy
、destroyed
),而是调用deactivated
钩子;当组件再次激活时,则调用activated
钩子。activated:当组件被激活(即从缓存中恢复显示)时调用。
deactivated:当组件被停用(即离开视图,但缓存保留)时调用。
通过 include
和 exclude
属性精确控制哪些组名称需要缓存。
1 |
|
12、$nextTick 原理及作用
修改了响应式数据后,数据更新后,DOM 可能还没有完成更新。
$nextTick
允许在 DOM 更新完成后执行回调函数。确保在回调函数执行时,DOM 已经完成了所有由当前数据变化引起的更新。
1 |
|
13、给 data 中的对象属性添加一个新的属性
Vue 2 采用 Object.defineProperty
对已存在的属性进行响应式处理,而无法检测后续新增的属性。自然就不会触发视图的更新。
使用Vue.set、this.$set
1 |
|
在 Vue 3 中,由于采用了 ES6 的 Proxy
实现响应式,所以直接添加新属性也能被拦截,从而实现响应式更新,因此这个问题在 Vue 3 中不存在。
14、Vue封装数组的方法
为了实现数组的响应式更新,Vue对数组的方法进行封装或者重写,确保调用他它们的时候能通知依赖更新,让视图随着数组的变化得到更新。调用方法时,Vue会在执行原有逻辑的同时触发依赖收集器的通知,让所有依赖该数组的watcher得到更新,从而重新渲染视图。
- push:向数组末尾添加一个或多个元素。
- pop:从数组末尾移除最后一个元素。
- shift:从数组头部移除第一个元素。
- unshift:向数组头部添加一个或多个元素。
- splice:在数组的指定位置添加或删除元素。
- sort:对数组进行排序。
- reverse:反转数组中元素的顺序。
使用索引修改数组元素,不会被响应式系统检测到。需要使用set。
Vue.set(this.items, index, newValue)
或 this.$set(this.items, index, newValue)
。
在 Vue 3 中,由于采用了 ES6 的 Proxy
实现响应式,所以可以直接检测数组的新增、修改等操作,无需像 Vue 2 那样重写数组方法。
15、Vue 单页应用与多页应用的区别
单页应用SPA:通常只有一个HTML文件,整个应用在首次加载时通过JS加载所有必要资源(HTML,CSS,JS,图片等)。使用Vue Router路由库在客户端进行路由管理,根据URI变化加载不同的组件或者视图,无需刷新整个页面。根据用户操作动态切换视图,所有页面切换都在当前页面完成,不会触发页面重新加载。整个应用由一个根实例管理,数据状态可以在各个组件之间共享和传递。
多页应用MPA:每个页面都有独立的HTML文件,页面切换依赖服务器加载不同的页面。页面导航也是由服务器处理,每次跳转要完整刷新页面,每个页面都是相对独立的应用,数据状态不会跨页面共享,依赖后端管理或者浏览器存储。
16、Vue自定义指令
指令:带有v-前缀的特殊属性,用来操作DOM的(条件渲染v-if、绑定-bind等)
自定义指令:封装DOM操作逻辑
定义对象时,可以包含多个钩子函数:
Vue 2 自定义指令的钩子名称:
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性初始化设置,但此时还没有插入 DOM。
inserted:被绑定元素插入到 DOM 时调用,此时可以操作 DOM。
update:所在组件的 VNode 更新时调用,但可能发生在子节点更新之前。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用,用于清理工作。
Vue 3 自定义指令的钩子名称一般为:
- created:指令第一次绑定到元素时调用,且在挂载前调用。
- beforeMount:在元素插入 DOM 前调用。
- mounted:在元素插入 DOM 后调用(通常用来操作 DOM)。
- beforeUpdate:在组件更新前调用。
- updated:在组件更新后调用。
- beforeUnmount:在指令解绑前调用。
- unmounted:在指令解绑后调用。
17、子组件可以直接改变父组件的数据吗
在 Vue 中,子组件不能直接修改父组件的数据,因为父组件通过 props 向子组件传递的数据是只读的。
父组件向子组件传递数据时,子组件通过 props 接收。Vue 将这些 props 设置为只读属性,目的是防止子组件直接修改父组件的数据,以保持数据流向的清晰和可预测。
如果子组件需要改变父组件的数据,应该通过触发事件(例如使用 this.$emit
)来通知父组件,由父组件在接收到事件后进行数据修改。这样既遵循了单向数据流,也保持了组件间的解耦。
18、 Vue是如何收集依赖的
每个data属性在初始化时会被遍历,被Object.defineProperty
劫持,给每个属性添加getter和setter。这样读取属性时会执行getter,修改属性时,会执行setter。
Dep依赖管理器:每个响应式属性都有一个关联的Dep对象,负责维护依赖列表,保存所有依赖的引用。
Watcher:每个组件渲染过程都会创建一个Watcher,代表了组件的渲染函数。渲染的时候,Watcher会访问data中的响应式属性,从而触发这些属性的getter。
依赖收集的流程
1、设置Watcher:组件渲染时,把当前watcher赋值给全局的Dep.target。
2、读取数据,触发getter:渲染过程中,在模板读取data中的属性时,调用该属性的getter。getter内部会调用Dep.depend。
3、注册依赖:depend方法检查当前有没有Dep.target,有就把Watcher添加到当前属性对应的Dep中,Watcher也会把这个Dep收集起来。
4、数据更新通知:数据变化时,setter被调用,通知对应的Dep对象。然后Dep会遍历所有订阅的watcher,触发它们的更新,进而重新渲染组件或者重新计算计算属性。
19、assets和static的区别
两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下。
如果资源需要在组件中动态引用、参与模块化打包、优化、缓存控制,建议放在 assets 文件夹中。
如果资源不需要经过打包处理,且希望保持原始文件名和内容(例如外部库、静态 HTML、favicon 等),则放在 static/public 文件夹中。
20、Vue如何做性能优化
1、异步批量更新
利用 this.$nextTick()
确保在 DOM 更新后执行依赖最新 DOM 状态的逻辑。
2、合理使用计算属性
将复杂的衍生数据计算封装为 computed 属性,而不是放在 methods 中每次调用时重新计算。计算属性具有缓存特性,只在依赖数据发生变化时重新计算,避免重复计算。
3、合理选择 v-if 与 v-show
v-if:适用于不频繁切换的场景,因为每次切换会创建或销毁 DOM。
v-show:适用于频繁切换的场景,因为它只改变 CSS 的 display 属性,不会销毁 DOM。
4、使用 keep-alive 缓存组件
<keep-alive>
会缓存被包裹的动态组件实例,避免重复创建,保留组件状态。
5、动态 import & 路由懒加载
在路由配置中使用动态 import,减少初始加载文件大小,按需加载,提升首屏加载速度。
1 |
|
6、Tree Shaking:利用 webpack 移除未使用的代码,减小打包文件体积。
7、图片与静态资源优化:使用合适的 Loader 对图片、字体等资源进行压缩、优化,生成带 hash 值的文件名以利于缓存。将无需处理的静态资源放在 public 文件夹中,直接复制到输出目录,减少构建时间。
8、全局状态管理:使用 Vuex 或 Pinia 集中管理状态,避免不必要的组件间数据传递和重复渲染。
9、服务端渲染(SSR):对于需要 SEO 和快速首屏加载的应用,可以使用 Nuxt.js 等解决方案进行服务端渲染,将初始内容在服务器端生成并发送给浏览器。