VueJS源码学习——订阅观察者类

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

你可能感兴趣的:(前端,javascript,vue.js)