文章目录
- 前言
- 正文
- 什么是 MVVC
- 什么是 MVVM
- 什么是 SPA
- 什么是SFC
- 为什么 data 选项是一个函数
- Vue 组件通讯(传值)有哪些方式
- Vue 的生命周期方法有哪些
- 如何理解 Vue 的单项数据流
- 如何理解 Vue 的双向数据绑定
- Vue3的响应式原理是什么
- 介绍一下 Vue 的虚拟 DOM
- 介绍一下 Vue 的 diff 算法
- Vue中key的作用
- v-if 和 v-show 的区别
- v-if 和 v-for 为什么不建议放在一起使用
- 计算属性(computed) 和 方法(methods) 的区别
- 计算属性(computed) 和 侦听器(watch) 的区别
- Vue中如何监控某个属性值的变化
- v-model 是如何实现的,语法糖实际是什么
- Vuex是什么?怎么使用
- 路由传值的方式有哪几种
- $route 和 $router 的区别
- 怎么定义 vue-router 的动态路由?怎么获取传过来的值
- vue-router 有哪几种路由守卫
- Vue.$nextTick
- Vue 实例挂载过程中发生了什么
- Vue 的模版编译原理
- computed 和 watch 的区别
- keep-alive 是什么
- 什么是mixin,如何使用
- 什么是插槽,如何使用
- Vue 中的修饰符有哪些
- Vue Router中的常用路由模式和原理
- 页面刷新后Vuex 状态丢失怎么解决
- 关于 Vue SSR 的理解
- 了解哪些 Vue 的性能优化方法
- Vue3的新特性有哪些
- reactive和ref的区别是什么
- Vue3和Vue2的区别
- Vue3的新工具 vite 和 Webpack 有什么区别
前言
Vue 官推消息,Vue 2 将于 2023年12月31日 停止维护(EOL)。
Vue 2.0 发布于 2016 年,迄今为止已有7年时间。Vue 2.0 的发布是其成为主流框架过程中的一个重要里程碑。
然而随着 2020 年 9 月份发布的 Vue 3 及其生态系统的日渐成熟,Vue 2.0 也渐渐退出了我们的视线,众多开发者们也在逐渐向 Vue3 靠拢。
早在2023年初,众多公司的面试重点就已经开始向Vue3倾斜。
2024有跳槽打算的朋友,拿下Vue3是重中之重。
正文
什么是 MVVC
- MVC全名是 Model View Controller,时模型 - 视图 - 控制器的缩写,一种软件设计典范。
- Model(模型):是用于处理应用程序数据逻辑部分。通常模型对象负责在数据库中存取数据。
- View(视图):是应用程序中处理数据显示的本分。通常视图是依据模型数据创建的。
- Controller(控制器):是应用程序处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
- MVC的思想:一句话描述就是Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View。
什么是 MVVM
- MVVM新增了VM类。
- ViewModel层:做了两件事达到了数据的双向绑定,一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。 实现的方式时:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转换成后端的数据。实现的方式是:DOM事件监听。
- MVVM与MVC最大的区别就是:实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再手动操作Dom元素来改变View的显示。 而是改变属性后该属性对应的View层显示会自动改变(对应Vue数据驱动的思想)
- 整体看来,MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也察觉不到View,这种低耦合模式提高代码的可重用性。
- 注意:Vue并没有完全遵循MVVM的思想,这一点官网自己也有声明。
- 那么问题来了,为什么官方要说Vue没有完全遵循MVVM思想呢?
- 严格的MVVVM要求View不能和Model直接通信,而Vue提供了$refs这个属性,让Model可以直接操作View,违反了这一规定,所以是Vue没有完全遵循MVVM。
什么是 SPA
- 单页面应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。
- 一些应用在前端需要具有丰富的交互性、较深的会话和复杂的状态逻辑。Vue 不仅控制整个页面,还负责处理抓取新数据,并在无需重新加载的前提下处理页面切换。
什么是SFC
- Vue 的单文件组件 (即 *.vue 文件,英文 Single-File Component,简称 SFC) 是一种特殊的文件格式,使我们能够将一个 Vue 组件的模板
<template>
、逻辑<script>
与样式<style>
封装在单个文件中。 - 使用 SFC 的优点:
- 使用熟悉的 HTML、CSS 和 JavaScript 语法编写模块化的组件
- 让本来就强相关的关注点自然内聚
- 预编译模板,避免运行时的编译开销
- 组件作用域的 CSS
- 在使用组合式 API 时语法更简单
- 通过交叉分析模板和逻辑代码能进行更多编译时优化
- 更好的 IDE 支持,提供自动补全和对模板中表达式的类型检查
- 开箱即用的模块热更新 (HMR) 支持
- SFC 的主要场景:
- 单页面应用 (SPA)
- 静态站点生成 (SSG)
- 任何值得引入构建步骤以获得更好的开发体验 (DX) 的项目
为什么 data 选项是一个函数
- 组件的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一分新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。
- 而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
Vue 组件通讯(传值)有哪些方式
- 组件通信常用方式有以下几种
- props / $emit:适用 父子组件通信
- 父组件向子组件传递数据是通过 prop 传递
- 子组件传递数据给父组件是通过 $emit 触发事件来做到
- ref 与 $parent:适用 父子组件通信
- ref,如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
- ref,如果用在子组件上,引用就指向组件实例
- $parent:访问访问父组件的属性或方法
- EventBus ($emit / $on):适用于 父子、隔代、兄弟组件通信
- $attrs:适用于 隔代组件通信
- $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。
- 当一个组件没有声明任何 prop时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind=“$attrs” 传入内部组件。
- provide / inject:适用于 隔代组件通信
- 祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
- provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系
- $root:适用于 隔代组件通信 访问根组件中的属性或方法,是根组件,不是父组件。
- Vuex 或 Pinia:适用于 父子、隔代、兄弟组件通信
- props / $emit:适用 父子组件通信
- 根据组件之间关系讨论组件通信最为清晰有效
- 父子组件:props / $emit / $parent / ref
- 兄弟组件:$parent / eventbus / vuex
- 跨层级关系:eventbus / vuex / provide+inject / $attrs / $root
Vue 的生命周期方法有哪些
- beforeCreate 在组件实例初始化完成之后立即调用。
- 会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用。
- 在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问。
- created 在组件实例处理完所有与状态相关的选项后调用。
- 在这一步,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。
- 然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。
- beforeMount 在组件被挂载之前调用。
- 组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。
- 它即将首次执行 DOM 渲染过程。
- mounted 在组件被挂载之后调用。
- 在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom节点。
- beforeUpdate 在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。
- 这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。
- updated 在组件因为一个响应式状态变更而更新其 DOM 树之后调用。
- 父组件的更新钩子将在其子组件的更新钩子之后调用。
- 这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。
- 如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
- **beforeUnmount **实例被卸载之前调用。
- 在这一步,实例仍然完全可用。
- 我们可以在这时进行 善后收尾工作,比如清除定时器。
- unmounted 实例卸载后调用。
- 调用后,Vue实例指示的东西都会卸载,所有的事件监听器会被移除,所有的子实例也会被卸载。
- activated 若组件实例是
<KeepAlive>
缓存树的一部分,当组件被插入到 DOM 中时调用。 - deactivated 若组件实例是
<KeepAlive>
缓存树的一部分,当组件从 DOM 中被移除时调用。
异步请求在哪一步发起?
- 可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data已经创建,可以将服务器端返回的数据进行赋值。
- 如果异步请求不需要依赖 DOM,推荐加载 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面loading时间;
- ssr 不支持 beforeMount、mounted 钩子函数,所以放在 created 中有助于一致性。
如何理解 Vue 的单项数据流
- 数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样可以防止从子组件意外改变父组件的状态,从而导致你的应用的数据流向难以理解。
- 注意:在子组件直接用 v-model 绑定父组件传过来的 props 这样是不规范的写法,开发环境会报警告。
- 如果实在要改变父组件的 props 值可以再data里面定义一个变量,并用 prop 的值初始化它,之后用$emit 通知父组件去修改。
如何理解 Vue 的双向数据绑定
- Vue.js 3中的双向数据绑定可以通过使用
v-model
指令来实现。在Vue.js 2及之前版本中,我们需要手动编写事件处理函数来更新视图或模型,而在Vue.js 3中,这些工作已经被自动化了。 - 当我们将
v-model
应用于表单元素(如输入框、复选框等)时,Vue会为该元素创建一个名为"value"的属性,并且还会为其添加相关的事件监听器。 - 当用户与表单交互时,比如修改文本内容或切换复选框状态,Vue会自动触发对应的事件,然后更新绑定到该元素上的数据。同样地,当我们在JavaScript中直接修改绑定的数据时,也能立即反映在视图上。
- 双向数据绑定的原理,参考下一题:Vue3响应式原理
Vue3的响应式原理是什么
- vue3采用数据代理+数据劫持+发布订阅模式的方法。
- 在初始化vue实例时,通过Proxy进行数据代理,用Proxy对象来代理目标对象,并且对目标对象中的所有属性动态地进行数据劫持,动态地指定一个getter、setter,并通过Reflect操作对象内部数据。
- 当Proxy对象属性或Proxy数组元素发生变化时,会触发Proxy属性的setter方法,然后通过Reflect操作目标对象属性,同时触发它Dep实例的notify方法进行依赖分发,通知所有依赖的Watcher实例执行内部回调函数
介绍一下 Vue 的虚拟 DOM
- 虚拟dom顾名思义就是虚拟的dom对象,它本身就是一个JavaScript对象,只不过它是通过不同的属性去描述一个视图结构,相比于真实dom只保留了核心属性,进而使后续操作更加快速;
- 通过引入vdom我们可以获得如下好处:
- 将真实元素节点抽象成VNode,有效减少直接操作dom次数,从而提高程序性能;
- 直接操作 dom 是有限制的,比如:diff、clone等操作,一个真实元素上有许多的内容,如果直接对其进行diff操作,会去额外diff一些没有必要的内容;同样的,如果需要进行clone,那么需要将其全部内容进行复制,这也是没必要的。但是,如果将这些操作转移到 JavaScript 对象上,那么就会变得简单了;
- 操作 dom 是比较昂贵的,频繁的dom操作容易引起页面的重绘和回流,但是通过抽象VNode进行中间处理,可以有效减少直接操作dom的次数,从而减少页面重绘和回流;
- 方便实现跨平台
- 同一VNode节点可以渲染成不同平台上的对应的内容,比如:渲染在浏览器是dom元素节点,渲染在Native(iOS、Android)变为对应的控件、可以实现SSR(服务端渲染)、渲染到WebGL中等等;
- Vue3中允许开发者基于VNode实现自定义渲染器(renderer),以便于针对不同平台进行渲染;
- vdom如何生成,又如何成为真实dom?以及在diff中的作用
- 在vue中我们常常会为组件编写模板template,这个模板会被编译器compiler编译为渲染函数(render function),在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。但它们还不是真正的dom,会在后续的patch过程中进一步转化为真实dom:
- 挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。
介绍一下 Vue 的 diff 算法
- DOM操作是非常昂贵的,因此我们需要尽量地减少DOM操作。这就需要找出本次DOM必须更新的节点来更新,其他的不更新,这个找出的过程,就需要应用 diff 算法
- vue的diff算法是平级比较,不考虑跨级比较的情况。
- 内部采用深度递归的方式+双指针(头尾都加指针)的方式进行比较。
- Vue 中的 diff 算法称为 patching 算法,它由 Snabbdom 修改而来,虚拟 DOM 要想转化为真实 DOM 就需要通过 patch 方法转换
- 最初 Vue1.x 视图中每个依赖均有更新函数对应,可以做到精准更新,因此并不需要虚拟 DOM 和 patching 算法支持,但是这样粒度过细导致 Vue1.x 无法承载较大应用;Vue 2.x 中为了降低 Watcher 粒度,每个组件只有一个 Watcher 与之对应,此时就需要引入 patching 算法才能精确找到发生变化的地方并高效更新
- vue 中 diff 执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行 render 函数获得最新的虚拟 DOM,然后执行 patch 函数,并传入新旧两次虚拟 DOM,通过比对两者找到变化的地方,最后将其转化为对应的 DOM 操作
- patch 过程是一个递归过程,遵循深度优先、同层比较的策略;以 vue3 的 patch 为例:
- 首先判断两个节点是否为相同同类节点,不同则删除重新创建
- 如果双方都是文本则更新文本内容
- 如果双方都是元素节点则递归更新子元素,同时更新元素属性
- 更新子节点时又分了几种情况
- 新的子节点是文本,老的子节点是数组则清空,并设置文本;
- 新的子节点是文本,老的子节点是文本则直接更新文本;
- 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素;
- 新的子节点是数组,老的子节点也是数组,那么比较两组子节点,更新细节 blabla
- vue3中引入的更新策略:静态节点标记等
Vue中key的作用
- key 是为Vue中Vnode的唯一标识,通过这个key,我们的diff操作可以更准确、更快速。
- 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。
- 更准确:因为带key就不是就地复用了,在sameNode函数 a.key === b.key 对比中可以避免就地复用的情况。所以更加准确。
- 更快速:利用key的唯一性生成map对象来获取对应节点,比遍历方式块。
v-if 和 v-show 的区别
- 规则
- v-show 隐藏规则是为该元素添加css属性display:none,dom元素依旧还在。
- v-if 显示隐藏是将dom元素整个添加或删除
- 编译过程:
- v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;
- v-show只是简单的基于css切换
- 编译条件:
- v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染
- 生命周期:
- v-show 由false变为true的时候不会触发组件的生命周期
- v-if由false变为true的时候,触发组件的beforeCreate、create、beforeMount、mounted钩子,由true变为false的时候触发组件的beforeDestory、destoryed方法
- 性能消耗:
- v-if有更高的切换消耗;
- v-show有更高的初始渲染消耗
- 使用场景:
- v-if 与 v-show 都能控制dom元素在页面的显示
- v-if 相比 v-show 开销更大的(直接操作dom节点增加与删除)
- 如果需要非常频繁地切换,则使用 v-show 较好
- 如果在运行时条件很少改变,则使用 v-if 较好
v-if 和 v-for 为什么不建议放在一起使用
- 因为 v-for 的优先级比 v-if 的优先级高,所以每次渲染时都会先将列表渲染出来,再通过条件判断进行显示隐藏,所以将 v-if 和 v-for 用在一起会特别消耗性能
- 可以通过计算属性 computed 提前过滤掉不符合条件的项
计算属性(computed) 和 方法(methods) 的区别
- 调用方式:
computed
是属性调用,在定义函数时以属性的形式调用,methods
是方法调用,在定义函数时以方法的形式调用,要加()
- 缓存功能:
computed
是计算属性,是有缓存的,当它的依赖属性改变的时候,才会进行重新计算,如果数据没有改变,它是不会运行的。methods
是方法,没有缓存功能,只要你调用一次,不管数据是否改变,它都是执行。
- 所以,同等条件下,
computed
比methods
性能好一点,避免重复执行。
计算属性(computed) 和 侦听器(watch) 的区别
- computed 是计算属性,依赖其它属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容,他可以设置getter和setter。
- watch 监听到值的变化就会执行回调,在回调中可以进行一系列的操作。
- 计算属性一般用在模板渲染中,某个值是依赖其它响应对象甚至是计算属性而来;
- 而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。
Vue中如何监控某个属性值的变化
- Vue3中使用
watch
方法来监听响应式数据的变化,并在数据变化时执行回调函数。
import { watch } from 'vue'
const state = reactive({
count: 0
})
watch(() => state.count, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
v-model 是如何实现的,语法糖实际是什么
v-model
本质上就是语法糖,在使用v-model
后既绑定了数据,又添加了一个@input
事件监听<input v-model='message' />
- 等价于
<input :value='message' @input='message = $event.target.value'>
- 当在input元素中使用
v-model
实现双数据绑定,其实就是在输入的时候触发元素的input事件,通过这个语法糖,也能够实现父子组件数据的双向绑定 - 如何实现
- 作用在表单元素上:
- 动态绑定了
input
的value
指向了messgae
变量 - 并且在触发
input
事件的时候去动态把message
设置为目标值:
- 动态绑定了
- 作用在表单元素上:
<input v-model='message' />
// 等同于
<input
:value="message"
@input="message=$event.target.value"
>
- 作用在组件上:
- 在自定义组件中,
v-model
默认会利用名为value
的prop
和名为input
的事件 - 本质是一个父子组件通信的语法糖,通过
prop
和$.emit
实现。 因此父组件v-model
语法糖本质上可以修改为:
<child :value="message" @input="function(e){message = e}"></child>
- 在组件的实现中,可以通过
v-model
属性来配置子组件接收的prop
名称,以及派发的事件名称。