Vue 理解之白话 getter/setter详解

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项 , Vue 将遍历此对象所有的属性 , 并使用 Object.defineProperty 把这些属性全部转为 getter/setter 。Object.defineProperty 是 ES5 中一个无法 shim 的特性 , 这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器
以上摘自 深入响应式原理
那么 , 把这些属性全部转为 getter/setter 具体是怎样一个过程呢?本文不深入具体 , 简单大致了解其过程 , 旨在整体把握 , 理解其主要思路
假设代码如下:

Vue 理解之白话 getter/setter详解

文章插图
data 选项可以接收一个对象或者方法 , 这里以对象为例(其实最后都会转为对象)
首先 , 这个对象的所有键值对都会被挂载在 vm._data 上(此外 vm._data 对象上还有个 __ob__ key , 暂时可以忽视) , 这样我们便能用 vm._data.msg 访问到数据
但是通常我们是用 vm.msg 这样访问数据 , 如何做到的呢?其实就是做了个代理 , 将 data 键值对中的 vm[key] 的访问都代理到 vm._data[key] 上
Vue 理解之白话 getter/setter详解

文章插图
通常 vm._data (下划线变量)用作内部程序 , 对外暴露的 API 是 vm.$data , 其实这两者也是一个东西 , 也是做了个代理 , 代码大概这样:
Vue 理解之白话 getter/setter详解

文章插图
简单理解就是访问 vm.data.msg 其实就是访问 vm._data.msg 。如果直接在开发环境对 vm.data = https://www.rkxy.com.cn/dnjc/xxx这样的赋值 , 而不是vm.$data.msg = xxx` 这样的赋值 , 后者是没问题的)
至此 , 我们理解了为什么能用 vm.msg、vm._data.msg 以及 vm.$data.msg 三种方式获取/改变数据 , 最原始的数据是 vm._data.msg , 而另外两者即代理了 _data 的数据 , vm.$data.msg 即为 Vue 向外提供的 API , 一般情况下开发我们直接用 vm.msg 这样比较多 , 也方便 , 如果要获取整个 data , 程序中需要用 this.$data , 而不是 this.data
接下来说 getter/setter
将 demo 稍微添点东西:
Vue 理解之白话 getter/setter详解

文章插图
msg2 是依赖于 msg 的 , 当 msg 改变的时候 , msg2 的值需要自动更新 , msg 的改变可以在 vm._data.msg 的 setter 中监听到 , 但是怎么知道 msg2 是依赖于 msg 的呢?
直观地我们可以想到 , 遍历所有 computed 对象的键值对 , 然后进行分析 , 理论上似乎可行 , 但是我寻思着这可能需要解析 AST 啊 , 或者正则去匹配 , 看看是否用到了 this.msg , 也可能是 this.$data.msg 啊 , 还可能是 this._data.msg , 而且还要遍历 data 中的所有 key , 这看起来也太麻烦了吧 , 而且 , 如果程序中没有用到 msg2 , 那不是多此一举了?
事实上 , Vue 初始化的时候会对 vm._data 的每个键值对设置 getter/setter , 大概代码如下:
Vue 理解之白话 getter/setter详解

文章插图
Vue 响应式核心就是 , setter 的时候会收集依赖 , getter 的时候会触发依赖更新
我们还是以上面的 computed msg2 为例 , 当我们第一次去取值 msg2 时(注意 , 必须是取值行为 , 可以是在 template , 也可以是程序中) , 势必需要去取值 this.msg , 这就会触发 msg 的 getter , 此时我们就可以确定 msg2 依赖于 msg
msg 可以被哪些东西依赖呢?目前看来有三
template 模版中
computed
watch
我们可以打印 vm._watchers 查看 , 是一个 Watcher 实例数组 , 直接看实例的 expression 值 , 其实就是触发这个表达式的时候 , 会触发 msg 的 getter
而这个表达式就对应上述的三种情况 , 因为 msg 改变的时候 , 这些表达式需要重新求值 , 所以这些依赖项都要保存起来 , 所以源码中定于了这个 Watcher 类

推荐阅读