vue源碼之響應式數據

<p>分析vue是如何實現數據響應的.</p> <h2>前記</h2> <p>如今回顧一下看數據響應的緣由. 以前看了vuex和vue-i18n的源碼, 他們都有本身內部的vm, 也就是vue實例. 使用的都是vue的響應式數據特性及<code>$watch</code>api. 因此決定看一下vue的源碼, 瞭解vue是如何實現響應式數據.</p> <p>本文敘事方式爲樹藤摸瓜, 順着看源碼的邏輯走一遍, 查看的vue的版本爲2.5.2.</p> <h2>目的</h2> <p>明確調查方向才能直至目標, 先說一下目標行爲:</p> <ol> <li>vue中的數據改變, 視圖層面就能得到到通知並進行渲染.</li> <li> <code>$watch</code>api監聽表達式的值, 在表達式中任何一個元素變化之後得到通知並執行回調.</li> </ol> <p>那麼準備開始以這個方向爲目標從vue源碼的入口開始找答案.</p> <h2>入口開始</h2> <p>來到<code>src/core/index.js</code>, 調了<code>initGlobalAPI()</code>, 其餘代碼是ssr相關, 暫不關心.</p> <p>進入<code>initGlobalAPI</code>方法, 作了一些暴露全局屬性和方法的事情, 最後有4個init, initUse是Vue的install方法, 前面vuex和vue-i18n的源碼分析已經分析過了. initMixin是咱們要深刻的部分.</p> <p>在<code>initMixin</code>前面部分依舊作了一些變量的處理, 具體的init動做爲:</p>html

vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

<p>vue啓動的順序已經看到了: 加載生命週期/時間/渲染的方法 =&gt; beforeCreate鉤子 =&gt; 調用injection =&gt; 初始化state =&gt; 調用provide =&gt; created鉤子.</p> <p>injection和provide都是比較新的api, 我還沒用過. 咱們要研究的東西在initState中.</p> <p>來到initState:</p>vue

export function initState (vm: Component) {
  vm._watchers = []
  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 */) // 若是沒有data, _data效果同樣, 只是沒作代理
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch &amp;&amp; opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

<p>作的事情很簡單: 若是有props就處理props, 有methods就處理methods, …, 咱們直接看<code>initData(vm)</code>.</p> <h2>initData</h2> <p>initData作了兩件事: proxy, observe.</p> <p>先貼代碼, 前面作了小的事情寫在註釋裏了.</p>react

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function' // 若是data是函數, 用vm做爲this執行函數的結果做爲data
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) { // 過濾亂搞, data只接受對象, 若是亂搞會報警而且把data認爲是空對象
    data = {}
    process.env.NODE_ENV !== 'production' &amp;&amp; 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
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) { // 遍歷data
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods &amp;&amp; hasOwn(methods, key)) { // 判斷是否和methods重名
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props &amp;&amp; hasOwn(props, key)) { // 判斷是否和props重名
      process.env.NODE_ENV !== 'production' &amp;&amp; warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) { // 判斷key是否以_或$開頭
      proxy(vm, `_data`, key) // 代理data
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

<p>咱們來看一下proxy和observe是幹嗎的.</p> <p>proxy的參數: vue實例, <code>_data</code>, 鍵.</p> <p>做用: 把vm.key的setter和getter都代理到vm._data.key, 效果就是vm.a實際實際是vm._data.a, 設置vm.a也是設置vm._data.a.</p> <p>代碼是:</p>vuex

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
  // 在initData中調用: proxy(vm, `_data`, key)
  // target: vm, sourceKey: _data, key: key. 這裏的key爲遍歷data的key
  // 舉例: data爲{a: 'a value', b: 'b value'}
  // 那麼這裏執行的target: vm, sourceKey: _data, key: a
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key] // getter: vm._data.a
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val // setter: vm._data.a = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition) // 用Object.defineProperty來設置getter, setter
  // 第一個參數是vm, 也就是獲取`vm.a`就獲取到了`vm._data.a`, 設置也是如此.
}

