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:发生实例销毁之后,组件被拆解,数据绑定被清除,监听被移除,子实例也被销毁
父子组件生命周期的调用顺序
- 加载渲染过程:父beforeCreated -> 父created -> 父beforeMount -> 子beforeCreated -> 子created -> 子beforeMount -> 子mounted -> 父mounted
- 子组件更新过程:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
- 父组件更新过程:父beforeUpdate -> 父updated
- 销毁过程:父beforeDestory -> 子beforeDestory -> 子destoryed -> 父destoryed
4. Vue双向数据绑定实现的原理
Vue主要通过数据劫持和发布-订阅模式,利用Object.defineProperty来劫持各个数据的getter和setter,当数据改变时发送消息给订阅者,触发相应的回调。主要分为以下几个步骤:
- 需要observer对数据进行递归遍历,给每个属性都添加getter和setter属性,每个属性都会存在一个Dep(属性订阅器),当给数据赋值时就会触发setter,就能监听到数据变化。
- compile解析模板指令,将模板中的变量替换成数据,并给每个指令对应的节点都绑定更新函数,添加数据的订阅者。
- Wathcer订阅者,是observer和compile之间沟通的桥梁,在自身实例化时往属性订阅器(dep)里面添加自己。待属性变动
dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,改变页面。 - Dep属性订阅器,当某个属性值发生变化时,Dep会收到observer的通知,随后调用
notify()函数,通知该属性的所有订阅者。
代码实现请参考:手写一个简易vue响应式带你了解响应式原理 - 掘金 (juejin.cn)
5. Vue2.x中如何检测数组的变化
vue2.x中对数组方法进行了重写,Vue将data里面数组的原型链进行重写,指向自己定义的数组原型方法。当数组调用API时,可以通知依赖更新。如果数组中包含引用类型,会对引用再次递归遍历进行监控
有两种情况无法检测数组的变化:
- 当利用数组的索引项赋予新值,vm.arr[index] = newVal。可以通过vm.$set(arr,index,newVal)
- 修改数组的长度,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
pushState和replaceState,可以改变URL,但是不会发送请求。这样就可以监听url变化来实现更新页面的内容 - 两者的区别:
- URL的展示:hash里面有存在
#号 - 刷新页面时,hash可以正常加载到hash对应的页面,history如果没有处理的话,会出现404。因为浏览器向这个地址发出请求时,文件资源不存在。所以需要后端将所有的请求都拦截到index.html上
- hash可以支持低版本的浏览器和IE
- URL的展示:hash里面有存在
$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完整的导航解析
- 导航被触发
- 失活组件调用beforeRouteLeave
- 调用全局守卫beforeEach
- 在重用组件里调用beforeRouteUpdate
- 在路由配置里面调用beforeEnter
- 解析异步路由组件
- 被激活的组件里调用beforeRouteEnter
- 调用全局守卫 beforeResolve
- 导航被确认
- 调用全局守卫afterEach
- 触发DOM更新
- 用创建好的组件调用beforeRouteEnter传给next的回调函数
详细参考:从使用到自己实现简单Vue Router看这个就行了 - 掘金 (juejin.cn)
12. Pina 和 Vuex
Vuex: State、Gettes、Mutations(同步)、Actions(异步)
Pinia: State、Gettes、Actions(同步异步都支持)
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是否有对应的缓存,如果有,从缓存中读取对应的组件实例,没有就将其缓存。