在学完Vue.js框架,完成了一个SPA项目后,一直想抽时间找本讲解Vue.js内部实现原理的书来看看,经过多方打听之后,我最后选择了《深入浅出Vue.js》这本书。然而惭愧的是,这本书已经买了将近一个多月了吧,自己中间却因为一些杂七杂八的事情,一直没有静下心来好好看书。今天,终于翻开了这本书,阅读了前三章的内容,目前感觉这本书写得还不错。因为经常看到关于Vue.js中双向数据绑定的问题,自己也在网上浏览过一些博客,然而基本上都是千篇一律而又不知所云,很多都是直接上来就是代码,说实话刚开始很多名词我都不知道是啥,比如什么依赖收集、发布订阅什么的。现在看了这本书上讲的,感觉讲得挺清楚的,我终于知道之前那些博客里都在说的依赖是个啥了。所以,决定将我现在看了的关于变化侦测部分的内容整理记录如下:
一、Object的变化侦测
说实话,在看这本书之前,我哪里知道原来Vue.js中的变化侦测还要分Object和Array啊。如果有人问我:“你知道Vue中的双向数据绑定如何实现的吗?”那我大概也只能答一个:“嗯,在Vue中使用指令v-model可以实现数据的双向绑定,其内部是通过Object.defineProperty属性来重新设置getter和setter函数来对数据变化进行侦测,从而达到数据双向绑定的。”是的,我最多就只能凑出这么一句话了,还不准确。而现在才发现,原来不同数据类型的变化侦测还不一样啊。好了,不废话了,开始进入正题。
“变化侦测就是侦测数据的变化。当数据发生变化时,要能侦测到并发出通知。Object可以通过Object.defineProperty将属性转换成getter/setter的形式来追踪变化。读取数据时会触发getter,修改数据时会触发setter。我们需要在getter中收集有哪些依赖使用了数据。当setter被触发时,去通知getter中收集的依赖数据发生了变化。收集依赖需要为依赖找一个存储依赖的地方,为此我们创建了Dep,它用来收集依赖、删除依赖和向依赖发送消息等。所谓的依赖,其实就是Watcher。只有Watcher触发的getter才会收集依赖,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中。当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。
整个过程就是Data通过Observer转换成了getter/setter的形式来追踪变化。当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。当数据发生了变化时,会触发setter,从而向Dep中的依赖发送通知。Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。”
二、Array的变化侦测
从书中所讲来看,Array的变化侦测比Object要麻烦一点,它是通过创建拦截器去覆盖数组原型的方式来追踪变化。为了不污染全局Array.prototype,我们在Observer中只针对那些需要侦测变化的数组使用_proto_来覆盖原型方法。Array收集依赖的方式和Object一样,都是在getter中收集。但是由于使用依赖的位置不同,数组要在拦截器中向依赖发消息,所以不能像Object那样保存在defineReactive中,而是把依赖保存在了Observer实例上。在Observer中,我们对每个侦测了变化的数据都标上印记_ob_,并把this保存在_ob_上。一方面是为了标记数据是否被侦测了变化,另一方面可以很方便地通过数据取到_ob_,从而拿到Observer实例上保存的依赖。当拦截到数组发生变化时,向依赖发送通知。
除了侦测数组自身的变化外,数组中元素发生的变化也要侦测。调用observerArray方法将数组中的每一个元素都转换成响应式的并侦测变化。当用户使用push等方法向数组中新增数据时,新增的数据也要进行变化侦测。我们使用当前操作数组的方法来判断,从参数中将新增数据提取出来,然后使用observerArray对新增数据进行变化侦测。
对于数组类型的数据,一些语法无法追踪到变化,只能拦截原型上的方法,而无法拦截数组特有的语法,例如使用length清空数组的操作就无法拦截。
说明:以上内容大多摘自《深入浅出Vue.js》,此文仅为自己的学习笔记