Vue 中对数据的响应式,即对 propsdatacomputed 的变化进行响应式更改。

其入口是在 src/core/instance/init.js 里的 initState(vm) 里进行实现的。

initState(vm) 里 初始化了 props, methods, data, computed, watch

initData

initData 入手来看下:

function initData (vm: Component) {
  let data = vm.$options.data
  // 将_data作为中间变量,后续 proxy 里会用到
  data = vm._data = typeof data === 'function'
    ? getData(data, vm) // getData 做了错误处理,并控制了 Dep.target,使得避免在获取 data 初始值的过程中意外地把依赖记录下来。
    : data || {}
  // 省略相关 warn 代码...
  // 进行 data proxy methods 重复 key 的检验并抛出 warn msg
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    // 省略相关关于重复 key 检测 warn 代码
    // 对 _data 对象上的每个 key 进行 getter setter 设置
    // 使得访问 this.xxx 代理到 this._data.xxx
    proxy(vm, `_data`, key)
  }
  // observe data
  /**
   * 响应式核心方法
   * 即 new Observer(value)
   * 具体看 new Observer(value) 逻辑
   * */
  observe(data, true /* asRootData */)
}

initData 里首先将原始 data 复制了份 _data,然后遍历了每个 key, 通过 proxy 方法对 _data 里的 key 进行了 getter setter 设置。

使得访问 this.xxx 代理到 this._data.xxx。

接下来执行 observe(data, true /* asRootData */)

该方法结果是 return new Observer(value),返回了一个 Observer 实例。

Observer

来看下 Observer 类的定义,这也是响应式实现的主要地方。

class Observer 源码
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 // 添加 ob 属性,值为自身,即 this.data.ob = this def(value, ob, this) if (Array.isArray(value)) { // 对数组原生方法进行了劫持,使得push 等能监控到,来触发响应式,但无法监控到 通过下标更改的情况 if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } // 对数组进行遍历执行 observe this.observeArray(value) } else { // 会进入 walk 方法 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 方法,设置 getter setter, 真正的响应代码 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]) } } }

Observer 在 new 的时候,主要逻辑会先执行 this.dep = new Dep() 来创建依赖实例,然后处理数组/对象的情况。

这中间还对里面的每个元素进行了深度遍历,使得每个元素均是响应式的。

再主要来看 this.walk(value) 方法,该方法通过 defineReactive 方法,设置 getter setter,这是真正的响应代码。

defineReactive 源码
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 缓存依赖
  const dep = new Dep()

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

// cater for pre-defined getter/setters // 这里代码类比 computed 里属性定义的 getter setter 那样 // 定义了,就用定义的 getter setter 方法 const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // childObserve 对象,对 child 进行监听 let childOb = !shallow && observe(val) 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 }, // 这步触发依赖并设置值 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 // 自己定义了 setter 就调用自己的 setter, 没有就直接赋值 if (setter) { setter.call(obj, newVal) } else { val = newVal } // newVal 是个 object,则继续递归执行监听 observe childOb = !shallow && observe(newVal) // 触发更新 dep.notify() } }) }

defineReactive 代码可以简化如下:

_defineReactive (obj, key, val) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    configurable: true,
    enumerable: true,
    get: function reactiveGetter() {
      dep.addSub(Dep.target)
      return val
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value) return
      // 自己定义了 setter 就调用自己的 setter, 没有就直接赋值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      dep.notify()
    }
  })
}

defineReactive 函数内,通过闭包实例化了 Dep 这个订阅者。

Dep

class Dep 源码
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 即 watcher:watcher.addDep(dep) Dep.target.addDep(this) } }

notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== ‘production’ && !config.async) { // subs aren’t sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } 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. // 保存当前全局唯一存在的 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() }

Dep 这里可以简化为:

class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

Dep.target = null

Dep 这个依赖订阅者,有自己的 subs 用来缓存 Dep.targetDep.target 其实是一个 watcher 实例,这里先不管 watcher。 回到 defineReactive,在 get 里将 watcher 实例添加到订阅者 Dep 里, 在 set 时遍历缓存在 dep.subs 数组里的 watcher 实例,执行 watcher.update() 方法,来触发更新。

Watcher

来看下 class Watcher

class Watcher 源码
  export default class Watcher {
    vm: Component; // 实例自身
    expression: string;
    cb: Function;
    id: number;
    deep: boolean;
    user: boolean;
    lazy: boolean;
    sync: boolean;
    dirty: boolean;
    active: boolean;
    deps: Array<Dep>;
    newDeps: Array<Dep>;
    depIds: SimpleSet; // ES6 set 类型
    newDepIds: SimpleSet; // ES6 set 类型
    before: ?Function;
    getter: Function;
    value: any;
<span class="token function">constructor</span> <span class="token punctuation">(</span>
  vm<span class="token operator">:</span> Component<span class="token punctuation">,</span>
  expOrFn<span class="token operator">:</span> string <span class="token operator">|</span> Function<span class="token punctuation">,</span> <span class="token comment">// 表达式本身 [ getter | noop ]</span>
  cb<span class="token operator">:</span> Function<span class="token punctuation">,</span> <span class="token comment">// [ noop ]</span>
  options<span class="token operator">?</span><span class="token operator">:</span> <span class="token operator">?</span>Object<span class="token punctuation">,</span> <span class="token comment">// { lazy: true } // 如果设置为 true 则在第一次 get 的时候才计算值,初始化的时候并不计算。init 时为 true</span>
  isRenderWatcher<span class="token operator">?</span><span class="token operator">:</span> boolean
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>vm <span class="token operator">=</span> vm
  <span class="token keyword">if</span> <span class="token punctuation">(</span>isRenderWatcher<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    vm<span class="token punctuation">.</span>_watcher <span class="token operator">=</span> <span class="token keyword">this</span>
  <span class="token punctuation">}</span>
  vm<span class="token punctuation">.</span>_watchers<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
  <span class="token comment">// options</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>deep <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>deep
    <span class="token keyword">this</span><span class="token punctuation">.</span>user <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>user
    <span class="token keyword">this</span><span class="token punctuation">.</span>lazy <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>lazy
    <span class="token keyword">this</span><span class="token punctuation">.</span>sync <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>options<span class="token punctuation">.</span>sync
    <span class="token comment">/*
    before作用是定义 beforeUpdate 钩子:
    「
      before () {
          if (vm._isMounted) {
            callHook(vm, 'beforeUpdate')
          }
        }
      」
    */</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>before <span class="token operator">=</span> options<span class="token punctuation">.</span>before
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>deep <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>user <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lazy <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sync <span class="token operator">=</span> <span class="token boolean">false</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>cb <span class="token operator">=</span> cb
  <span class="token keyword">this</span><span class="token punctuation">.</span>id <span class="token operator">=</span> <span class="token operator">++</span>uid <span class="token comment">// uid for batching</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>active <span class="token operator">=</span> <span class="token boolean">true</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>dirty <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lazy <span class="token comment">// for lazy watchers</span>
  <span class="token comment">// 两个数组,</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>deps <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>newDeps <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token comment">// 两个id 为 set 实例,在 add 时候,防止重复添加相同 id</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>depIds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>expression <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span>
    <span class="token operator">?</span> expOrFn<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token operator">:</span> <span class="token string">''</span>
  <span class="token comment">// parse expression for getter</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> expOrFn <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>getter <span class="token operator">=</span> expOrFn
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>getter <span class="token operator">=</span> <span class="token function">parsePath</span><span class="token punctuation">(</span>expOrFn<span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>getter<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>getter <span class="token operator">=</span> noop
      process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span> <span class="token operator">!==</span> <span class="token string">'production'</span> <span class="token operator">&amp;&amp;</span> <span class="token function">warn</span><span class="token punctuation">(</span>
        <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Failed watching path: "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>expOrFn<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" </span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span>
        <span class="token string">'Watcher only accepts simple dot-delimited paths. '</span> <span class="token operator">+</span>
        <span class="token string">'For full control, use a function instead.'</span><span class="token punctuation">,</span>
        vm
      <span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lazy
    <span class="token operator">?</span> <span class="token keyword">undefined</span>
    <span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Evaluate the getter, and re-collect dependencies.
 */</span>
<span class="token comment">// 执行 getter, 并重新收集依赖关系</span>
<span class="token function">get</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">/* pushTarget 代码:
  「
      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()
      }
    」
  */</span>
  <span class="token comment">// 这行代码作用:在进行 get 取值时,使得 Dep.target 为 watcher 实例自身</span>
  <span class="token function">pushTarget</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
  <span class="token keyword">let</span> value
  <span class="token keyword">const</span> vm <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>vm
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getter</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>vm<span class="token punctuation">,</span> vm<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">handleError</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> vm<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">getter for watcher "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>expression<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> e
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
    <span class="token comment">// "touch" every property so they are all tracked as</span>
    <span class="token comment">// dependencies for deep watching</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>deep<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">traverse</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <span class="token function">popTarget</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cleanupDeps</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> value
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Add a dependency to this directive.
 */</span>
<span class="token function">addDep</span> <span class="token punctuation">(</span><span class="token parameter">dep<span class="token operator">:</span> Dep</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> id <span class="token operator">=</span> dep<span class="token punctuation">.</span>id
  <span class="token comment">// if (!this.newDepIds.has(id)) 判断,为了防止如 computed 里</span>
  <span class="token comment">// `return this.a + this.a` 这种相同值的计算情况</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>newDeps<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>dep<span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>depIds<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      dep<span class="token punctuation">.</span><span class="token function">addSub</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Clean up for dependency collection.
 */</span>
<span class="token function">cleanupDeps</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">.</span>length
  <span class="token keyword">while</span> <span class="token punctuation">(</span>i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> dep <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">[</span>i<span class="token punctuation">]</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>dep<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      dep<span class="token punctuation">.</span><span class="token function">removeSub</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">let</span> tmp <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>depIds
  <span class="token keyword">this</span><span class="token punctuation">.</span>depIds <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds
  <span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds <span class="token operator">=</span> tmp
  <span class="token keyword">this</span><span class="token punctuation">.</span>newDepIds<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  tmp <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps
  <span class="token keyword">this</span><span class="token punctuation">.</span>deps <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>newDeps
  <span class="token keyword">this</span><span class="token punctuation">.</span>newDeps <span class="token operator">=</span> tmp
  <span class="token keyword">this</span><span class="token punctuation">.</span>newDeps<span class="token punctuation">.</span>length <span class="token operator">=</span> <span class="token number">0</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Subscriber interface.
 * Will be called when a dependency changes.
 */</span>
<span class="token function">update</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">/* istanbul ignore else */</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>lazy<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>dirty <span class="token operator">=</span> <span class="token boolean">true</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sync<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token function">queueWatcher</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Scheduler job interface.
 * Will be called by the scheduler.
 */</span>
<span class="token function">run</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>active<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>
      value <span class="token operator">!==</span> <span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">||</span>
      <span class="token comment">// Deep watchers and watchers on Object/Arrays should fire even</span>
      <span class="token comment">// when the value is the same, because the value may</span>
      <span class="token comment">// have mutated.</span>
      <span class="token function">isObject</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token operator">||</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>deep
    <span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// set new value</span>
      <span class="token keyword">const</span> oldValue <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>value
      <span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> value
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">try</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cb</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">,</span> value<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span>
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token function">handleError</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">callback for watcher "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>expression<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cb</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">,</span> value<span class="token punctuation">,</span> oldValue<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Evaluate the value of the watcher.
 * This only gets called for lazy watchers.
 */</span>
<span class="token function">evaluate</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span>dirty <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Depend on all deps collected by this watcher.
 */</span>
<span class="token function">depend</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">.</span>length
  <span class="token keyword">while</span> <span class="token punctuation">(</span>i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">depend</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">/**
 * Remove self from all dependencies' subscriber list.
 */</span>
<span class="token function">teardown</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>active<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// remove self from vm's watcher list</span>
    <span class="token comment">// this is a somewhat expensive operation so we skip it</span>
    <span class="token comment">// if the vm is being destroyed.</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">.</span>_isBeingDestroyed<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token function">remove</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>vm<span class="token punctuation">.</span>_watchers<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">.</span>length
    <span class="token keyword">while</span> <span class="token punctuation">(</span>i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>deps<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">removeSub</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>active <span class="token operator">=</span> <span class="token boolean">false</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

上面说到在 set 某个值的时候,实际上会执行 watcher.update() 方法。来看下这个 update 方法:

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

update 方法,同步情况执行 this.run() ,一般情况是异步,会进入到最后个 else 里,执行 queueWatcher(this)

queueWatcher 里主要代码 nextTick(flushSchedulerQueue),它将 watch 队列放到 nextTick 里统一执行,这也使得连续 set 同个数据两次不同值,不会更新两次视图,提升了一定性能。

computed 的原理

注意 watcher 实例化 constructor 最后行有个 this.value:

this.value = this.lazy
  ? undefined
  : this.get()

这个 value,在 initComputed 的时候,会对 computed 里的每个 key new Watcher(...),所以可以说是 computed 的双向绑定实现即是通过 Watcher 来响应变化。

initComputed 源码函数:
const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering()

// 遍历定义在 computed 上的 key for (const key in computed) { const userDef = computed[key] // 处理自定义 getter 情况 const getter = typeof userDef === ‘function’ ? userDef : userDef.get if (process.env.NODE_ENV !== ‘production’ && getter == null) { warn( </span><span class="token string">Getter is missing for computed property "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">".</span><span class="token template-punctuation string">, vm ) } // 非 ssr 情况,对 key 进行 监控,ssr 下就不需要监控了 if (!isSSR) { // create internal watcher for the computed property. // 对每个key 设置 Watcher watchers[key] = new 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)) { // 将 computed 里定义的 key 在实例上添加进行代理,使得可直接通过 this.xx 来访问 defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== ‘production’) { if (key in vm.$data) { warn(</span><span class="token string">The computed property "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" is already defined in data.</span><span class="token template-punctuation string">, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(</span><span class="token string">The computed property "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" is already defined as a prop.</span><span class="token template-punctuation string">, vm) } } } }

从上面传入参数 { lazy: true } 可知,初始化时 value 为 undefineddefineComputed(vm, key, userDef)Object.defineProperty(target, key, sharedPropertyDefinition). 在 sharedPropertyDefinition 里会拿之前 watcher 过的依赖的值:

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
    }
  }
}

所以 computed 中的值在读取的时候,拿到的结果即 watcher.value。

总结下 data 响应式流程:

this.xx 代理到 this._data.xx 然后 _data 在 Observer 里定义过 setter getter,在初始 render 时会收集依赖到闭包的 Dep 依赖实例的数组里,在 setter 时会通知所有依赖即每个 watcher 实例进行 update 更新。

总结下 computed 响应式流程:

this.xx 初始化时会先创建一个 watcher,然后通过 Object.defineProperty 进行 setter,getter 设置,其中 getter 会得到是 watcher.value 的值。

注意:

Observer 文件下定义了个 arrayMethods,该方法重写了数组方法,使得改变 push 等操作也能触发更新。

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
  })
})

附: 实现vue精简版响应式代码