<p>代理完成以後是本文的核心, initData最後調用了<code>observe(data, true)</code>,來實現數據的響應.</p> <h2>observe</h2> <p>observe方法實際上是一個濾空和單例的入口, 最後行爲是建立一個observe對象放到observe目標的<code>__ob__</code>屬性裏, 代碼以下:</p>express

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) { // 只能是監察對象, 過濾非法參數
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') &amp;&amp; value.__ob__ instanceof Observer) {
    ob = value.__ob__ // 若是已被監察過, 返回存在的監察對象
  } else if ( // 符合下面條件就新建一個監察對象, 若是不符合就返回undefined
    observerState.shouldConvert &amp;&amp;
    !isServerRendering() &amp;&amp;
    (Array.isArray(value) || isPlainObject(value)) &amp;&amp;
    Object.isExtensible(value) &amp;&amp;
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData &amp;&amp; ob) {
    ob.vmCount++
  }
  return ob
}

<p>那麼關鍵是<code>new Observer(value)</code>了, 趕忙跳到Observe這個類看看是如何構造的.</p> <p>如下是Observer的構造函數:</p>segmentfault

constructor (value: any) {
    this.value = value // 保存值
    this.dep = new Dep() // dep對象
    this.vmCount = 0
    def(value, '__ob__', this) // 本身的副本, 放到__ob__屬性下, 做爲單例依據的緩存
    if (Array.isArray(value)) { // 判斷是否爲數組, 若是是數組的話劫持一些數組的方法, 在調用這些方法的時候進行通知.
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value) // 遍歷數組, 繼續監察數組的每一個元素
    } else {
      this.walk(value) // 直到再也不是數組(是對象了), 遍歷對象, 劫持每一個對象來發出通知
    }
  }

<p>作了幾件事:</p> <ul> <li>創建內部Dep對象. (做用是以後在watcher中遞歸的時候把本身添加到依賴中)</li> <li>把目標的<code>__ob__</code>屬性賦值成Observe對象, 做用是上面提過的單例.</li> <li>若是目標是數組, 進行方法的劫持. (下面來看)</li> <li>若是是數組就observeArray, 不然walk.</li> </ul> <p>那麼咱們來看看observeArray和walk方法.</p>api

