Vue响应式原理

vue-reactive

Posted on 2018-06-18

Vue响应式原理依赖于Object.defineProperty,正是由于这个原因才使得Vue不兼容IE8及以下浏览器的原因。

Object.defineProperty

Object.defineProperty(obj, prop, descriptor)
  • obj 要在其上定义属性的对象
  • prop 要定义或修改的属性的名称
  • descriptor 将被定义或修改的属性描述符, 为一个对象,有下面这些属性
    • configurable 是否可以删除目标属性或是否可以再次修改属性的特性,默认为false
    • enumerable 此属性是否可以被枚举(使用for...inObject.keys())默认为false
    • value 属性对应的值,可以使任意类型的值,默认为undefined
    • writable 属性的值是否可以被重写。设置为true可以被重写;设置为false,不能被重写。默认为false
    • get 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined
    • set 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined

响应式

在 Vue 实例 的初始化过程中,_init 方法执行时,会执行 initState(vm) 方法。

// src/core/instance/state.js

export function initState (vm: Component) {
  vm._watchers = []
  // 获取实例的options
  const opts = vm.$options
  // 存在props, 则初始化props
  if (opts.props) initProps(vm, opts.props)
  // 存在methods, 则初始化methods
  if (opts.methods) initMethods(vm, opts.methods)
  // 存在data, 则初始化data
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

可以看到,initState主要是对propsmethodsdatacomputedwatcher属性初始化。

initData

// src/core/instance/state.js

function initData (vm: Component) {
  // 获取 data 值
  let data = vm.$options.data
  // data 是否是 function 类型,如果是调用 getData
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // data 是否是一个普通的对象, 不是则直接赋值{}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  
  // 获取data的key值
  const keys = Object.keys(data)
  // 获取 props
  const props = vm.$options.props
  // 获取 methods
  const methods = vm.$options.methods
  let i = keys.length
  // 遍历
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      // 确保 data 和 methods 没有重复的key
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 确保 data 和 props 没有重复的key
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // vm._data.key 代理到 vm.key 上
      proxy(vm, `_data`, key)
    }
  }
  // 响应式数据
  observe(data, true /* asRootData */)
}

数据代理

在我们使用Vue的过程中,在 methods 中,可以通过 this.key 来获取定义在 data上的数据。但我们实际过程中并没有给实例绑定任何的key,这就是proxy的作用。即把 props 和 data 上的属性代理到Vue实例上。

// src/core/instance/state.js

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

proxy(vm, _data, key)

通过 Object.defineProperty 把 target[sourceKey][key] 的读写变成了对 target[key] 的读写

例如

this.name = 'tonny' // 调用 set 方法,执行this._data.name = 'tonny'
this.name // 调用 get 方法, 返回 this._data.name

响应式

observe(data, true) 用来监听数据的变化

// src/core/observer/index.js

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 确保要监听的 value 不是 VNode 类型且不是非 Object 类型
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 要监听的对象是否有 __ob__ , 有则直接赋值
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    // 没有 __ob__属性,满足条件则创建一个 Observer
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

从上面代码可以看出,主要是获取value的 __ob__ ,有则直接获取,无则创建一个Observer

// src/core/observer/index.js

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
    this.dep = new Dep()
    this.vmCount = 0
    // 通过 Object.defineProperty 为 value 添加 __ob__ 属性
    def(value, '__ob__', this)
    
    // 如果 value 是数组,调用 observeArray ,否则调用 walk
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    
    // 循环定义响应式
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

walk方法中遍历的调用defineReactive,该方法主要给value添加getter 和 setter,实现响应式

// src/core/observer/index.js

export function defineReactive (
  obj: Object, // 目标对象
  key: string, // 对象属性
  val: any, // 对象值
  customSetter?: ?Function, // 自定义setter
  shallow?: boolean
) {
  
  // 创建 Dep
  const dep = new Dep()
  
  // 获取obj的属性描述符
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // 属性不可配置则直接return
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 获取已经定义的set和get
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  
  // 嵌套的对象的 Observer
  let childOb = !shallow && observe(val)
  
  // 通过 Object.defineProperty 给 obj 添加 key 属性
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      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
    },
    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()
    }
  })
}

上述代码中,我们知道了如何去给Vue实例中的 data 实现响应式,但其中遇到了Dep,它是一个类,主要用来处理依赖收集。

// src/core/observer/dep.js

import type Watcher from './watcher'
import { remove } from '../util/index'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher; // 全局唯一的 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)
    }
  }
	// 通知订阅者去更新,即执行订阅者的 update 方法
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
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()
}

可以看到 Dep(dependency) 主要是对 Watcher 进行管理

export default class Watcher {
  vm: Component; // 
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  computed: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  dep: Dep;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.computed = !!options.computed
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.computed = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.computed // for computed 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)
      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
        )
      }
    }
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () { ... }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) { ... }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () { ... }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () { ... }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () { ... }

  getAndInvoke (cb: Function) { ... }

  /**
   * Evaluate and return the value of the watcher.
   * This only gets called for computed property watchers.
   */
  evaluate () { ... }

  /**
   * Depend on this watcher. Only for computed property watchers.
   */
  depend () { ... }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () { ... }
}