Vue相关

41 阅读9分钟

1. MVVM

MVVM分为Model、View、ViewModel:

  • Mdoel:代表数据模型,数据和业务逻辑都在Model层定义
  • View: 负责UI视图,对数据的展示
  • ViewModel: 负责监听Mdoel层数据的改变并且控制视图更新,处理用户操作

Model和View之间没有交互,都是通过ViewModel实现。ViewModel和Mdoel存在双向数据绑定,当Model里面的数据发生改变时会更新View,而View中对于用户操作引起的数据改变也是同步到Model

这种模式实现Model和View的自动同步,开发者只需要专注于数据的维护即可,不需要自己操作DOM

2. Vue的优点

Vue是一个构建数据驱动Web页面的渐进式框架。Vue的目标是通过尽可能简单的API实现 响应数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。Vue的优点,主要有响应式编程、组件化开发和虚拟DOM

  • 响应式编程:在Vue中数据发生变化,页面会自动响应;让开发者不需要再操作DOM,可以专心处理业务逻辑。
  • 组件化开发:页面的不同模块可以通过一个个组件实现,可以提高开发效率、方便重复使用、便于协同开发。
  • 虚拟DOM:可以通过对JavaScript的各种计算,将最终的DOM操作计算出来并优化,在计算过程中并没有真正操作DOM,只有计算完成后才提交对DOM的操作

3. Vue生命周期

什么是生命周期?

Vue生命周期就是一个Vue实例从创建到销毁的整个过程。

生命周期的作用是什么?

在生命周期中会存在一些生命周期函数,让开发者可以生命周期的不同阶段添加业务逻辑

其实和回调是一个概念,当程序执行到一个阶段时,检查是否存在hook(钩子),如果有的话就执行回调

生命周期的阶段

  • beforeCreate:new Vue()之后触发的第一个钩子,当前阶段data、methods、computed、watch上的数据和方法都不能被访问
  • created:已经完成了对数据的观测,可以访问数据,改变数据。但是在这里改变数据不会触发updated函数,可以做一些数据的获取,不能和DOM进行交互,如果想要访问DOM,需要通过nextTick()
  • beforeMount:发生在挂载之前,template已经导入了渲染函数编译,虚拟dom已经创建完成,即将开始渲染。
  • mounted:真实DOM已经挂载完毕,双向数据绑定已经完成,可以操作DOM
  • beforeUpdate:响应式数据发生更新,虚拟dom重新渲染之前被触发,在这个阶段改变数据,不会造成重渲染
  • updated:DOM已经更新完毕,避免在此阶段修改数据,因为可能造成无限循环的更新
  • beforeDestory:发生在实例销毁之前,在此阶段实例仍可以使用,可以在这时进行善后收尾工作,如清除计时器
  • destoryed:发生实例销毁之后,组件被拆解,数据绑定被清除,监听被移除,子实例也被销毁

父子组件生命周期的调用顺序

  1. 加载渲染过程:父beforeCreated -> 父created -> 父beforeMount -> 子beforeCreated -> 子created -> 子beforeMount -> 子mounted -> 父mounted
  2. 子组件更新过程:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
  3. 父组件更新过程:父beforeUpdate -> 父updated
  4. 销毁过程:父beforeDestory -> 子beforeDestory -> 子destoryed -> 父destoryed

4. Vue双向数据绑定实现的原理

Vue主要通过数据劫持发布-订阅模式,利用Object.defineProperty来劫持各个数据的getter和setter,当数据改变时发送消息给订阅者,触发相应的回调。主要分为以下几个步骤:

  1. 需要observer对数据进行递归遍历,给每个属性都添加getter和setter属性,每个属性都会存在一个Dep(属性订阅器),当给数据赋值时就会触发setter,就能监听到数据变化。
  2. compile解析模板指令,将模板中的变量替换成数据,并给每个指令对应的节点都绑定更新函数,添加数据的订阅者。
  3. Wathcer订阅者,是observer和compile之间沟通的桥梁,在自身实例化时往属性订阅器(dep)里面添加自己。待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,改变页面。
  4. Dep属性订阅器,当某个属性值发生变化时,Dep会收到observer的通知,随后调用notify()函数,通知该属性的所有订阅者。

image.png

代码实现请参考:手写一个简易vue响应式带你了解响应式原理 - 掘金 (juejin.cn)

5. Vue2.x中如何检测数组的变化

vue2.x中对数组方法进行了重写,Vue将data里面数组的原型链进行重写,指向自己定义的数组原型方法。当数组调用API时,可以通知依赖更新。如果数组中包含引用类型,会对引用再次递归遍历进行监控

