把一个普通 Javascript 对象传给 Vue 实例来作为它的
data
选项,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setters,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的
setter
被调用时,会通知
watcher
重新计算,从而致使它关联的组件得以更新。
响应式原理实现步骤:
1.轮询data中的所有属性,先将属性值赋值internalValue,并将所有属性设置为访问器属性,
读取属性时需要调用get()函数,写入属性时需要调用set()函数
2.读取属性时调用get()函数,查看是否正在调用watch,是否需要添加函数句柄到subscribers中,
并返回internalValue给属性读取处
3.写入属性时调用set()函数,先将属性值写入internalValue,再执行subscribers中的所有watch调用的函数句柄,
函数句柄中读取属性时又重新通过get()函数获取了internalValue(写入的属性值),并进行计算来实现依赖数据的更新
响应式原理实现代码:可以自行添加打印后查看
1 let data = { price: 5, quantity: 2 } 2 let target = null 3 4 class Dep { 5 // Dep.prototype.custructor 指向Dep构造函数,this指向的属性为实例属性 6 constructor () { 7 this.subscribers = [] //存储函数句柄的数组 8 } 9 10 // 定义在Dep.prototype上的depend()方法 11 depend () { 12 // 若果target(函数句柄)不为空且subscribers数组中没有target(函数句柄),则将target(函数句柄)加入subscribers数组中 13 if (target && !this.subscribers.includes(target)) { 14 this.subscribers.push(target) 15 } 16 } 17 notify () { 18 //轮询subscribers数组,且执行数组中的每个函数句柄 19 this.subscribers.forEach(sub => sub()) 20 } 21 } 22 23 //获取data中的可枚举的实例属性组成的字符串数组,并且轮询这个数组,数组中的每个属性执行箭头函数 24 Object.keys(data).forEach(key => { 25 //获取每个属性对应的属性值(实时更新) 26 let internalValue = data[key] 27 console.log('1.internalValue:'+internalValue) 28 29 //创建一个实例dep,dep有subscribers属性(存储函数句柄)和depend()方法、notify()方法 30 const dep = new Dep() 31 32 //将data对象的数据属性修改为访问器属性,访问属性时会调用相应的get/set方法 33 //在函数内部创建另一个函数即闭包,闭包指有权访问另一个函数作用域中的变量的函数,即get/set函数有权访问箭头函数中的变量 34 Object.defineProperty(data, key, { 35 get() { 36 console.log('getter') 37 console.log('2.internalValue:'+internalValue) 38 dep.depend() //执行dep.depend()方法,添加targe属性到subscribers数组中 39 return internalValue // 返回属性值 40 }, 41 set(newVal) { 42 console.log('setter') 43 console.log('3.internalValue:'+internalValue) 44 internalValue = newVal //把新的值设置到internalValue中 45 dep.notify() //执行dep.notify()方法,轮询subscribers数组,且执行数组中的每个函数句柄 46 } 47 }) 48 }) 49 50 //添加watcher函数声明,将myFun(函数声明)赋值给target,并执行target(函数句柄) 51 function watcher(myFun) { 52 target = myFun 53 target() //调用函数句柄 54 target = null 55 } 56 57 //将函数声明作为参数传入并执行watcher函数 58 watcher(() => { 59 data.total = data.price * data.quantity //箭头函数不是立即执行,需要手动调用 60 }) 61 62 watcher(() => { 63 data.total1 = data.price * 2 64 }) 65 66 console.log("total = " + data.total) 67 data.price = 20 68 console.log("total = " + data.total) 69 data.quantity = 10 70 console.log("total = " + data.total)
参考来源:
1.vue官网:http://doc.vue-js.com/v2/guide/reactivity.html
2.JavaScript 响应式与 Proxy:https://mp.weixin.qq.com/s/GktsHoN3q12nz8c-QlfqgQ