参考: Vue数据响应式原理(写得非常好)
1、响应式原理(以下讲解大部分都是从这里copy过来的)
源码解读笔记代码
vue采用数据劫持结合发布者-订阅者模式的方式实现数据的响应式,使用Object.defineProperty()把data下面的数据设置为set/get方式,数据get的时候收集订阅者,set的时候通知发布更新
原理解释主要涉及5个概念,如下图(图片也是参考这里:Vue数据响应式原理)
图中红色的箭头表示的是收集依赖时获取数据的流程。Watcher会收集依赖的时候(这个时机可能是实例创建时,解析模板、初始化watch、初始化computed,也可能是数据改变后,Watcher执行回调函数前),会获取数据的值,此时Observer会拦截数据(即调用get函数),然后通知Dep可以收集订阅者啦。Dep将订阅数据的Watcher保存下来,便于后面通知更新。
图中绿色的箭头表示的是数据改变时,发布更新的流程。当数据改变时,即设置数据时,此时Observer会拦截数据(即调用set函数),然后通知Dep,数据改变了,此时Dep通知Watcher,可以更新视图啦。
data和view就是字面意思,主要讲解Observer、Dep、watcher
Observer: 数据的观察者,让数据对象的读写操作都处于自己的监管之下。当初始化实例的时候,会递归遍历data,用Object.defineProperty来拦截数据(如果数据是多维数组,或者数组里面是对象这种多层嵌套的模式,那么每一层的数据都会被定义)。
但是需要注意的是,如果是数组实际上observer是没有定义get和set的,参见如下源码:
Dep:数据更新的发布者,get数据的时候,收集订阅者,触发Watcher的依赖收集;set数据时发布更新,通知Watcher 。(这个dep的使用就是在Object.defineProperty的get和set里面,参考源码defineReactive$$1函数)
Watcher:数据更新的订阅者,订阅的数据改变时执行相应的回调函数(更新视图或表达式的值)。
一个Watcher可以更新视图,如html模板中用到的{{test}},也可以执行一个$watch监督的表达式的回调函数(Vue实例中的watch项底层是调用的$watch实现的),还可以更新一个计算属性(即Vue实例中的computed项)。
因为js基础欠缺,有些原理我有点一知半解,如果想要看更加详细的代码讲解参考: Vue数据响应式原理 ,也可以结合我跑过的 Vue数据响应式原理加强理解
2、几个问题
1. 为什么数组下标修改非数组元素,修改数组len不响应
定义数组arr = [1,2,3],如果修改arr[0]=999是不会再页面响应的
因为defineReactive没有定义数组元素的get\set(get\set里面加了watcher的依赖),所以如果修改数组元素是不会感知到的,但是其实使用defineProperty是可以实现数组元素的监测的,看下面代码就实现了对每个元素修改的监测(参考:vue为什么不能检测数组的变化):
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function defineGet() {
console.log(`get key: ${key} value: ${value}`)
return value
},
set: function defineSet(newVal) {
console.log(`set key: ${key} value: ${newVal}`)
value = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
let arr = [1, 2, 3]
observe(arr)
在控制台设置arr[0]=10的时候,会发现进入了set函数:
但是为什么vue不实现,看github有人提问尤作者,他的回答是:
可以理解,应该是数组在实际开发中元素大的时候,如果使用defineProperty对每个元素进行监听就太消耗性能了,而如果想要实现对数组的某个元素进行修改实际上可以使用方法splice(这里的splice在vue中是有重写的,具体参考文章:https://www.jianshu.com/p/1032ecd62b3a)
2. 如果数组元素是对象可以通过下标修改
数组objArr = [1,{k1,‘v1’}],修改:objArr[1].k1 = ‘vAfter’,这个修改可以响应
查看源码会发现对于数组中的元素如果是数组或者对象还会调用observe函数,vue源码如下:
最后也使用defineReactive$$1设置了set,get,所以修改也会被响应
3. 如果删除或者新增数组元素,不管数组元素是否是对象,都不会响应
对于(2)中的数组,如果执行:objArr[1]={kAfter:‘vAfter’},这样是不会被响应的,该objArr[1]就是看objArr有没有set/get了,但其实数组在vue中没有给数组设置set/get
4. 如何修改数组某个下标非对象的元素,如果修改len
官方给出了说法https://cn.vuejs.org/v2/guide/reactivity.html#%E6%A3%80%E6%B5%8B%E5%8F%98%E5%8C%96%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9 使用vm.$set(vm.items, indexOfItem, newValue)修改下标值
如果要修改len,可以直接使用splice