# vue源码 响应式系统分开讲

# data的响应式流程

  • /src/core/instance/state.js
  • initdata 阶段 先proxy一层代理后,初始化 observer
// initData 
function initData(data) {
  ···
  observe(data, true)
}
// observe
function observe(value, asRootData) {
  ···
  ob = new Observer(value);
  return ob
}

  • observer 类会给 data 加上一个不可枚举的属性_ob_,标识为响应式对象,并且外界无法通过遍历获得该属性值
// 观察者类,对象只要设置成拥有观察属性,
//则对象下的所有属性都会重写getter和setter方法,
//而getter,setter方法会进行依赖的收集和派发更新
var Observer = function Observer (value) {
    ···
    // 将__ob__属性设置成不可枚举属性。外部无法通过遍历获取。
    def(value, '__ob__', this);
    // 数组处理
    if (Array.isArray(value)) {
        ···
    } else {
      // 对象处理
      this.walk(value);
    }
  };

  function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable, // 是否可枚举
    writable: true,
    configurable: true
  });
}

  • 如果是对象,遍历 walk遍历重写getter/setter后调用defineReactive,如果是数组是另外的处理
Observer.prototype.walk = function walk (obj) {
    // 获取对象所有属性,遍历调用defineReactive###1进行改写
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
        defineReactive###1(obj, keys[i]);
    }
};

  • defineReactive 里会实例化dep类,这个类为每个数据创建一个依赖管理,实质也是Object.defineProperty
  • data被访问时会被getter拦截
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {// 在getter阶段进行依赖收集
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    }
  • 实例挂载前会创建一个watcher,见mountComponent
// we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)

  • render的时候会访问data,触发getter进行依赖收集
dep.depend();
...
Dep.prototype.depend = function depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  };

  Watcher.prototype.addDep = function addDep (dep) {
    var id = dep.id;
    if (!this.newDepIds.has(id)) {
      // newDepIds和newDeps记录watcher拥有的数据
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      // 避免重复添加同一个data收集器
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  };


  • 数据发生改变后,触发setter, 判断逻辑后,dep.notify()进行更新的派发
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()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify() // 
    }
  • 更新时会将每个watcher推入队列中
update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this) //推入队列
    }
  }
  • run() 还是会调用this.get()触发getter并重新收集
 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) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

# computed的响应式流程

  • initComputed /src/core/instance/state.js
function initComputed (vm: Component, computed: Object) {

...
  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
   ...

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher( // 为每个computed属性实例化watcher
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
    ....
    }
  }
}

  • 计算属性结果是会被缓存的,只有在响应式数据发生变化的时候才会被重新求值
...
        ? createComputedGetter(key)

...
  Object.defineProperty(target, key, sharedPropertyDefinition)

  • createComputedGetter()
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) { // 标志是否执行过计算
        watcher.evaluate() // -->this.get()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}
  • computed watcher 不会立即执行依赖的更新派发,会通过dirty进行标记

# 数组的响应式流程

  • 数组的改变不会出发setter进行依赖更新,所以vue创建了一个新的数组类,重写后改变指向
  • 访问时依旧触发getter进行依赖派发
  • vue的做法是在原型链上对数组方法进行重写
  • /core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
  • methodsToPatch
  • 当调用数组内部一些操作时,会执行arrayMethods进行响应式
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
  • def 也是Object.defineProperty
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
...   
  • observe类里也有对数组相关的处理 constructor
...
 if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
  • protoAugment 将当前数组的原型指向arrayMethods
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
  • copyAugment 不支持_proto_时,通过代理
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

# 对象检测异常

  • test:{a:1},当test.b = 2添加时vue无法检测(因为没有依赖收集的过程)
  • vue.setvue.$set解决

# 手动watch的响应式过程

  • initWatch
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)
    }
  }
}
  • createWatcher
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
  • vm.$watch
 Vue.prototype.$watch = function (c// yiki watcher
    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
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) { // 立即执行
     ...
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
Last Updated: 2022/6/26 上午11:43:14