简单实现Vue的observer和watcher

非庖丁瞎解牛系列~ =。=

在日常项目开发的时候,我们将js对象传给vue实例中的data选项,来作为其更新视图的基础,事实上是vue将会遍历它的属性,用Object.defineProperty 设置它们的 get/set,从而让 data 的属性能够响应数据变化:

Object.defineProperty(obj, name, { // 获取值的时候先置入vm的_data属性对象中 get() { // 赋值的时候显示的特性 }, set() { // 值变化的时候可以做点什么 } })

接下来可以利用其实现一个最简单的watcher.既然要绑定数据执行回调函数,data属性和callback属性是少不了的,我们定义一个vm对象(vue中vm对象作为根实例,是全局的):

/** * @param {Object} _data 用于存放data值 * @param {Object} $data data原始数据对象,当前值 * @param {Object} callback 回调函数 */ var vm = { _data: {}, $data: {}, callback: {} }

在设置值的时候,如果检测到当前值与存储在_data中的对应值发生变化,则将值更新,并执行回调函数,利用Object.definedProperty方法中的get() & set() 我们很快就可以实现这个功能~

vm.$watch = (obj, func) => { // 回调函数 vm.callback[ obj ] = func // 设置data Object.defineProperty(vm.$data, obj, { // 获取值的时候先置入vm的_data属性对象中 get() { return vm._data[ obj ] }, set(val) { // 比较原值,不相等则赋值,执行回调 if (val !== vm._data[ obj ]) { vm._data[ obj ] = val const cb = vm.callback[ obj ] cb.call(vm) } } }) } vm.$watch('va', () => {console.log('已经成功被监听啦')}) vm.$data.va = 1

虽然初步实现了这个小功能,那么问题来了,obj对象如果只是一个简单的值为值类型的变量,那以上代码完全可以满足;但是如果obj是一个具有一层甚至多层树结构对象变量,我们就只能监听到最外层也就是obj本身的变化,内部属性变化无法被监听(没有设置给对应属性设置set和get),因为对象自身内部属性层数未知,理论上可以无限层(一般不会这么做),所以此处还是用递归解决吧~

咱们先将Object.defineProperty函数剥离,一是解耦,二是方便我们递归~

var defineReactive = (obj, key) => { Object.defineProperty(obj, key, { get() { return vm._data[key] }, set(newVal) { if (vm._data[key] === newVal) { return } vm._data[key] = newVal const cb = vm.callback[ obj ] cb.call(vm) } }) }

咦,说好的递归呢,不着急,上面只是抽离了加get和set功能的函数,
现在我们加入递归~

var Observer = (obj) => { // 遍历,让对象中的每个属性可以加上get set Object.keys(obj).forEach((key) =>{ defineReactive(obj, key) }) }

这里仅仅只是遍历,要达到递归,则需要在defineReactive的时候再加上判断,判断这个属性是否为object类型,如果是,则执行Observer自身~我们改写下defineReactive函数

// 判断是否为object类型,是就继续执行自身 var observe = (value) => { // 判断是否为object类型,是就继续执行Observer if (!value || typeof value !== 'object') { return } return new Observer(value) } // 将observe方法置入defineReactive中Object.defineProperty的set中,形成递归 var defineReactive = (obj, key) => { // 判断val是否为对象,如果对象有很多层属性,则这边的代码会不断调用自身(因为observe又执行了Observer,而Observer执行defineReactive),一直到最后一层,从最后一层开始执行下列代码,层层返回(可以理解为洋葱模型),直到最前面一层,给所有属性加上get/set var childObj = observe(vm._data[key]) Object.defineProperty(obj, key, { get() { return vm._data[key] }, set(newVal) { // 如果设置的值完全相等则什么也不做 if (vm._data[key] === newVal) { return } // 不相等则赋值 vm._data[key] = newVal // 执行回调 const cb = vm.callback[ key ] cb.call(vm) // 如果set进来的值为复杂类型,再递归它,加上set/get childObj = observe(val) } }) }

现在我们来整理下,把我们刚开始实现的功能雏形进行进化

var vm = { _data: {}, $data: {}, callback: {}} var defineReactive = (obj, key) => { // 一开始的时候是不设值的,所以,要在外面做一套observe // 判断val是否为对象,如果对象有很多层属性,则这边的代码会不断调用自身(因为observe又执行了Observer,而Observer执行defineReactive),一直到最后一层,从最后一层开始执行下列代码,层层返回(可以理解为洋葱模型),直到最前面一层,给所有属性加上get/set var childObj = observe(vm._data[key]) Object.defineProperty(obj, key, { get() { return vm._data[key] }, set(newVal) { if (vm._data[key] === newVal) { return } // 如果值有变化的话,做一些操作 vm._data[key] = newVal // 执行回调 const cb = vm.callback[ key ] cb.call(vm) // 如果set进来的值为复杂类型,再递归它,加上set/get childObj = observe(newVal) } }) } var Observer = (obj) => { Object.keys(obj).forEach((key) =>{ defineReactive(obj, key) }) } var observe = (value) => { // 判断是否为object类型,是就继续执行Observer if (!value || typeof value !== 'object') { return } Observer(value) } vm.$watch = (name, func) => { // 回调函数 vm.callback[name] = func // 设置data defineReactive(vm.$data, name) } // 绑定a,a若变化则执行回调方法 var va = {a:{c: 'c'}, b:{c: 'c'}} vm._data[va] = {a:{c: 'c'}, b:{c: 'c'}} vm.$watch('va', () => {console.log('已经成功被监听啦')}) vm.$data.va = 1

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwjpff.html