vue响应式原理
vue响应式也叫作数据双向绑定,大致原理阐述:

​ 首先我们需要通过Object.defineProperty()方法把数据(data)设置为getter和setter的访问形式,这样我们就可以在数据被修改时在setter方法设置监视修改页面信息,也就是说每当数据被修改,就会触发对应的set方法,然后我们可以在set方法中去调用操作dom的方法。

​ 此外,如果页面有input用v-model绑定数据,我们需要在这种绑定了data的input元素上添加监听,添加input事件监听,每当input事件被触发时,就修改对应的data。

vue实现数据响应式,是通过数据劫持侦测数据变化,发布订阅模式进行依赖收集与视图更新,换句话说是Observe,Watcher以及Compile三者相互配合,

  • Observe实现数据劫持,递归给对象属性,绑定setter和getter函数,属性改变时,通知订阅者
  • Compile解析模板,把模板中变量换成数据,绑定更新函数,添加订阅者,收到通知就执行更新函数
  • Watcher作为Observe和Compile中间的桥梁,订阅Observe属性变化的消息,触发Compile更新函数

 1.什么是响应式

当price 发生变化的时候,Vue就知道自己需要做三件事情:

  • 更新页面上price的值

  • 计算表达式 price*quantity 的值,更新页面

  • 调用totalPriceWithTax 函数,更新页面

数据发生变化后,会重新对页面渲染,这就是Vue响应式,那么这一切是怎么做到的呢?

想完成这个过程,我们需要:

  • 侦测数据的变化

  • 收集视图依赖了哪些数据

  • 数据变化时,自动“通知”需要更新的视图部分,并进行更新

对应专业俗语分别是:

  • 数据劫持 / 数据代理

  • 依赖收集

  • 发布订阅模式

如何侦测数据的变化 

​​​​​​​首先有个问题,在Javascript中,如何侦测一个对象的变化?其实有两种办法可以侦测到变化:使用 Object.defineProperty和ES6的 Proxy,这就是进行数据劫持或数据代理。

方法1.Object.defineProperty

Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

方法2.Proxy

Proxy 是 JavaScript 2015 的一个新特性。 Proxy 的代理是针对整个对象的,而不是对象的某个属性,因此不同于 Object.defineProperty 的必须遍历对象每个属性, Proxy 只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。此外 Proxy支持代理数组的变化。

为什么要收集依赖

我们之所以要观察数据,其目的在于当数据的属性发生变化时,可以通知那些曾经使用了该数据的地方。比如第一例子中,模板中使用了price 数据,当它发生变化时,要向使用了它的地方发送通知。 

订阅者 Dep

1.为什么引入 Dep

收集依赖需要为依赖找一个存储依赖的地方,为此我们创建了Dep,它用来收集依赖、删除依赖和向依赖发送消息等。

于是我们先来实现一个订阅者 Dep 类,用于解耦属性的依赖收集和派发更新操作,说得具体点,它的主要作用是用来存放 Watcher 观察者对象。我们可以把Watcher理解成一个中介的角色,数据发生变化时通知它,然后它再通知其他地方。

观察者 Watcher

1.为什么引入Watcher

Vue 中定义一个 Watcher 类来表示观察订阅依赖。至于为啥引入Watcher,《深入浅出vue.js》给出了很好的解释:

当属性发生变化后,我们要通知用到数据的地方,而使用这个数据的地方有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个watch,这时需要抽象出一个能集中处理这些情况的类。然后,我们在依赖收集阶段只收集这个封装好的类的实例进来,通知也只通知它一个,再由它负责通知其他地方。

依赖收集的目的是将观察者 Watcher 对象存放到当前闭包中的订阅者 Dep 的 subs 中,形成如下所示的这样一个关系(图参考《剖析 Vue.js 内部运行机制》)。

640?wx_fmt=other

 收集依赖

所谓的依赖,其实就是Watcher。至于如何收集依赖,总结起来就一句话,在getter中收集依赖,在setter中触发依赖。先收集依赖,即把用到该数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍就行了。

具体来说,当外界通过Watcher读取数据时,便会触发getter从而将Watcher添加到依赖中,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中。当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。

最后我们对 defineReactive 函数进行改造,在自定义函数中添加依赖收集和派发更新相关的代码,实现了一个简易的数据响应式。


  • 总结: 

     

    640?wx_fmt=other

  • 在 newVue() 后, Vue 会调用 _init 函数进行初始化,也就是init 过程,在 这个过程Data通过Observer转换成了getter/setter的形式,来对数据追踪变化,当被设置的对象被读取的时候会执行 getter 函数,而在当被赋值的时候会执行 setter函数。

  • 当render function 执行的时候,因为会读取所需对象的值,所以会触发getter函数从而将Watcher添加到依赖中进行依赖收集。

  • 在修改对象的值的时候,会触发对应的 setter, setter通知之前依赖收集得到的 Dep 中的每一个 Watcher,告诉它们自己的值改变了,需要重新渲染视图。这时候这些 Watcher就会开始调用 update 来更新视图。

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