Vue源码的原理-对象的响应式

97 阅读1分钟

Vue源码学习记录

参考于 深入浅出vue.js此书

vue实现响应式?

对象是通过Object.defineProperty()实现的,数组是通过写数组的方法(push、pop 、shift、unshift reverse sort),设置拦截器实现响应式的。一般包括四个部分:dep(收集、删除、通知依赖)、watcher(指依赖,类似中介,被dep收集起来,数据发生变化时dep通知watcher,它再通知其它地方)、Observer(将每个对象数据的属性都变为响应式:即添加上getter和setter)、Data(数据)

四者关系图如下:

截屏2022-02-24 下午2.21.33.png

对象的实现如下:

基本响应式框架

function defineReactive(data,key,val) {
    if(typeof val === 'object') { // 递归子属性
      new Observer(val)
    }
    let dep = new Dep() // 将依赖收集加入
    Object.defineProperty(data,key,{
      enumerable: true,
      configurable: true,
      get:function() { // 在getter中收集依赖
        dep.depend()
        return val
      },
      set(newVal) { // 在set中触发依赖
        if(val === newVal) {
          return
        }
        val = newVal
        dep.notify()
      }
    })
  }
Dep依赖管理器实现
class Dep{
   constructor() {
     this.subs = []
   }
   addSub(sub) {
     this.subs.push(sub)
   }
   remove(sub) {
    remove(this.subs,sub)
   }
   depend() { // 添加订阅对象
     if(window.target) {
       this.addSub(window.target)
     }
   }
   notify() {
     let subs = this.subs.slice()
     for(let i=0;i<subs.length;i++) {
       subs[i].update() // watcher中自定义函数
     }
   }
 }
function remove(arr,item) {
    if(arr.length) {
     let index = arr.indexOf(item)
     if(index > -1) {
       return arr.splice(index,1)
     }
      
   }
 }
watcher的实现
class Watcher {
  constructor(vm,exporFn,cb) { // cb:回调函数
    this.vm = vm
    this.getter = parsePath(exporFn) // 读取路径:exporFn类似’a.b.c.c‘
    this.cb = cb
    this.value = this.get()
  }
  get(){
    window.target = this // 设置成当前watcher实例
    let value = this.getter.call(this.vm,this.vm)
    window.target = undefined
    return value
  }
  update() {
    const oldVal = this.value
    this.value = this.get()
    this.cb.call(this.vm,this.value,this.oldVal)
  }
}
function parsePath(path) {
  let reg = /^\w$/
  if(!reg.test(path)) {
    return
  }
  let splitArr = path.split('.')
  return function(obj) {
    for(let i=0;i<splitArr.length;i++) {
      if(!obj) return
      obj = obj[splitArr[i]]
    }
    return obj
  }
}
Observer类的实现
class Observer{
  constructor(value) {
    this.value = value
    if(!Array.isArray(value)) {
      this.walk(value)
    }
  }
  walk(obj) { // 只有对象才被调用
    const keys = Object.keys(obj)
    for(let i=0;i<keys.length;i++) {
      defineReactive(obj,keys[i],obj[keys[i]])
    }
  }
}

只能检测到对象的修改、不能检测到增加和删除属性,可以使用vue.setvue.set和vue.delete来检测两者。