vue源码学习总结 深入响应式原理

  • Post author:
  • Post category:vue




深入响应式原理图

请反复观看原理图,画这个图的人请收下我的膝盖。。。

在这里插入图片描述



从initState切入,发现有3种watcher

Vue.prototype._init= function (options?: Object) {
	callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props 初始化注入
    initState(vm) // 初始化 data/props/method/computed/watch
    initProvide(vm) // resolve provide after data/props 初始化provide
    callHook(vm, 'created')
}
export function initState (vm: Component) {
  vm._watchers = [] //可以通过this._watchers知道当前实例有哪些watcher
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props) 
  if (opts.methods) initMethods(vm, opts.methods) 
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed) //里面new Watcher()来处理computed的依赖
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch) //里面new Watcher()来处理自定义watch的依赖
  }
}

假设有伪代码如下:

	mounted() {
      console.log(this._watchers)
    },
    computed: {
      computedName() {
        return "计算属性watcher:"+this.ff
      }
    },
    watch: {
      watchName(newVal,oldVal) {
        console.log(newVal,oldVal)
      }
    },

事实上有3种watcher:

  1. 响应computed变化的 ,特征

    lazy为true


    在这里插入图片描述
  2. 响应用户自定义watch变化的,特征

    user为true


    在这里插入图片描述
  3. 挂载页面时用户响应渲染依赖变化的,特征

    expression为 function () {↵ vm._update(vm._render(), hydrating);↵ }


    在这里插入图片描述

computed和watch的先不谈,先看看第三种。



第三种watcher分三步,第一步数据劫持设置getter/setter,第二步页面挂载的时候触发get收集依赖,第三步数据发生变化set的时候通知被依赖者重新渲染

这句话是深入响应式原理的重中之重!!!



数据劫持Object.defineProperty()

从initState=>

initData=>

observer(data) =>

new Observer(data) =>

defineReactive(obj, keys[i], obj[keys[i]]), 源码如下:

function initData (vm: Component) {
  // observe data 观察data数据
  observe(data, true /* asRootData */)
}
/**
 * 尝试为某个值创建观察者实例,如果观察成功,则返回新的观察者,如果该值已具有现有的观察者,则返回现有的观察者。
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value) //新建观察者对数据进行观察
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

/**
 * 附加到每个被观察对象的观察者。一旦附加,观察者将目标对象的属性键转换为getter/setter,以收集依赖项并派发更新。
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value //vm._data={}
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      // 如果是数组类型,改写数组的操作方法 push pop unshift等
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value) // 观察数组每一项
    } else {
      this.walk(value)
    }
  }

  /**
   * 遍历每个属性并将它们转换为getter/setter。仅当值类型为“对象”时才应调用此方法。
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]]) 
    }
  }

  /**
   * 观察数组的每一项
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean //浅的
) {
  // 每个被观察的数据对应一个dep,dep就是依赖项,watcher是依赖dep的人
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  let childOb = !shallow && observe(val) // 对对象的所有属性迭代观察
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // Dep.target为当前依赖此值的watcher,调用get方法时就可以得知依赖此dep的watcher是谁,收集成数组保存
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 新值需要重新观察
      childOb = !shallow && observe(newVal)
      dep.notify() //通知依赖此值的watcher, 此值改变了
    }
  })
}


注意此时的 getter/setter 是定义,并没有执行。也就是说依赖项还没有收集起来。

如果没注意到这一点可能会对执行先后顺序完全懵逼(我之前就懵逼了+_+)。当前阶段为beforeCteate和created之间



挂载-渲染-收集依赖

从 mountComponent=>

new Watcher()=>

watcher.get() =>

vm._update(vm._render(), hydrating) 此处为生成VNode节点然后渲染DOM树(略),其中会求数据值,所以=>

Object.defineProperty的getter =>

dep和watcher互相绑定(建立依赖关系)

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount')

  let updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }

  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;//是否立刻求值 immediate
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: ISet;
  newDepIds: ISet;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn) //将字符串“a.b.c”转换成引用this.a.b.c
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   * 评估getter,并重新收集依赖项。
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  addDep (dep: Dep) {// dep是被观测的属性值  sub是watcher
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
}
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// 当前正在执行求值(依赖收集)操作的watcher
Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}



data变化-触发set-watcher更新

当data变化this.name=newVal =>

Object.defineProperty的setter =>

dep.notify() =>

queueWatcher(this)循环每个需要更新的watcher进入到更新队列(去重)等待更新 =>

nextTick(flushSchedulerQueue) =>

执行watcher.run() =>

执行watcher.get完成更新, 此处get实际为执行updateComponent = () =>{vm._update(vm._render(), hydrating)},页面重新渲染(渲染过程中重新求值完成更新)



深入computed的响应式原理

initComputed=>

new Watcher({lazy:true})=>

defineComputed(vm, key, userDef) =>

createComputedGetter(key)=>

watcher.evaluate()

computed初始化时不求值,第一次使用(页面挂载)的时候才求值,当响应式依赖变化时设置watcher.dirty为true, 下一次调用的时候发现dirty为true才求新值,否则返回缓存值。new Date().getTimes()和Math.random()等都不是响应式依赖,所以不会重新求值。

const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)
  // 新建watcher响应computed的变化
  watchers[key] = new Watcher(
    vm,
    getter || noop,
    noop,
    computedWatcherOptions
  )
  defineComputed(vm, key, userDef)  //对computed的访问进行数据劫持
}

export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    this.lazy = !!options.lazy // 计算属性这里为true
    this.dirty = this.lazy // data默认是脏的,所以第一次使用的时候才会求值
    // computed 不会立即求值,而是用的时候才求值
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  update () {
    if (this.lazy) {
     // 不主动更新值, 而是改变标志变量,等到用的时候才更新值(如果没人用就不求值节约性能)
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
  evaluate () {
  	// computed求值后,设置标志变量false
    this.value = this.get()
    this.dirty = false
  }
}
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  // 对computed的访问进行数据劫持
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {// 当数据脏了才求值, 否则返回旧值
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}



深入watch的响应式原理

initState=>

initWatch=>

createWatcher=>

Vue.prototype.$watch()=>

new Watcher()

//参数解释
watch: {
    name: {
      deep:false,//是否要深度响应,子属性改变的时候也执行此函数
      immediate:false,//是否立即执行一次
      asyn:false,//是否在当前tick执行,默认nextTick(watcher)执行
      handler(newVal,oldVal) {
        console.log(newVal,oldVal)
      }
    }
  }
function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
function createWatcher (
  vm: Component,
  keyOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(keyOrFn, handler, options)
}
Vue.prototype.$watch = function (
  expOrFn: string | Function,
  cb: any,
  options?: Object
): Function {
  const vm: Component = this
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options)
  }
  options = options || {}
  options.user = true //用户自定义的watch ,默认user为true
  const watcher = new Watcher(vm, expOrFn, cb, options)
  if (options.immediate) { //是否立即执行一次
    cb.call(vm, watcher.value)
  }
}


深入响应式原理参考

再回头看看最上面的深入响应式原理图,是不是感觉所有松散的线都串联在一起了。

终于把深入响应式原理写完了,我真是太难了。。。

关注下再走呗 +_+ 。。。。。。。



版权声明:本文为lyt_angularjs原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。