Vue 理解之白话 getter/setter详解


Posted in Javascript onApril 16, 2019

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器

 以上摘自 深入响应式原理

那么,把这些属性全部转为 getter/setter 具体是怎样一个过程呢?本文不深入具体,简单大致了解其过程,旨在整体把握,理解其主要思路

假设代码如下:

const vm = new Vue({
 el: '#app',
 data: {
 msg: 'hello world'
 }
})

data 选项可以接收一个对象或者方法,这里以对象为例(其实最后都会转为对象)

首先,这个对象的所有键值对都会被挂载在 vm._data 上(此外 vm._data 对象上还有个 __ob__ key,暂时可以忽视),这样我们便能用 vm._data.msg 访问到数据

但是通常我们是用 vm.msg 这样访问数据,如何做到的呢?其实就是做了个代理,将 data 键值对中的 vm[key] 的访问都代理到 vm._data[key] 上

proxy(vm, `_data`, key)

export function proxy (target: Object, sourceKey: string, key: string) {
 sharedPropertyDefinition.get = function proxyGetter () {
 return this[sourceKey][key]
 }
 sharedPropertyDefinition.set = function proxySetter (val) {
 this[sourceKey][key] = val
 }
 Object.defineProperty(target, key, sharedPropertyDefinition)
}

通常 vm._data (下划线变量)用作内部程序,对外暴露的 API 是 vm.$data,其实这两者也是一个东西,也是做了个代理,代码大概这样:

const dataDef = {}
dataDef.get = function () { return this._data }

Object.defineProperty(Vue.prototype, '$data', dataDef)

if (process.env.NODE_ENV !== 'production') {
 dataDef.set = function () {
 warn(
  'Avoid replacing instance root $data. ' +
  'Use nested data properties instead.',
  this
 )
 }
}

简单理解就是访问 vm.data.msg 其实就是访问 vm._data.msg。如果直接在开发环境对 vm.data = 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 稍微添点东西:

const vm = new Vue({
 el: '#app',
 data: {
 msg: 'hello world'
 },
 computed: {
 msg2() {
  return this.msg + '123'
 }
 }
})

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,大概代码如下:

// obj 即为 vm._data
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
 defineReactive(obj, keys[i])
}

Object.defineProperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function reactiveGetter () {
 const value = getter ? getter.call(obj) : val
 if (Dep.target) {
  dep.depend()
  if (childOb) {
  childOb.dep.depend()
  if (Array.isArray(value)) {
   dependArray(value)
  }
  }
 }
 return value
 },
 set: function reactiveSetter (newVal) {
 const value = getter ? getter.call(obj) : val
 /* eslint-disable no-self-compare */
 if (newVal === value || (newVal !== newVal && value !== value)) {
  return
 }
 /* eslint-enable no-self-compare */
 if (process.env.NODE_ENV !== 'production' && customSetter) {
  customSetter()
 }
 // #7981: for accessor properties without setter
 if (getter && !setter) return
 if (setter) {
  setter.call(obj, newVal)
 } else {
  val = newVal
 }
 childOb = !shallow && observe(newVal)
 dep.notify()
 }
})

Vue 响应式核心就是,setter 的时候会收集依赖,getter 的时候会触发依赖更新

我们还是以上面的 computed msg2 为例,当我们第一次去取值 msg2 时(注意,必须是取值行为,可以是在 template,也可以是程序中),势必需要去取值 this.msg,这就会触发 msg 的 getter,此时我们就可以确定 msg2 依赖于 msg

msg 可以被哪些东西依赖呢?目前看来有三

  1. template 模版中
  2. computed
  3. watch

我们可以打印 vm._watchers 查看,是一个 Watcher 实例数组,直接看实例的 expression 值,其实就是触发这个表达式的时候,会触发 msg 的 getter

而这个表达式就对应上述的三种情况,因为 msg 改变的时候,这些表达式需要重新求值,所以这些依赖项都要保存起来,所以源码中定于了这个 Watcher 类

A watcher parses an expression, collects dependencies, and fires callback when the expression value changes. This is used for both the $watch() api and directives.

 watcher.deps 数组表示该 watcher 的依赖项,值为 Dep 实例,可以理解成和 Watcher 实例的表达式有关的 data 数据。注意,deps 数组可能是空,对于 template 而言,可以是 template 中不依赖于 data,对于 computed 而言,可以是这个 computed 数据还没被获取(比如我定义了 msg2,但是程序中没有用,这时 deps 为空,这表明我如果改变了 msg,但是不需要通知到 msg2,因为 msg2 根本没用到嘛,但是我在控制台输入 vm.msg2,从而触发了 msg 的 getter,继而进行了依赖收集,这时 deps 就不为空了,这表明我已经使用了 msg2,下次 msg 更新时需要通知到 msg2 进行改变)

而对于 watch 而言,我试了下任何情况下 deps 都不为空,这需要进一步查看源码确认

deps 数组元素是 Dep 实例,该实例有个 subs 属性,是 Watcher 实例数组,表示依赖于这个 Dep 的项目

