平平无奇古哥哥

V1

2023/05/23阅读:22主题:山吹

对线面试官:Vue响应式原理

面试官:

讲一下什么是响应式,Vue是怎么实现响应式的?

小明:

所谓响应式,指数据变更 -> 视图自动更新。

Vue2是通过Object.defineProperty进行数据侦听,并且在数据getter时做依赖收集,在setter时通知页面更新视图来实现响应式的。


面试官:

你刚刚提到依赖收集,那你讲讲什么是依赖收集,为什么要做依赖收集?

小明:

收集依赖是为了知道哪些视图用到了data里的状态。

举个例子,在模板里我们使用了两个状态name和sex:

但在data里面,我们声明了3个状态,name、sex和age:

当我们通过this.age = xxx来改变age时,页面其实并不需要更新。

可是Vue是怎么知道哪个状态变了需要更新,哪个变了不需要更新呢,这就需要一种机制来保存哪些状态被视图引用了,而这个机制,就叫依赖收集。

(注:其实引用状态的并不只有视图,watch和computed里面也引用了data里的属性,也会做依赖收集,但此处为了便于理解,我们统称为视图。)


面试官:

那你知道依赖收集的实现原理吗?

小明:

依赖收集的实现主要分两步,依靠Dep和Watch两个类来实现:

  • 首先是当Vue实例化时,会遍历数据对象中的每个属性,并为每个属性创建一个依赖收集器(Dep)对象,用于存储依赖于该属性的Watcher对象。
  • 然后当组件渲染时,会解析模板变量,触发属性的getter,同时会创建Watcher对象,用来将组件实例、属性和callback方法关联起来,并且存到依赖收集器对象里。

这样当状态变更时,Vue就知道是哪个组件需要更新了,只需调用与该组件对应的callback方法去完成更新即可。


面试官:

嗯,不错不错(假装听懂),那你知道什么是细粒度更新吗,为什么Vue要采取中等粒度的更新方式?

小明:

我们刚刚讲到,当状态变更时,Vue会通知到需要更新的组件,然后会在组件内部做虚拟DOM的diff,通过diff算法来实现组件级别的更新。简单来说就是,Vue的更新是组件级别的。

之所以说Vue2的这种组件级别的更新机制是中等粒度的,其实是相对于Vue1和React来说的:

  • 在Vue1中并没有虚拟DOM,Vue1的更新机制完全是依靠响应式来实现的,状态变更时直接通知到需要变更的DOM,属于DOM级别的更新。但这样做的缺陷是要创建的Watcher对象太多,在大型项目中会产生性能问题。
  • React也使用了虚拟DOM,但React的更新是应用级别的。React每次更新,是将一整颗新的虚拟DOM树和旧的进行比对,这样更新的性能会相对较差,有很多不必要的更新。

Vue2采取组件级别的更新,相当于是取了个折中的方案,这也是一种平衡的设计理念。


面试官:

那你知道Vue2的响应式有哪些缺陷吗?

小明:

由于Object.defineProperty这个API的限制,导致Vue2的响应式存在两大缺陷:

  1. 无法监听到对象新增属性或者删除属性的变更。
  2. 无法直接对数组进行监听,Vue2是通过重写了数组的push、pop、shift、unshift、sort、reverse方法来实现对数组的响应式的,我们通过这些方法来改变数组是可以被Vue监听到的。但是对于直接更改数组长度、向指定位置添加元素以及修改指定位置元素,是无法被监听到的。

对于这些问题,在开发中可以使用$set这个API去解决。举个例子:

另外,在Vue3中使用了Proxy去实现响应式,这两类问题也就不存在了。


面试官:

嗯嗯,你小子讲得还可以。

下周来参加二面吧,下次我们来聊聊Vue3的响应式实现,好好准备下,能不能过就看下次了。

小明:

好的,收到!

未完待续。。

分类:

前端

标签:

前端

作者介绍

平平无奇古哥哥
V1