/**
   * 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 &lt; keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]]) // 用'obj[keys[i]]'這種方式是爲了在函數中直接給這個賦值就好了
    }
  }

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

<p>咱們發現, observeArray的做用是遞歸調用, 最後調用的方法是<strong><code>defineReactive</code></strong>, 能夠說這個方法是最終的核心了.</p> <p>下面咱們先看一下數組方法劫持的目的和方法, 以後再看<code>defineReactive</code>的作法.</p> <h2>array劫持</h2> <p>以後會知道defineReactive的實現劫持的方法是<code>Object.defineProperty</code>來劫持對象的getter, setter, 那麼數組的變化不會觸發這些劫持器, 因此vue劫持了數組的一些方法, 代碼比較零散就不貼了. </p> <p>最後的結果就是: array.prototype.push = function () {…}, 被劫持的方法有<code>['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']</code>, 也就是調用這些方法也會觸發響應. 具體劫持之後的方法是:</p>數組

def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args) // 調用原生的數組方法
    const ob = this.__ob__ // 獲取observe對象
    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() // 出發notify
    return result
  })

<p>作了兩件事:</p> <ol> <li>遞歸調用</li> <li>觸發所屬Dep的<code>notify()</code>方法.</li> </ol> <p>接下來就說最終的核心方法, defineReactive, 這個方法最後也調用了notify().</p> <h2>defineReactive</h2> <p>這裏先貼整個代碼:</p>緩存

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  // 這個方法是劫持對象key的動做
  // 這裏仍是舉例: 對象爲 {a: 'value a', b: 'value b'}, 當前遍歷到a
  obj: Object, // {a: 'value a', b: 'value b'}
  key: string, // a
  val: any, // value a
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property &amp;&amp; property.configurable === false) { // 判斷當前key的操做權限
    return
  }

  // cater for pre-defined getter/setters
  // 獲取對象原本的getter setter
  const getter = property &amp;&amp; property.get
  const setter = property &amp;&amp; property.set

  let childOb = !shallow &amp;&amp; observe(val) // childOb是val的監察對象(就是new Observe(val), 也就是遞歸調用)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val // 若是自己有getter, 先調用
      if (Dep.target) { // 若是有dep.target, 進行一些處理, 最後返回value, if裏的代碼咱們以後去dep的代碼中研究
        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 // 若是自己有getter, 先調用
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal &amp;&amp; value !== value)) { // 若是值不變就不去作通知了, (或是某個值爲Nan?)
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' &amp;&amp; customSetter) {
        customSetter() // 根據"生產環境不執行"這個行爲來看, 這個方法可能做用是log, 多是保留方法, 還沒地方用?
      }
      if (setter) { // 若是自己有setter, 先調用, 沒的話就直接賦值
        setter.call(obj, newVal)
      } else {
        val = newVal // 由於傳入參數的時候實際上是'obj[keys[i]]', 因此就等因而'obj[key] = newVal'了
      }
      childOb = !shallow &amp;&amp; observe(newVal) // 從新創建子監察
      dep.notify() // 通知, 能夠說是劫持的核心步驟
    }
  })
}

<p>解釋都在註釋中了, 總結一下這個方法的作的幾件重要的事:</p> <ul> <li>創建Dep對象. (下面會說調用的Dep的方法的具體做用)</li> <li>遞歸調用. 能夠說很大部分代碼都在遞歸調用, 分別在建立子observe對象, setter, getter中.</li> <li>getter中: 調用原來的getter, 收集依賴(Dep.depend(), 以後會解釋收集的原理), 一樣也是遞歸收集.</li> <li>setter中: 調用原來的setter, 並判斷是否須要通知, 最後調用<code>dep.notify()</code>.</li> </ul> <p>總結一下, 總的來講就是, 進入傳入的data數據會被劫持, 在get的時候調用<code>Dep.depend()</code>, 在set的時候調用<code>Dep.notify()</code>. 那麼Dep是什麼, 這兩個方法又幹了什麼, 帶着疑問去看Dep對象.</p> <h2>Dep</h2> <p>Dep應該是dependencies的意思. dep.js整個文件只有62行, 因此貼一下:</p>app

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array&lt;Watcher&gt;;

  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 &lt; 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的get方法同時調用
Dep.target = null
const targetStack = []

export function pushTarget (_target: Watcher) {
  // 設置target, 把舊的放進stack
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  // 從stack拿一個做爲當前的
  Dep.target = targetStack.pop()
}

<p>首先來分析變量:</p> <ul> <li>全局Target. 這個實際上是用來跟watcher交互的, 也保證了普通get的時候沒有target就不設置依賴, 後面會解釋.</li> <li>id. 這是用來在watcher裏依賴去重的, 也要到後面解釋.</li> <li>subs: 是一個watcher數組. sub應該是subscribe的意思, 也就是當前dep(依賴)的訂閱者列表.</li> </ul> <p>再來看方法:</p> <ul> <li>構造: 設uid, subs. addSub: 添加wathcer, removeSub: 移除watcher. 這3個好無聊.</li> <li>depend: 若是有Dep.target, 就把本身添加到Dep.target中(調用了<code>Dep.target.addDep(this)</code>).<p>那麼何時有Dep.target呢, 就由<code>pushTarget()</code>和<code>popTarget()</code>來操做了, 這些方法在Dep中沒有調用, 後面會分析是誰在操做Dep.target.(這個是重點)</p> </li> <li>notify: 這個是setter劫持之後調用的最終方法, 作了什麼: 把當前Dep訂閱中的每一個watcher都調用<code>update()</code>方法.</li> </ul> <p>Dep看完了, 咱們的疑問都轉向了Watcher對象了. 如今看來有點糊塗, 看完Watcher就都明白了.</p> <h2>Watcher</h2> <p>watcher很是大(並且打watcher這個單詞也很是容易手誤, 心煩), 咱們先從構造看起:</p>

constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm // 保存vm
    vm._watchers.push(this) // 把watcher存到vm裏
    // 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
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    // 設置getter, parse字符串, 並濾空濾錯
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' &amp;&amp; warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    // 調用get得到值
    this.value = this.lazy
      ? undefined
      : this.get()
  }

<p>註釋都寫了, 我來高度總結一下構造器作了什麼事:</p> <ul> <li>處理傳入的參數並設置成本身的屬性.</li> <li>parse表達式. watcher表達式接受2種: 方法/字符串. 若是是方法就設爲getter, 若是是字符串會進行處理:</li> </ul>

/**
   * Parse simple path.
   */
  const bailRE = /[^\w.$]/
  export function parsePath (path: string): any {
    if (bailRE.test(path)) {
      return
    }
    const segments = path.split('.')
    // 這裏是vue如何分析watch的, 就是接受 '.' 分隔的變量.
    // 若是鍵是'a.b.c', 也就等於function () {return this.a.b.c}
    return function (obj) {
      for (let i = 0; i &lt; segments.length; i++) {
        if (!obj) return
        obj = obj[segments[i]]
      }
      return obj
    }
  }