Watcher 和 Dep 比较难理解,可以暂时这样理解,Dep 和 data 挂钩,每一个 Dep 实例就对应 data 的一个键值对,Watcher 实例则依赖于 Dep,那么有三个情况会依赖,也就是以上三种(想想是不是这样,当数据更新的时候,是不是只有这三处需要同时更新,或者同时响应)

总结下:我们会对 data 中所有键值对设置 getter/setter,getter 的时候我们会收集依赖(依赖项为上面三项,并不是任何情况下都会收集依赖,比如在钩子中打印 msg,这时候就没依赖,所以源码中这里还有复杂判断),setter 的时候我们会将收集的依赖项触发,从而更新数据,理解了这些,就能初步理解 Vue 的响应式原理

以上所述是小编给大家介绍的Vue getter setter详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript之appendChild、insertBefore和insertAfter使用说明
Dec 30 Javascript
javascript中解析四则运算表达式的算法和示例
Aug 11 Javascript
js监听鼠标事件控制textarea输入字符串的个数
Sep 29 Javascript
BootStrap 附加导航组件
Jul 22 Javascript
vue.js 表格分页ajax 异步加载数据
Oct 18 Javascript
微信小程序 网络API 上传、下载详解
Nov 09 Javascript
vue2.0开发实践总结之疑难篇
Dec 07 Javascript
jquery实现刷新随机变化样式特效(tag标签样式)
Feb 03 Javascript
使用canvas进行图像编辑的实例
Aug 29 Javascript
vue 下列表侧滑操作实例代码详解
Jul 24 Javascript
javascript匿名函数中的'return function()'作用
Oct 15 Javascript
vue-cli 目录结构详细讲解总结
Jan 15 Javascript
在vue中使用setInterval的方法示例
Apr 16 #Javascript
vue实现局部刷新的实现示例
Apr 16 #Javascript
如何自动化部署项目?折腾服务器之旅~
Apr 16 #Javascript
每周一练 之 数据结构与算法(Stack)
Apr 16 #Javascript
在vue中获取微信支付code及code被占用问题的解决方法
Apr 16 #Javascript
JavaScript实现选项卡效果的分析及步骤
Apr 16 #Javascript
Vuex持久化插件(vuex-persistedstate)解决刷新数据消失的问题
Apr 16 #Javascript
You might like
PHP file_get_contents 函数超时的几种解决方法
2009/07/30 PHP
Blitz templates 最快的PHP模板引擎
2010/04/06 PHP
深入解析PHP 5.3.x 的strtotime() 时区设定 警告信息修复
2013/08/05 PHP
优化WordPress的Google字体以加速国内服务器上的运行
2015/11/24 PHP
php实现mysql连接池效果实现代码
2018/01/25 PHP
用js实现上传图片前的预览(TX的面试题)
2007/08/14 Javascript
js去除输入框中所有的空格和禁止输入空格的方法
2014/06/09 Javascript
Jquery网页内滑动缓冲导航的实现代码
2015/04/05 Javascript
js实现简单的可切换选项卡效果
2015/04/10 Javascript
基于JavaScript实现手机短信按钮倒计时(超简单)
2015/12/30 Javascript
vue使用axios跨域请求数据问题详解
2017/10/18 Javascript
AngularJs用户登录问题处理(交互及验证、阻止FQ处理)
2017/10/26 Javascript
vue中element组件样式修改无效的解决方法
2018/02/03 Javascript
jQuery实现的鼠标拖动浮层功能示例【拖动div等任何标签】
2018/12/29 jQuery
NestJs 静态目录配置详解
2019/03/12 Javascript
6种JavaScript继承方式及优缺点(小结)
2020/02/06 Javascript
JS错误处理与调试操作实例分析
2020/04/13 Javascript
javascript设计模式 ? 外观模式原理与用法实例分析
2020/04/15 Javascript
JavaScript实现拖拽和缩放效果
2020/08/24 Javascript
Python的词法分析与语法分析
2013/05/18 Python
用Python实现数据的透视表的方法
2018/11/16 Python
详解python配置虚拟环境
2019/04/08 Python
Python @property使用方法解析
2019/09/17 Python
python将三维数组展开成二维数组的实现
2019/11/30 Python
使用Pytorch搭建模型的步骤
2020/11/16 Python
python实现马丁策略回测3000只股票的实例代码
2021/01/22 Python
深入CSS3 动画效果的总结详解
2013/05/09 HTML / CSS
实习自我鉴定范文
2013/10/30 职场文书
教师自我反思材料
2014/02/14 职场文书
《生命的药方》教学反思
2014/04/08 职场文书
室内设计专业毕业生求职信
2014/05/02 职场文书
校园新闻广播稿5篇
2014/10/10 职场文书
居安思危观后感
2015/06/11 职场文书
售房协议书范本
2015/08/11 职场文书
2016新党章学习心得体会
2016/01/15 职场文书
SONY AN-LP1 短波有源天线放大器图
2022/04/05 无线电