什么是数据的响应式?
数据的响应式即数据发生变化时会触发视图自动变化,不用操作真实Dom,这种机制使得开发者能够专注于数据的处理和逻辑的编写,而不需要关心视图的同步问题。
数据的响应式是如何实现的?
Vue2
依赖收集
- 在vue2中是通过Object.defineProperty来实现的。
- 在Vue组件实例创建的时候会循环遍历data中的属性,通过defineReactive 方法为每个响应式属性创建 getter 和 setter。defineReactive内部实现是使用Object.defineProperty为每个key添加setter和getter来实现对数据的劫持。
- 在getter中会进行依赖收集,在getter中会为每一个key创建一个Dep实例,每个组件编译后都会创建一个Watcher实例,当模板中访问到响应式属性的数据,就会当前个Watcher添加到Dep实例的订阅列表中,同时Watcher内部也会维护一个依赖列表,并将使用到的Dep实例添加到自己的依赖列表中。
- 上面过程就是将属性和使用到属性的地方建立起关联,这就是依赖收集的过程。
派发更新
- 在setter中会进行派发更新,当对某个属性发生变化的时候,就会走到set中,通过属性拿到关联的Dep实例。
- 调用Dep的 notify方法,notify方法会遍历Dep的订阅列表,执行其关联的回调函数,进行更新。
- 这个Watcher的更新也是异步执行的,通常Vue会将Watcher放到一个队列中,等同步代码执行完毕后进行批量更新,以此来避免不必要的DOM计算。这就是派发更新的整个过程。
存在问题
vue2的响应式是基于Object.defineProperty实现的,该方法只能对已存在的属性进行劫持,而不能劫持整个对象的新增/删除属性,数组也是一个特殊的对象,使用Object.defineProperty只能拦截对象的属性访问和修改,而不能直接拦截数组的特定方法(如push、pop等),因此也无法监听到这些变化。如果Vue实现对整个对象的劫持,将会大大增加vue的复杂性和性能开销,所以vue就采用了其他方案进行解决。
如何解决
对于对象,数组可以使用Vue.set(this.$set)方法来添加响应式属性,从而触发视图的更新。而且对于数组,vue2还在内部还重写了数组的7个方法(push,pop,shift,unshift,sort,reverse,splice),以确保能够正确地触发响应式系统的更新。
Vue3
依赖收集
- vue3中通过reactive和ref将数据变成响应式,内部的实现是使用ES6之后的Proxy来代理对象。
- 将数据转为代理对象,当组件首次渲染时,模板中使用到的数据,就会走到该属性的getter中,track会将当前正在执行的副作用函数(如组件的渲染函数)与访问到的响应式属性关联起来,并记录到依赖映射中。
- vue3通过的是weakkMap的形式,将依赖关系进行存储。这就是整个依赖收集的过程。
派发更新
- 当属性发生改变的时候,会调用trigger函数,tigger函数会遍历与该属性相关联的依赖列表,
- 并循环调用每个依赖项的更新函数。
与vue2的区别
因为vue3使用的是proxy来创建一个代理对象从而实现数据的响应式,可以对整个对象进行代理,所以不存在对象的新增删除以及监听不到数组的方法的问题。
reactive和ref区别?
reactive和ref都是创建数据响应式的方法.
区别
访问方式
- ref:使用ref创建的响应式数据需要使用.value来读取和修改。
- reactive:使用reactive创建的数据响应式则直接读取修改即可.
处理数据类型
- ref:一般用来处理基本数据类型例如(string,number,boolean)等。
- reactive:一般用来处理复杂数据类型例如(Array,Object)这些。
深度理解区别
- ref:解决单一元素或数据的响应式需求,如果使用ref包裹复杂数据类型,内部还是会转使用reactive响应式,只不过在读取修改时需要使用.value,内部使用的还是reactive数据响应式那部分的逻辑。
- reactive:解决复杂数据的响应式需求,内部使用proxy实现数据响应式,proxy是会对象进行代理生成响应式数据对象,传入基本数据类型还是要转化为对象才能进行数据响应式,这样很消耗性能,所以又设计一个ref。
vue的diff算法详解
什么是虚拟DOM?
虚拟DOM就是对真实DOM抽象描述的对象。
使用虚拟DOM的好处?
性能优化: 操作真实的DOM会频繁触发页面的回流和重绘。然而通过比较新旧虚拟DOM的差别,以最小代价把变化的那部分更新到真实DOM中。减少直接的DOM操作,提高性能。 跨平台能力: 一套虚拟DOM可以转换为多套代码,运行在不同平台。
vue2diff算法详解
同层比较(深度优先算法)
同层比较每个节点及子节点,从根节点开始,递归子节点进行新旧DOM的比较。
- 首先进行遍历首尾比较法::在比较过程中,如果节点类型和key相同,则直接复用节点。
- 头头比较
- 尾尾比较
- 头尾比较: 头尾和尾头比较主要用于节点的移动复用
- 尾头比较
- 处理剩余节点: 比对完毕后,根据新旧节点的数目来判断新增节点和删除节点。
vue3diff算法详解(快速diff算法)。
- 首先进行前置预处理: 从头到尾遍历新旧节点列表,如果是相同类型节点,直接进行patch更新;
- 后置预处理: 从尾至头遍历新旧节点列表,如果是相同类型则进行patch更新;
- 进行新增卸载操作: 前置预处理和后置预处理完毕后,根据遍历的索引与新旧节点列表末尾索引的比对来判断是进行新增/卸载操作。
- 复杂情况处理: 当遇到既有新增、又有卸载,且节点顺序可能发生变化时,进行复杂操作
- 构建新节点索引位置映射表: 遍历新节点,使用Map记录每个key与其在新节点位置的索引。
- 构建新旧节点位置索引映射表: 记录新节点与旧节点索引位置。
- 遍历旧节点列表: 如果新节点位置映射表中有对应的旧节点,则直接更新新旧节点,如果没有则说明该节点被卸载了
- 判断节点的移动: 遍历过程中,比较新旧节点索引位置映射表,来判断节点是否发生了移动。
- 计算最长递增子序列: 如果需要移动然后vue3会利用最长递增子序列算法来优化移动操作,进行最小程度的移动。
vue中通讯方式
父子
- props: 父传子,单项数据流
- $emit: 子传父,单项数据流
- ref: 通过给子组件添加ref属性,可以通过this.$refs访问子组件数据和方法
<Child ref="childRef" />
`this.$refs.childRef`访问子组件。
- Provide,inject: 祖先与后代之间的传值
全局
- EventBus事件总线 EventBus.on('eventName', handler)
- parent访问父组件上的数据和方法,$root 访问跟组件数据和方法
- vuex/pinia
为什么Vue2中的data是函数而不是对象?
因为组件可能会被多次复用,如果使用对象,那么每个实例都共用一个对象,会造成每个实例数据相互影响。而使用函数确定每次创建组件都有自己的实例,每个实例单独维护数据的状态,互不影响。
vue2和vue3有哪些区别
- 实现响应式原理不同。
- API特性:vue2选项式API,vue3组合式API
- 生命周期的改变
- TS支持
- vue3支持多个根节点
- 打包体积的优化。vue3使用更有效tree-sharking机制,允许去除代码中未使用的部分
- 性能提升,vue3引入PatchFlag,在编译的时候会标记节点的动态部分,在更新的时候,只关注标记的部分,不需要进行全量更新,显著提高了性能。
vue 中 v-if 和 v-show 区别
1.v-if 是动态向 dom 树中添加或者删除 dom 元素,而 v-show 则是通过控制 dom 元素的 display 属性来控制显隐的
2.v-if 切换消耗较大性能,v-show 首次渲染有较大消耗
3.如果需要频繁切换显示隐藏使用 v-show’比较好,当不大可能改变的时候使用 v-if
vue数据的双向绑定?
是什么
- 数据和视图相互驱动更新,是相互影响的关系。
- 数据的响应式式数据发生改变引起视图的变化。
体现
- v-model体现了数据的双向绑定,
- v-model作用在表单上相当于v-bind和v-on的一个语法糖
<input v-model="message">
等价于
<input v-bind:message v-on:input='message = e.target.value'>
原理
- 采用“数据劫持”结合“发布者-订阅者”模式的方式来实现
vue路由模式
- history模式
- hash模式
- Abstract模式:主要用于非浏览器环境,vue-router不会对url进行任何处理,而是将路由信息保存在内存中。
常用路由模式分析
- history更美观,hash地址栏会有#号
- hash兼容性更好,historyIE10后才可兼容
- 浏览器行为,hash模式是urlhash的改变,浏览器触发hashchange事件,前端路由根据变化进行页面的渲染,history则是根据History API进行url的改变,前端路由根据url的改变进行渲染。
- 刷新页面,history模式会出现404问题,需要做好重定向。
watch、watchEffect、computed、methods
watch
- 监听数据变化: 可以监听一个数据也可以监听多个数据的变化。
- 执行时机: 默认第一次不会执行,除非将第三个函数设置为{"immediate":true}。
- 可执行副作用操作: 可以在回调用执行一些产生副作用的操作。
- 支持异步: 侦听器非常适合执行异步操作或具有显著开销的操作。
- 无缓存: 检测到数据变化,就会执行一次。。
watchEffect
- 执行: 默认第一次就执行。
- 使用场景: 特别适合不需要知道监听数据前后的变化,只需要在数据变化后执行相关的操作这种场景。
computed
- 缓存: 计算属性的值会基于其响应式依赖而被缓存,一个计算属性仅会在其响应式依赖更新时才会重新计算。
- 无副作用: 计算属性用来用来基于计算,不改变依赖数据的值。
- 用于模板: 通常用于模板中。
- 同步: 计算属性需要在同步代码中执行。
methods
- 非响应式: 通常用于处理用户交互、逻辑计算或调用 API 等,可以通过修改响应式数据来间接地影响视图的更新。
- 无缓存: 每次被调用时都会执行,并且不会被缓存。
- 可复用: 可以在组件多个地方调用。
- 可执行异步操作
SPA单页面应用的理解
理解
- 单页面应用就是页面在第一次渲染的时候就加载相应的HTML,CSS,js和一些异步加载的数据;
- 一旦页面加载完成不会因为用户的操作造成页面的重新加载或跳转;
- 取而代之路由的切换来实现html内容的变化,,ui与用户的交流。
优点
- 用户体验好,避免不必要的跳转;
- 对服务器压力比较小;
- 前后端分离,架构清晰。
缺点
- 第一次渲染的时候加载东西比较多,比较耗时;
- 需要前进后退路由管理;
- SEO难度比较大。
VUE中的$nextTick()
作用
主要用于DOM下次更新结束后执行的延迟回调。 当数据发生改变后,视图并不会立即更新,而是等到vue异步队列更新队列清空后才会进行DOM更新。 如果需要在DOM更新后执行某些操作,比如基于新的DOM元素来获取元素尺寸这些。
原理
- 接收一个回调函数,这个回调函数就是用于下次DOM更新后需要执行的代码。
- vue内部会维护一个队列来专门存放所有通过$nextTick()传入的回调函数。
- 在更新DOM之前,会先把这个队列给清空,在DOM更新完成后,在依次执行队列中的回调函数。
- 一旦DOM更新完毕,vue就会执行$nextTick()所有的回调函数.
MVC和MVVM区别
二者都是开发的一种架构模式。
不同
M(模型Model)V(视图View)C(控制器Controller),通信是单向的 M(模型Model)V(视图View)VM(视图模型ViewModel),通信是双向通信的
大型vue项目如何划分结构和组件
使用vue构建项目,项目结构清晰会提高开发效率,熟悉项目的各种配置同样会让开发效率更高
在划分项目结构的时候,需要遵循一些基本的原则:
- 文件夹和文件夹内部文件的语义一致性
- 单一入口/出口
- 就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
- 公共的文件应该以绝对路径的方式从根目录引用
/src外的文件不应该被引入
路由守卫作用
认证和授权
可以在全局守卫中确保用户在访问某些路由之前是否已经登录或者是否有访问某个路由的权限,可以在前置路由守卫中阻挡未通过验证的用户访问页面
数据的预加载
路由独享守卫只在进入路由时触发。比如可以在路由独享守卫中beforeEnter中提前请求好数据,跳转前已经预加载了页面的数据,可以提高用户的体验,避免页面加载的时候请求数据导致的页面空白。
VUE的缓存机制
组件缓存
使用keep-alive对路由视图进行缓存。当切换到其他路由时,被缓存的组件实例不会被销毁,而是会被暂停,并在再次激活时恢复。
<keep-alive>
<router-view></router-view>
</keep-alive>
细粒度缓存
可以使用include和exclude属性来指定哪些组件应该被缓存
<keep-alive include="MyComponent">
<router-view></router-view>
</keep-alive>
keep-alive对应的生命周期
- 当引入keep-alive的时候,页面第一次进入,钩子函数的触发顺序 created > mounted > activated 退出时触发deactivated.
- 当再次进入时只触发activated生命周期。
vue中为什么v-for和v-if不能同时使用
性能消耗、逻辑混乱、可读性差
- 在vue2中v-for的优先级大于v-if,如果将二者放一起,则渲染出来的dom节点是先经过循环然后在经过判断,这就会造成对性能的浪费。
- 在vue3中,与vue2相反,v-if的优先级大于v-for,如果二者一起使用,v-if在判断的时候变量还未存在,坑你出现异常
- 在vue的官方文档中也明确表明了永远不要把v-if和v-for同时用在同一个元素上
解决方法
常用的场景基本解决方案是:
- 使用到循环中的数据然后进行判断,这种场景可以使用过滤器处理完成后在进行页面的渲染。
- 还有一种是不想渲染本应该隐藏的数据,可以使用在外层包裹一个template或者div来处理