VueJS原始碼學習——訂閱觀察者類

chenjsh36發表於2017-09-04

observer 實現了一個簡單的訂閱觀察者類,這個類被用於在資料修改時通知各個 watcher 以觸發對應的更新,從而實現資料的響應,這個會在後續的資料響應化裡提到。

原文地址
專案地址

src/observer

src/observer/dep.js

depwatcher 引用

A dep is an observable that can have multiple

directives subscribing to it.
(dep 是一個可以被多個指令訂閱的觀察者)

// src/observer/dep.js

import { toArray } from `../util/index`

let uid = 0

export default function Dep () {
  this.id = uid++
  this.subs = []
}

Dep.target = null

Dep.prototype.addSub = function (sub) {
  this.subs.push(sub)
}

Dep.prototype.removeSub = function (sub) {
  this.subs.$remove(sub)
}

Dep.prototype.depend = function () {
  Dep.target.addDep(this)
}

Dep.prototype.notify = function () {
  var subs = toArray(this.subs)
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

程式碼量很少,但是幾個點暫時不明,一個是 this.subs.$remove(sub), 貌似是給陣列新增了一個 remove 方法, target 擁有 addDep 方法, subs的 item 擁有 update 方法

更正,dep 類用來做依賴採集,是一個簡單的訂閱-觀察者模式,而 target 和 subs 用於存放 watcher,dep 類用於資料響應化的實現,在後續會講到這個

src/observer/array.js

實現了 arrayMethods 類,該類繼承了 array 物件:

...
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
...

同時還對 array 擴充了 $set $remove 方法, 這就是在 dep.js 裡使用到的 $remove 方法


def(
  arrayProto,
  `$set`,
  function $set (index, val) {
    if (index >= this.length) {
      this.length = index + 1
    }
    return this.splice(index, 1, val)[0]
  }
)
def(
  arrayProto,
  `$remove`,
  function $remove (item) {
    /* istanbul ignore if */
    if (!this.length) return
    var index = indexOf(this, item)
    if (index > -1) {
      return this.splice(index, 1)
    }
  }
)

def 方法的實現:

export function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

除此之外, arrayMethods 重寫了

`push`,
`pop`,
`shift`,
`unshift`,
`splice`,
`sort`,
`reverse`

這些原型方法:

;[
  `push`,
  `pop`,
  `shift`,
  `unshift`,
  `splice`,
  `sort`,
  `reverse`
]
.forEach(function (method) {
  // cache original method
  var original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments: 避免洩露引數 arguments
    // http://jsperf.com/closure-with-arguments
    var i = arguments.length
    var args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    var result = original.apply(this, args)
    var ob = this.__ob__
    var inserted
    switch (method) {
      case `push`:
        inserted = args
        break
      case `unshift`:
        inserted = args
        break
      case `splice`:
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

通過改寫陣列的幾個原型方法,從而能夠在開發者操作 data 陣列時,能夠觸發更新事件: ob.dep.notify()

src/observer/index.js

index.js 實現了 Observer 類, 觀察者類繫結了每一個被觀察的物件,一旦繫結,Observer 類會將目標物件的屬性property keys 轉化為 getter/setters, 以收集依賴關係和分派更新

/**
 * Observer class that are attached to each observed
 * object. Once attached, the observer converts target
 * object`s property keys into getter/setters that
 * collect dependencies and dispatches updates.
 *
 * @param {Array|Object} value
 * @constructor
 */
export function Observer (value) {
  this.value = value
  this.dep = new Dep()
  def(value, `__ob__`, this)
  if (isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys)
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}
...

從建構函式可以知道之前程式碼裡一直出現的 _ob_ 屬性就是指 Observer 類

其中 hasProto 實現在 util/env 中:

export const hasProto = `__proto__` in {}

實現的原理是利用 Object 的 Geeter 和 Setter:


/**
 * Define a reactive property on an Object.
 *
 * @param {Object} obj
 * @param {String} key
 * @param {*} val
 */

export function defineReactive (obj, key, val) {
  var dep = new Dep()

  // cater for pre-defined getter/setters
  var getter, setter
  if (config.convertAllProperties) {
    var property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
      return
    }
    getter = property && property.get
    setter = property && property.set
  }

  var childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
        if (isArray(value)) {
          for (var e, i = 0, l = value.length; i < l; i++) {
            e = value[i]
            e && e.__ob__ && e.__ob__.dep.depend()
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val
      if (newVal === value) {
        return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify()
    }
  })
}

相關文章