vue中监听object数据变化的基本原理
2020-07-01 10:27
906 查看
# 简略版+自己的注释
// 判断一个变量是否是对象 function isObject(obj) { return obj.constructor === Object } class Observer { constructor(value) { this.value = value; if (!arr.isArray(value)) { this.walk(value); } } walk(obj) { const keys = Object.keys(obj); // 循环将obj中的每一个属性转换成getter/setter进行变化追踪 for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]); } } } function defineReactive(data, key, val) { if (isObject(val)) { new Observer(val); // 进行递归调用 } let dep = new Dep(); Object.defineProperty(data, key, { configurable: true, enumerable: true, get: function () { dep.depend(); return val; }, set: function (newVal) { if (val === newVal) return // 如果赋值的新值也是一个对象 也需要进行侦测 if (isObject(newVal)) { new Observer(val); // 进行递归调用 } val = newVal; dep.notify(); // 通知所有的订阅者,数据要被修改了,做出相应的行为(也就是执行对应的回调函数) } }) } class Dep { constructor() { this.subs = [] // 这个里面存放的是Watch实例对象 } addSub(sub) { this.subs.push(sub); // 在这个地方收集订阅者 } removeSub(sub) { remove(this.subs, sub); } depend() { if (window.target) { this.addSub(window.target); // 在这个地方触发depend方法,进行收集订阅者 } } notify() { const subs = this.subs.slice(); for (let i = 0; i < subs.length; i++) { subs[i].update(); // 在这个地方执行回调函数 } } } function remove(arr, item) { if (ar 1bc2 r.length) { const index = arr[item]; if (index > -1) { return arr.splice(index, 1); } } } class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm; this.getter = parsePath(expOrFn); this.cb = cb; this.value = this.get(); // 获取expOrFn中的值 在这个内部同时会触发getter,从而在dep中添加自己 } get() { window.target = this; // 将watch实例对象赋值给全局的target变量上 let value = this.getter.call(this.vm, this.vm); // 该代码的作用很关键 // 如果expOrFn直接是一个表达式不是一个函数 eg: 'name', 'age' 假设只有一个属性 不是这种的 'name.a.b' // 我们就可以直接下面这样写,只是为了测试理解,vue.js源码处理更加全面 // let value = this.vm[this.expOrFn] // vm就是要监听的数据,当然expOrFn要在constructor中挂在到this身上 window.target = undefined; return value; } update() { const oldValue = this.value; this.value = this.get(); this.cb.call(this.vm, this.value, oldValue); } } // 该方法的作用是将 'name.a.b'这种表达式的值取出来 // 也就是通过该方法返回的是 obj['name']['a']['b]的值 function parsePath(path) { const bailRE = /[^\w.$]/; const segments = path.split('.') // 闭包 return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] // 取出属性的值 所以这个地方会执行getter } return obj; // 返回属性的值 } } <script> let btns = document.getElementsByTagName('button'); // 定义数据 let person = {}; // 定义响应式数据的属性 defineReactive(person, 'name', '乔峰') // 监听数据 let w = new Watcher(person, 'name', function (newVal, oldVal) { console.log('数据发生变化了') console.log(newVal, oldVal); }) // 1.取值操作 person.name // 2.改变数据 btns[0].onclick = function () { person.name = '小龙女'; }
# 总结
1.在实例化一个Watcher对象时,其get方法中会触发defineReactive中的getter访问器,在其getter访问器中会执行dep.depend()方法,dep.depend()方法会调用this.subs.push(sub)方法,从而收集依赖,也就是在subs 中存放的是当前的Watcher实例对象
2.Watch实例对象自身必须有一个update方法
3.当数据发生变化时,会触发defineReactive中的setter访问器,在其setter访问器中会调用subs[i].update()方法,其中的每一个subs[i]就是watch实例对象,从而执行了update方法,在update方法中执行了this.cb.call(this.vm, this.value, oldValue)回调函数
3.Observer 类是用来将传递进来的数据遍历转换成响应式数据,也就是转换成getter/setter的形式进行侦测
4.在使用的时候,我们首先需要调用defineReactive方法,来创建一个响应式数据,然后再调用Watcher方法来监听这个响应式数据的变化,然后就能做到数据驱动,或者叫响应式数据
______如有不对,请指正_______
相关文章推荐
- 记vue的watch深度监听
- 关于Vuex中异步请求到的数据值是有的但是获取不到的问题
- Chrome安装vue-DevTools5.3.3版出现Cannot find module ‘@vue-devtools/build-tools‘错误解决办法
- vue实现历史搜索
- Vue中的计算属性与v-on参数
- Vue中非父子之间的事件车传值(事件总线)
- Vue基础路由跳转传值
- Go-vue 前后端跨域 (cors+axios)
- vue刷新页面
- vue页面适配
- Vue.js简单实现Todolist综合案例 (一) 选择完成或未完成 双击修改文本内容
- Vue从一个A页面 “携带参数” 跳转到另外一个B页面
- Vue基础使用
- vue 打包上线后字体图标不显示
- Vuex有什么作用?如何使用Vuex
- vue封装toast插件
- vue3打包不显示问题
- vue中的图片懒加载-vueLazyLoad
- vue基础篇(一)
- vue结合Element - ui实现行合并(看懂这个以后的行合并都不是问题)