有两种情况无法检测数组的变化:

  1. 当利用数组的索引项赋予新值,vm.arr[index] = newVal。可以通过vm.$set(arr,index,newVal)
  2. 修改数组的长度,vm.arr.length = newLen。可以通过数组的splice方法

6. Vue3使用Proxy的优点

相对vue2中的Object.defineProperty,Proxy有以下优点

  • Object.defineProperty无法检测数组元素下边的变化,通过下标添加元素不能实现
  • Object.defineProperty只能劫持对象的属性,需要对对象的每次属性都进行遍历,proxy可以整体劫持对象,返回一个新对象
  • Proxy不仅可以代理对象,还可以代理数组,可以代理动态增加的属性

7. Proxy只会代理对象的第一层,Vue3是怎样处理这个问题

判断当前Reflect.get的返回值是否是Object,如果是则在通过reactive方法做代理,这样实现了深度观测

8、v-model双向绑定的原理是什么

v-model的本质就是value + input的语法糖,可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性

  • text和textarea元素使用value属性和input事件
  • checkBox和radio使用checked属性和change事件
  • select字段,将value作为prop,将change作为事件

9. Vue2和Vue3的diff算法

虚拟DOM和diff算法

虚拟DOM就是用来表示真实DOM的对象,Vue在模板编译时生成虚拟DOM,随后通过渲染器渲染成真实DOM。当数据更新时,产生新的虚拟DOM树,这时直接用新的虚拟DOM树生成新的DOM树不是最优的办法。

Diff是一种比较算法,对比旧的虚拟DOM和新的虚拟DOM,找出发生变化的虚拟节点,并且只更新对应的真实节点,就是实现准确的更新DOM

Vue2双端diff算法

vue2采用了双端diff算法,核心方法是 updateChildren,通过新前与旧前、新后与旧后、新后与旧前、新前与旧后、暴力查找 五种查找。

新前:newChildren未做处理的第一个节点

新后:newChildren未作处理的最后一个节点

旧前:oldChildren未作处理的第一个节点

旧后:oldChildren未作处理的最后一个节点

暴力查找(乱序):前四种方法方法都没有找到,就需要循环oldChildren生成一个key和index的映射表。然后用newChildren开始节点的key去映射表里面查找,如果找到,就将该节点移动到最前面,原位置用undfined占位,防止老节点移动后破坏了初始的映射表位置,如果没有找到,直接把新节点插入。

对比两个节点是否相同的方法( isSameNode ):判断两个vnode的标签和key值是否相同,如果相同就认为是同一个节点,可以复用。

Vue3快速diff算法

vue3采用了快速diff算法,核心方法是patchKeyedChildren。处理新旧两个组的子节点中相同的前置节点和后置节点。处理完成后,如果无法通过挂载新节点或卸载旧节点完成更新,就需要根据节点的索引关系,构建出一个最长递增子序列,这个子序列所指向的节点就不是不需要移动的节点。

Vue2和Vue3的区别

vue2是全量进行diff,vue3使用了静态标记,只对被标记的节点diff

vue3在模板编译后会在动态标签末尾加上PatchFlag,也就是在生成Vnode时,同时打上标记,patch过程会判断这个标记来diff优化流程。

详情参考:说说 vue2 和 vue3 核心diff算法 - 掘金 (juejin.cn)

10. vue组件之间传值的方式

  • 父子组件通信 props / $emit
  • 事件总线 EventBus $emit / $on
  • vuex
  • 多级组件传递数据 $attrs / $listeners
  • 祖先和子孙组件 provide / inject
  • 父子组件 $parent / $children
  • ref

详细请看:vue中组件之间传值的六种方式(完整版)_vue 组件传值_小邱程序员的博客-CSDN博客

11. Vue路由实现

hash模式和history模式

  • hash模式:#后面hash的改变不会触发对浏览器的请求,也就不会刷新页面,通过监听 hashChange 就可以知道hash值的改变,然后根据hash值更新页面的内容
  • history模式:主要是通过HTML5的两个API pushStatereplaceState,可以改变URL,但是不会发送请求。这样就可以监听url变化来实现更新页面的内容
  • 两者的区别:
    • URL的展示:hash里面有存在#
    • 刷新页面时,hash可以正常加载到hash对应的页面,history如果没有处理的话,会出现404。因为浏览器向这个地址发出请求时,文件资源不存在。所以需要后端将所有的请求都拦截到index.html上
    • hash可以支持低版本的浏览器和IE