<p>處理的效果寫在上面代碼的註釋裏.</p> <ul><li>調用<code>get()</code>方法.</li></ul> <p>下面說一下get方法. <strong>get()方法是核心, 看完了就能把以前的碎片都串起來了</strong>. 貼get()的代碼:</p>

/**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    // 進入隊列, 把當前watcher設置爲Dep.target
    // 這樣下面調用getter的時候出發的dep.append() (最後調用Dep.target.addDep()) 就會調用這個watcher的addDep.
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
      // 調用getter的時候會走一遍表達式,
      // 若是是 this.a + this.b , 會在a和b的getter中調用Dep.target.addDep(), 最後結果就調用了當前watcher的addDep,
      // 當前watcher就有了this.a的dep和this.b的dep
      // addDep把當前watcher加入了dep的sub(subscribe)裏, dep的notify()調用就會運行本watcher的run()方法.
    } 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
      // 走到這裏已經經過了getter得到到了value, 或者失敗爲undefined, 這個值返回做爲watcher的valule
      // 處理deep選項 (待看)
      if (this.deep) {
        traverse(value)
      }
      popTarget() // 移除隊列
      this.cleanupDeps() // 清理依賴(addDep加到newDep數組, 這步作整理動做)
    }
    return value
  }

<p>註釋都在代碼中了, 這段理解了就對整個響應系統理解了. </p> <p>我來總結一下: (核心, 很是重要)</p> <ul> <li><strong>dep方面: 傳入vue參數的data(實際是全部調用<code>defineReactive</code>的屬性)都會產生本身的Dep對象.</strong></li> <li><strong>Watcher方面: 在全部new Watcher的地方產生Watcher對象.</strong></li> <li> <strong>dep與Watcher關係: Watcher的get方法創建了雙方關係:</strong><p><strong>把本身設爲target, 運行watcher的表達式(即調用相關數據的getter), 由於getter有鉤子, 調用了Watcher的addDep, addDep方法把dep和Watcher互相推入互相的屬性數組(分別是deps和subs)</strong></p> </li> <li><strong>dep與Watcher創建了多對多的關係: dep含有訂閱的watcher的數組, watcher含有所依賴的變量的數組</strong></li> <li> <strong>當dep的數據調動setter, 調用notify, 最終調用Watcher的update方法</strong>.</li> <li><strong>前面提到dep與Watcher創建關係是經過<code>get()</code>方法, 這個方法在3個地方出現: 構造方法, run方法, evaluate方法. 也就是說, notify了之後會從新調用一次get()方法. (因此在lifycycle中調用的時候把依賴和觸發方法都寫到getter方法中了). </strong></li> </ul> <p>那麼接下來要看一看watcher在什麼地方調用的.</p> <p>找了一下, 一共三處:</p> <ul> <li> <p>initComputed的時候: (state.js)</p>

watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )

</li> <li> <p>$watch api: (state.js)</p>

new Watcher(vm, expOrFn, cb, options)

</li> <li> <p>lifecycle的mount階段: (lifecycle.js)</p>

new Watcher(vm, updateComponent, noop)

</li> </ul> <h2>總結</h2> <p>看完源碼就不神祕了, 寫得也算很清楚了. 固然還有不少細節沒寫, 由於衝着目標來.</p> <p>總結其實都在上一節的粗體裏了.</p> <h2>甜點</h2> <p>咱們只從data看了, 那麼props和computed應該也是這樣的, 由於props應該與組建相關, 下回分解吧, 咱們來看看computed是咋回事吧.</p>

const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()
  for (const key in computed) {
    // 循環每一個computed
    // ------------
    // 格式濾錯濾空
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' &amp;&amp; getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      // 爲computed創建wathcer
      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.
    // 由於沒有被代理, computed屬性是不能經過vm.xx得到的, 若是能夠得到說明重複定義, 拋出異常.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props &amp;&amp; key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

<p>已註釋, 總結爲:</p> <ul> <li>遍歷每一個computed鍵值, 過濾錯誤語法.</li> <li>遍歷每一個computed鍵值, 爲他們創建watcher, options爲<code>{ lazy: true}</code>.</li> <li>遍歷每一個computed鍵值, 調用defineComputed.</li> </ul> <p>那麼繼續看defineComputed.</p>

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  // 由於computed除了function還有get set 字段的語法, 下面的代碼是作api的兼容
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache &amp;&amp; userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  // 除非設置setter, computed屬性是不能被修改的, 拋出異常 (evan說改變了自由哲學, 要控制低級用戶)
  if (process.env.NODE_ENV !== 'production' &amp;&amp;
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 其實核心就下面這步... 上面步驟的做用是和data同樣添加一個getter, 增長append動做. 如今經過vm.xxx能夠獲取到computed屬性啦!
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers &amp;&amp; this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

<p>由於computed能夠設置getter, setter, 因此computed的值不必定是function, 能夠爲set和get的function, 很大部分代碼是作這些處理, 核心的事情有2件:</p> <ul> <li>使用Object.defineProperty在vm上掛載computed屬性.</li> <li>爲屬性設置getter, getter作了和data同樣的事: depend. 可是多了一步: <code>watcher.evalueate()</code>.</li> </ul> <p>看到這裏, computed註冊核心一共作了兩件事:</p> <ol> <li>爲每一個computed創建watcher(lazy: true)</li> <li>創建一個getter來depend, 並掛到vm上.</li> </ol> <p>那麼dirty成了疑問, 咱們回到watcher的代碼中去看, lazy和dirty和evaluate是幹什麼的.</p> <p>精選相關代碼:</p> <ul> <li>(構造函數中) <code>this.dirty = this.lazy</code> </li> <li>(構造函數中) <code>this.value = this.lazy ? undefined : this.get()</code> </li> <li> <p>(evaluate函數)</p>

evaluate () {
    this.value = this.get()
    this.dirty = false
  }

</li> </ul> <p>到這裏已經很清楚了. 由於還沒設置getter, 因此在創建watcher的時候不當即調用getter, 因此構造函數沒有立刻調用get, 在設置好getter之後調用evaluate來進行依賴註冊.</p> <p>總結: computed是watch+把屬性掛到vm上的行爲組合.</p>

原文地址:http://www.javashuo.com/article/p-ktygvwog-bd.html

相關文章
相關標籤/搜索