了解一下Vue - [Vue是怎么实现响应式的(二)]

写在前面:本文为我的在平常工做和学习中的一些总结,便于后来查漏补缺,非权威性资料,请带着本身的思考^-^。
前文连接:了解一下Vue - [Vue是怎么实现响应式的(一)]
前言:上一篇文章简单介绍了基于data的Vue响应式的实现,这篇将进行一点扩展,data变动会自动触发computed进行从新计算,从而反映到视图层面,那这个过程又是怎么作到的呢?react

从Vue实例生成过程的initComputed提及

顾名思义,在initComputed中主要进行的工做是对computed进行初始化,上代码:segmentfault

function initComputed (vm, computed) {
  const watchers = vm._computedWatchers = Object.create(null) // 用于存放computed 相关的watcher
  
  for (const key in computed) { // 遍历computed,为每个computed属性建立watcher实例,这个watcher实例的做用后面会体现
    // 这里能够看出,平时咱们的computed通常都是函数形式的,但不少时候咱们也能够写成{get() {}, set() {}},这种对象形式
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop, // 此函数参数会在watcher实例的get方法中进行执行
        noop,
        computedWatcherOptions // {dirty: true},能够翻一下以前的class Watcher代码或者找源码看一下,这个options其中的一个做用就在于控制实例化watcher的时候是否先执行一次get() 方法,这个get方法内部会对参数传进来的getter进行执行
      )
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    }
  }
}

// function defineComputed

function defineComputed ( // 总体来讲此函数的做用就是经过defineProperty定义getter/setter将computed中的属性代理到vm上
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering() // 非服务端渲染,则为true
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key) // computed的计算结果会被缓存,不须要每次访问computed属性时都从新进行计算
      : userDef // computed 不使用缓存的状况
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

实例化Vue期间,对computed的处理,作了:缓存

  1. 将computed[key]的值做为getter参数,实例化一个Watcher对象(至于Watcher对象的做用,后面会提到);
  2. 将computed[key]的值(实际上是通过包装的)做为getter,经过defineProperty将computed[key]代理到vm[key];

说到$mount的执行过程

$mount包含了模板编译、render函数生成... 再也不赘述
和响应式相关的是在$mount中实例化了一个render Watcher,前文已经有过标注,在实例化Watcher中会执行get()函数,从而执行render函数,在render函数的执行过程当中势必会读取页面渲染使用到的computed属性,触发其getter:函数

// computed属性的getter
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key] // watcher就是initComputed时传入computed[key]做为getter参数实例化的watcher
    if (watcher) {
      if (watcher.dirty) { // 若是是首次读取computed[key],通常watcher.dirty为true
        watcher.evaluate()
        /**
        evaluate () {
            this.value = this.get()
            this.dirty = false // 执行完get以后就会将dirty置为false,这样下次读取computed[key]时就不会再从新计算
        }
        执行watcher.get(),在这个函数内部会作几个事情:
        执行pushTarget函数,将当前的Dep.target置为当前watcher实例
        执行computed[key],计算获得结果赋值给watcher.value
        若是computed[key]函数内容是经过几个data计算获得值,则将会触发data的getter,
        这将会把这个几个data的dep对象做为依赖添加至watcher的deps列表,同时将当前watcher添加至这些dep的subs列表中,
        通俗一点说,这个过程对于当前watcher来讲就是依赖收集过程,将其依赖的项添加至deps中
        对于某一个data的dep来讲,就是将当前watcher添加至其观察者列表subs中
        执行完以上过程,就会将Dep.target重置为以前的值,
        */
      }
      if (Dep.target) {
        watcher.depend() // 这一步也很重要,此处是将当前watcher的依赖也加入到Dep.target的依赖列表中
        /**
        为何要有这一步呢?
        由于当前的Dep.target在执行完watcher.evaluate以后就被重置回了上一个Dep.target,通常来讲当前的Dep.target
        就是render Watcher
        设想有这种状况:某一个data的属性x并无直接用于render,那么在render执行过程的依赖收集x就不会被添加
        到render Watcher的deps中,x的dep.subs中也没有render Watcher 也就是说以后若是对x进行从新赋值,则不会
        通知render Watcher,此时尚未问题,但时若是x被computed用到,因为computed没有setter,则x被从新赋值
        通知到computed Watcher去从新计算,可是computed并无直接通知render Watcher的方法,这个时候render就不会
        从新执行,页面也就不会进行更新。。。
        */
      }
      return watcher.value
    }
  }
}

总之,详见代码注释。。。oop

当computed依赖的data更新时

这里已经知道对data从新赋值,会触发其对应setter学习

// setter
set: function reactiveSetter (newVal) {
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      
      val = newVal
      childOb = !shallow && observe(newVal)
      dep.notify() // 这里会通知到全部watcher,让它们进行update
    }
    
// dep.notify
notify () { // 在render阶段进行依赖收集时会将watcher加入subs列表,computed在进行计算的时候会收集依赖的data,
// 与此同时会将computed的watcher对象添加至data的dep.subs列表
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

render watcher和computed watcher执行update是不一样的,computed watcher的update方法只会将watcher.dirty置为true,这表明该computed依赖的data发生了更新,须要从新计算;这样在render函数再次执行的时候会读取computed,触发computed的getter,在getter中会从新计算得出computed的新值,而且将dirty置为false,表明只需计算一次,在同一个render loop中屡次引用该computed将不会从新计算。this

THE END...lua

相关文章
相关标签/搜索