资讯专栏INFORMATION COLUMN

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

Godtoy / 3249人阅读

摘要:实现了一个简单的订阅观察者类,这个类被用于在数据修改时通知各个以触发对应的更新,从而实现数据的响应,这个会在后续的数据响应化里提到。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85216.html

相关文章

  • 从Vue.js源码角度再看数据绑定

    摘要:在学习过程中,为加上了中文的注释,希望可以对其他想学习源码的小伙伴有所帮助。数据绑定原理前面已经讲过数据绑定的原理了,现在从源码来看一下数据绑定在中是如何实现的。 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。文章的原地址:https://github.com/answershuto/le...

    elina 评论0 收藏0
  • VueJS源码学习——MutationObserver实现nextTick

    摘要:倡导开发者尽量不直接操作,但有的时候由于各种需求让开发者不得不这样做,于是的实现就是让开发者在修改数据后,能够在数据更新到后才执行对应的函数,从而获取最新的数据。 Vue 倡导开发者尽量不直接操作 DOM,但有的时候由于各种需求让开发者不得不这样做,于是 nextTick 的实现就是让开发者在修改数据后,能够在数据更新到 DOM 后才执行对应的函数,从而获取最新的 DON 数据。 原文...

    xumenger 评论0 收藏0
  • VueJS源码学习——项目结构&目录

    摘要:所以整个的核心,就是如何实现这三样东西以上摘自囧克斯博客的一篇文章从版本开始这个时候的项目结构如下源码在里面,为打包编译的代码,为打包后代码放置的位置,为测试代码目录。节点类型摘自资源另一位作者关于源码解析 本项目的源码学习笔记是基于 Vue 1.0.9 版本的也就是最早的 tag 版本,之所以选择这个版本,是因为这个是最原始没有太多功能拓展的版本,有利于更好的看到 Vue 最开始的骨...

    ad6623 评论0 收藏0
  • RxJava2.x源码解析(一):订阅流程

    摘要:现在网上已经有大量的源码分析文章,各种技术的都有。你完全可以写成下面的链式风格方法会最先被执行同样,为了便于理解,我会借用流里面经常用到的水流进行类比。该子类的命名是有规律可言的。现在网上已经有大量的源码分析文章,各种技术的都有。但我觉得很多文章对初学者并不友好,让人读起来云里雾里的,比源码还源码。究其原因,是根本没有从学习者的角度去分析。在自己完成了源码阅读之后,却忘记了自己是如何一步步提...

    harryhappy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<