简单实现Vue中的依赖收集与响应

74 阅读3分钟

开始

声明一个对象man,可以视为vue中的data

let man = {
	height:180,
    weight:70,
    wealth:10000
}

添加Observer

作用在于将参数对象的属性变为响应式,只要对象的属性被读取或者被修改都能观察到.然后新建一个Obersver实例,将man作为参数扔进去.这里的proxyData是将man的属性代理到以man为参数的Obersver实例上去.

    class Observer {
      constructor(obj){
        this.walk(obj)
      }
      walk(obj){
        Object.keys(obj).forEach(prop => {
          this.[prop] = obj[prop]
          this.proxyData(obj,prop)
          this.defineReactive(this,prop,obj[prop])
        })
      }
      proxyData(obj,prop){
        let _this = this
        Object.defineProperty(obj,prop,{
          get(){
            return _this[prop]
          },
          set(newVal){
            _this[prop] = newVal
          }
        })
      }
      defineReactive(obj,prop,val){
        Object.defineProperty(obj,prop,{
          get(){
            console.log(`${prop} - 被读取!`);
            return val
          },
          set(newVal){
            if(newVal == val) return
            val = newVal
            console.log(`${prop} - 被修改!`);
          }
        })
      }
    }
    new Observer(man)

这是打印一下man对象 现在调用man的属性都是由Obersver实例所对性的属性getter来返回,只有在查看时会被触发 对man的属性进行修改也会出发实例对应属性setter

添加Watcher

现在的Watcher有点像vue中的computed,实际上就是定义一个计算属性,这个计算属性依赖于前面man中的某些属性,由他们计算而得

    class Watcher{
      constructor(obj,prop,computed){
        this.getVal(obj,prop,computed,callback)
      }
      getVal(obj,prop,computed){
        Object.defineProperty(obj,prop,{
          get(){
            console.log(`computed属性 - ${prop}被读取`);
            // computed函数调用,会触发上面getter
            return computed()
          },
          set(){
            console.error('计算属性不可被修改');
          }
        })
      }
    }
    new Watcher(man,'strength',() => {
      let {height,weight} = man
      if(height > 160 && weight > 70) return 'strong'
      return 'weak'
    },() => {
      console.log(`i am so ${man.strength} !`);
    })

现在看起来没什么问题,所依赖的属性如果变了,计算属性只要再被查看(get方法)一次就可以更新了.但vue中的视图渲染是实时的,视图层依赖于数据层,数据变化了,视图层也会跟着变化,不需要手动更新.类比到这个例子就是计算属性如何才能在其所依赖的属性发生变化时被通知从而触发应有的事件

这时我们先给Watcher加多一个callback,用于处理当以来的数据被修改时,我们这个计算属性该怎么响应

比如当依赖被修改时,我们就把这个计算属性的值打印出来

class Watcher{
	constructor(obj,prop,computed,callback){
    	this.getVal(obj,prop,computed,callback)
    }
}
new Watcher(man,'strength',() => {
	let {height,weight} = man
    if(height > 160 && weight > 70) return 'strong'
    return 'weak'
}, () => {
	console.log(`i am so ${man.strength} !`)
})

一切都住备好了,接下来就是该如何实现?

我们先看下Watcher中getVal这个方法

getVal(obj,prop,computed,callback){
	Object.defineProperty(obj,prop,{
     get(){
     	console.log(`computed属性 - ${prop}被读取!`)
        return computed()
     },
     set(){
     	console.log('计算属性不可被修改!')
     }
    })
}

当我们查看计算属性时,会嗲用computed这个方法,相当于查看了其所依赖的height和weight属性,而在上面我们已经让man的所有属性都已拥有了get方法,即他们被查看是我们是不是可以把callback赛给他们?

这个时候我们引进一个桥梁,来连接Watcher和Observer

添加Dep

Dep的用处在于当某一个属性被依赖了,将以来自己的粉丝们 -- 也就是Watcher收集起来,假如自己发生了变化,能够及时通知粉丝们

    class Dep{
      constructor(){
        this.deps = []
      }
      getDeps(){
        if(!Dep.target || this.deps.includes(Dep.target)) return
        console.log('以来添加',Dep.target);
      }
      notify(){
        this.deps.forEach(dep => {
          dep()
        })
      }
    }

这里的Dep.target就是前面所说的callback方法了.这时我们改一下Watcher中的getVal

getVal(obj,prop,computed,callback){
	Object.defineProperty(obj,prop,{
    	get(){
        	Dep.target = callback
            console.log(`computed属性 - ${prop}被读取!`)
        },
        set(){
        	console.error('计算属性不可被修改!')
        }
    })
}

在计算属性被查看时,将callback赋值给Dep.target,接下来就会调用其所依赖属性的getter,我们只要在getter里把callback给收集起来就行了.接下来修改依赖属性的getter方法

    defineReactive(obj,prop,val){
      let dep = new Dep()
      Object.defineProperty(obj,prop,{
        get(){
          console.log(`${prop} - 被读取!`);
          dep.getDeps() //依赖收集
          return val
        },
        set(newVal){
          if(newVal == val) return
          val = newVal
          console.log(`${prop} - 被修改!`);
        }
      })
    }

这时watcher的callback都被依赖属性给收集起来了,当依赖属性发生变化只要去运行这些callback就可以了.接下来就是修改依赖属性的setter方法

    defineReactive(obj,prop,val){
      let dep = new Dep()
      Object.defineProperty(obj,prop,{
        get(){
          console.log(`${prop} - 被读取!`);
          dep.getDeps()
          return val
        },
        set(newVal){
          if(newVal == val) return
          val = newVal
          console.log(`${prop} - 被修改!`);
          dep.notify() //运行所有callback
        }
      })
    }