4

Vue的主要原理中主要用到了定义的这么几个函数Dep,Watcher,observer。
我们来使用这几个函数简单的实现一下vue构造函数数据绑定和相互依赖部分,梳理一下它们之间的关系。
省略了编译部分和proxy代理与其他的一些复杂逻辑。

Dep

Dep是依赖类,简要实现为

class Dep {
  constructor () {
    // 放当时属性的观察者
    this.subs = []
  }
}
// target 用来挂载当时的watcher观察者
Dep.target = null

observer

做属性劫持,并做点其他事情


function observer (vm, key, val) {
  let dep = new Dep()
  Object.defineProperty(vm, key, {
    /**
     * get主要做两个事情
     * 1. 收集观察当前key的wathcer(即依赖当前key的操作)
     * 2. 获取值
     */
    get () {
      // 这是作用1
      if (Dep.target) {
        dep.subs.push(Dep.target)
      }
      // 这是作用2
      return val
    },
    /**
     * set也是两个事情
     * 1. 修改目标值
     * 2. 执行依赖当前key的watcher
     */
    set (newVal) {
      // 这是作用1
      val = newVal
      // 这是作用2
      for(cb of dep.subs) {
        cb.call(vm)
      }
    }
  })
}

Watcher

Watcher是观察者类,用来创建依赖某属性的操作(如指令,渲染,计算属性等)

class Watcher {
  /**
   * vm: 实例
   * cb: 依赖某属性的操作函数
   */
  constructor (vm, cb) {
    // 把当前的操作挂载到Dep上
    Dep.target = cb
    /**
     * 执行操作,两个作用
     * 1. 进行操作的初始化
     * 2. 触发属性的get方法,使当前cb被收集
     */
    cb.call(vm)
    Dep.target = null
  }
}

demo

那么我们就使用上面定义好的函数写个例子

<div>
  <p class="text"></p>
<div>
let vm = new Vue({
  // 假设有data
  data: {msg: 1},
  // 有某个v-text操作,我们抽象为vText函数,依赖属性msg(代表所有依赖其他属性的操作)
  renderFun: {
    vText () {
      document.querySelector('.text').innerText = this.msg
    }
  }
})
// 修改vue实例的值,观察变化
vm.msg = 333

那么我们也写一个vue的简易构造函数

class Vue {
  constructor (options) {
    let data = options.data
    let renderFun = options.renderFun
    // initData
    Object.keys(data).forEach(key => {
      observer(this, key, data[key])
    })
    // 模拟计算属性,watcher,指令等依赖属性的操作
    Object.keys(renderFun).forEach(key => {
      new Watcher(this, renderFun[key])
    })
  }
}

执行过程

完整的代码可以看demo部分的两个链接

  1. 创建vue实例,执行new Vue()
  2. 对data进行初始化,对data中属性进行属性劫持

    • 劫持过程中,在闭包内创建对当前属性的依赖队列(dep.subs)和值(val)。get进行观察者watcher的收集和值得获取;set进行值的更新和依赖队列中watcher的执行
  3. 对编译过程中如computed\watcher模板编译过程中的指令函数进行初始化,我们以renderFun代替
  4. 针对renderFun中的每个功能函数进行new Watcher()工作
  5. vText为例子,在new Wathcer()过程中

    1. vText挂载到全局通用的Dep.target
    2. 执行vText,其中有读vm.msg的操作,则触发msg属性的get,进入Dep.target判断,将Dep.targetvText收集进msgsubs依赖队列中,此时vText执行完毕,页面innetText被修改
    3. Dep.target置空
  6. 执行vm.msg = 333,则触发msgset

    1. set先修改msg的值
    2. 再执行msg依赖队列中的所有watcher的函数,即vText,页面的innerText被同步更新

总结

总之几者的关系就是在observerget中将对当前属性的watcher收集进dep,在observerset中执行收集到的watcher

而vue的真正的执行过程绝不是上面写的这么简单,比如watcher的执行就绝不是简单的遍历执行,而且还对observer进行了很大程度的简化。我们还省略了诸如_proxydefineReactive等出现频率较高的函数。写这样一个最简实现主要是为了梳理一下主干,降低阅读源码的难度。


toBeTheLight
16.6k 声望26.6k 粉丝

学习数学知识(0/n)