Ydl's Blog

vue

2023-03-21

vue双向绑定

vue的双向绑定是指数据和视图的修改会影响另外一方。

修改视图更新数据是通过addEventLisenter增加监听事件来实现的

修改数据更新视图是通过数据劫持和发布-订阅者模式实现的,vue2中通过Object.defineProperty方法来劫持各个属性,当触发对象属性的setter时,会通知订阅者触发相应的监听回调。主要流程如下

  • new Vue实例的时候会调用Observer和Compile,Observer来完成数据劫持(value丢失问题),Compile负责解析原先的dom,通过createDocumentFragment将要处理的dom放到临时内存里,统一处理后再append到vue挂载的dom上初始化视图
  • 发布订阅者模式的实现和Dependency、Watcher两个类有关;Dependency维护了一个订阅者数组,以及两个方法:添加订阅者和通知数组内所有订阅者调用监听回调的方法。Watcher的构造函数则包含了监听的对象、监听的属性和属性发生修改的回调函数,对外暴露一个update方法给Dependency调用,内部再调用监听的回调函数
  • 在Compile里会new Watcher ,不同的nodeType会有不同监听回调的Watcher,在Compile里告诉Watcher具体要的回调要怎么处理;new Watcher的时候会去访问对应的属性,为的是触发这个key的getter,在这个getter里将watcher加入到dependency的订阅数组里(为什么Dependency是在Observer里创建:确保当给key赋值一个对象类型的值时,这个值也有对应的Dependency);setter则是负责调用dependency的notify

computed和watch的区别

computed是计算属性,他会根据其他属性的值计算结果,并且默认走缓存,只有当依赖的数据发生变化时才会重新计算。watch是监听器,负责监听数据,当监听的数据发生变化时就会触发相应的操作。

缓存方面,computed支持缓存,而watch不支持缓存

异步方面,computed中有异步操作时无法监听数据变化,而watch支持异步监听

v-if和v-show的区别

实现上:v-if是动态的添加或者删除DOM元素,v-show则是通过设置元素的display样式属性控制显示隐藏

编译上:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show是在任何条件下,都会被编译生成DOM元素

性能上:v-if有更高的切换消耗,v-show有更高的初始渲染消耗

keep-alive的理解,如何实现,具体缓存的内容

当一个组件在切换时需要保存状态防止多次渲染时,就可以使用keep-alive组件包裹

虚拟DOM

Virtual Dom是一个JavaScript对象,包括tag、props、children属性,通过对象的方式来表示DOM结构,配合不同的渲染工具,使跨平台渲染成为可能,比如Node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟DOM

通过将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。当数据变化时,将现在的虚拟DOM会与缓存的虚拟DOM进行比较,vue内部封装了diff算法,通过这个算法来进行比较,渲染修改改变的变化,原先没有发生改变的则使用缓存。

无需手动操作DOM

首次渲染大量DOM时,由于多了一层虚拟DOM的计算,性能会稍差一些

虚拟DOM的解析过程

首先对DOM树结构进行解析,使用js来模拟这个对象并保存下来,然后再把这个DOM插入到文档中

当页面发生变更,重新构建对象树,将新旧对象树进行diff比较,记录下差异patch对象,最后将ptach对象解析到真正的DOM树

具体来说,就是当数据发生变化时,通过劫持setter,让数据的Dependency去通知监听的Watcher,Watcher获取新的虚拟DOM执行patch,对比缓存的旧的虚拟DOM,如果sameVnode返回true,那么patchVnode进行后续对比子节点变化,如果vnode和oldVnode都有子节点并且子节点不一样,那么调用updateChildren更新子节点

diff算法原理

diff中只对同层的子节点进行比较,放弃跨级的节点比较

首先,我们拿到新旧节点的数组,然后初始化四个指针,分别指向新旧节点的开始位置和结束位置,进行vNode的key的两两对比,相同则patchNode对比更新DOM

  • 如果新的开始节点和旧开始节点相同,则都向后面移动,如果结尾节点相匹配,则都前移指针

  • 如果新开始节点和旧结尾节点匹配上了,则会将旧的结束节点移动到旧的开始节点前

  • 如果旧开始节点和新的结束节点相匹配,则会将旧开始节点移动到旧结束节点的后面

  • 如果上述节点都没匹配上,则会进行一个兜底逻辑的判断,判断开始节点是否在旧节点中,若是存在则复用,若是不存在则在旧节点数组中创建

如果旧vnode先遍历完,就添加新vnode没有遍历的节点;如果新vnode先遍历完,就删除旧vnode没有遍历的节点

头对尾,尾对头的操作是因为可以通过reverse操作快速检测

vue3和vue2中diff的区别

vue2是全量diff,vue3是静态标记+非全量diff

vue3使用最长递增子序列优化了对比流程

key的作用

在diff算法中更精确的找到相同节点,因此patch过程会非常高效

如果不使用key或者index作为key时,假设往列表中插入了一个新的元素,那么插入元素后面的元素的位置会发生变化,相比原来的虚拟DOM不再相同,所以全都会执行更新操作

vue-router如何实现懒加载

箭头函数+import

const router = new VueRouter({
routes: [
{
path: '/list',
component: () => import('@/components/list.vue')
}
]
})

箭头函数+require

component: resolve => require(['@/components/list'], resolve)

webpack的require.ensure

component: r => require.ensure([], () => r(require('@/components/list')), 'list')

vue2和vue3的区别

双向绑定使用的API不同;前者是Object.defineProperty,后者是Proxy

object.defineProperty无法监听添加或删除对象的属性;本身也无法监听数组的push等方法,vue底层额外进行了处理,但是仍然无法监听到数组下标和长度的变化

Proxy直接代理整个对象而非对象的属性;可以监听数组的变化

vue2使用的是option API,vue3使用的是composition API

diff算法不同;前者使用的是头尾节点对比,后者是最长递增子序列

生命周期钩子函数不同;前者是beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed;后者是setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onRenderTracked、onRenderTriggered

Tags: 面试