$route$router 的区别

  • $route表示当前的路由信息,包含了当前URL解析得到的信息
    • $route.params 路由参数
    • $route.query URL查询参数
    • $route.hash 当前路由的 hash 值 (不带 #)
    • $route.fullPath 完成解析后的 URL
    • $route.matched 数组,包含当前路由的配置参数对象
    • $route.name 当前路由的名字
    • $route.meta 路由原信息
  • $router是全局路由的实例,是router构造方法的实例
    • psuh 向history栈添加一个记录
    • go 页面路由前进或后退
    • replace 替换当前的路由,不会在history里面添加记录

VueRouter的导航守卫

  • 全局守卫
    • router.beforeEach
    • router.beforeResolve
    • router.afterEach
  • 路由独享守卫
    • beforeEnter
  • 组件内守卫
    • beforeRouteEnter 进入路由前,组件还未被实例化,无法获取this
    • beforeRouteUpdate 这个阶段可以获取this,在路由复用同一个组件时触发
    • beforeRouteLeave 可以获取this,当离开组件对应的路由时

VueRouter完整的导航解析

  1. 导航被触发
  2. 失活组件调用beforeRouteLeave
  3. 调用全局守卫beforeEach
  4. 在重用组件里调用beforeRouteUpdate
  5. 在路由配置里面调用beforeEnter
  6. 解析异步路由组件
  7. 被激活的组件里调用beforeRouteEnter
  8. 调用全局守卫 beforeResolve
  9. 导航被确认
  10. 调用全局守卫afterEach
  11. 触发DOM更新
  12. 用创建好的组件调用beforeRouteEnter传给next的回调函数

详细参考:从使用到自己实现简单Vue Router看这个就行了 - 掘金 (juejin.cn)

12. Pina 和 Vuex

Vuex: StateGettesMutations(同步)、Actions(异步)

Pinia: StateGettesActions(同步异步都支持)

Pina特性

  • 没有Mutation
  • Action 支持同步和异步
  • 没有模块的嵌套结构
  • 更好的TypeScript支持
  • vue2 和 vue3都支持
  • 支持 vue DevTools

13. v-if 和 v-show

  • 都是动态的显示DOM元素
  • v-if是动态的向DOM树里面添加或删除DOM元素,v-show是通过display属性控制DOM元素的显隐
  • v-if有一个局部编译/卸载的过程,切换过程会销毁和重建内部的事件监听和子组件,v-show是简单的css切换
  • v-if是惰性的,如果一开始条件是false,则什么都不做,直到第一次条件变为true,才会编译。v-show在任何条件下都会被编译,然后被缓存,DOM保留
  • v-if有较高的切换消耗,v-show有较高初始渲染消耗
  • v-if适合运营条件不太可能切换
  • v-show适合频繁切换

14. Scpoed实现原理

Vue中的scoped属性主要是通过postcss转义实现的。PostCss给组件内的所有DOM都添加了一个动态属性。然后给CSS添加一个对应的css属性选择器,使得样式只作用域含有该属性的DOM,也就是组件内的DOM

15. keep alive

keep-alive是什么

keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件,会缓存不活动的组件实例,能够保存组件的状态,避免组件被反复创建和销毁,提升系统性能。总的来说,keep-alive保存的是组件的渲染状态。

keep-alive使用场景

用户在列表页面筛选出一个数据列表,由列表页面进入详情页面,然后从详情页面返回列表页面后,我们希望:列表页可以保留用户的筛选状态。

keep-alive用法

  • 在动态组件中的用法
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
    <component :is="currentComponent"></component> 
</keep-alive>
  • 在vue-router中的用法
<keep-alive :include="whiteList" :exclude="blackList" :max="amount"> 
    <router-view></router-view> 
</keep-alive>

keep-alive具有inclued和exclude属性,include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。

keep-alive嵌套的组件会有两个生命周期钩子,activated 和 deactivated,他们分别在组件激活和失活时触发,第一次activated触发是在mounted之后

keep-alive实现

created () { 
    this.cache = Object.create(null) // 缓存虚拟dom 
    this.keys = [] // 缓存的虚拟dom的健集合 
}

在具体的实现上keep-alive在内部维护一个key数组和缓存对象。key数组保存的是当前组件的key值,cache对象以key为键,vnode为值,用于缓存组件对应的虚拟DOM。在keep-alive的渲染函数中,判断当前渲染的vnode是否有对应的缓存,如果有,从缓存中读取对应的组件实例,没有就将其缓存。