关于前端:学习Vue响应式原理

41次阅读

共计 3230 个字符,预计需要花费 9 分钟才能阅读完成。

本篇文章次要想记录一下自己在学习 vue 中的响应式原理中遇到的一些问题,如有谬误,请大佬们斧正!

1. WatcherDepObserver别离负责什么?

  • Observer负责递归数据及所有子数据,通过 Object.defineProperty 为属性定义 getter/setter,监听数据的变动,将数据定义为响应式。但仅仅是把 Object.defineProperty 封装起来,监听数据的变动,是没有任何作用的,次要的目标是为了 收集依赖

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter() {var value = getter ? getter.call(obj) : val;
          /* 省略 */
          if (Dep.target) { // 收集依赖
              dep.depend();}
          return value
      },
      set: function reactiveSetter(newVal) {
          /* 省略 */
          dep.notify(); // 触发依赖}
    });
    
    // 总的来说就是,在 `getter` 中收集依赖,在 `setter` 中触发依赖。
  • 为什么要收集依赖,是为了数据属性发生变化时,告诉应用了该数据的中央。
  • 依赖收集在哪里?依赖收集在 Dep 中,Dep 用来收集依赖、删除依赖和告诉依赖等。
  • 那么依赖是谁?依赖就是Watcher

为什么须要把依赖封装成 Watcher 类,《深入浅出 vue.js》给出了很好的解释:

当属性发生变化后,咱们要告诉用到数据的中央,而应用这个数据的中央有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个 watch, 这时须要形象出一个能集中处理这些状况的类。而后,咱们在依赖收集阶段只收集这个封装好的类的实例进来,告诉也只告诉它一个。接着,它再负责告诉其余中央。

2. Dep 和 Watcher 的关系?

在我刚开始学习 Vue 源码的时候,对这里也很困惑:

  1. Dep.target是干什么的?

Dep.target 其实就是以后 Watcher 实例,是一个全局惟一的动态变量,读取数据的时候会触发这个数据的 getter,触发了 getter 就会执行

if (Dep.target) {dep.depend();
}

把本人收集到该数据的 Dep 中,大抵代码如下:

 get () {
    /* 将本身 watcher 观察者实例设置给 Dep.target,用以依赖收集。*/
    pushTarget(this)
    let value
    const vm = this.vm

    value = this.getter.call(vm, vm)
    
    /* 将观察者实例从 target 栈中取出,置空 Dep.target*/
    popTarget()
    return value
  }

  /* 将 watcher 观察者实例设置给 Dep.target,用以依赖收集。同时将该实例存入 target 栈中 */
  function pushTarget (_target: Watcher) {if (Dep.target) targetStack.push(Dep.target)
    Dep.target = _target
  }
  1. 为什么 Dep 收集依赖是用 Watcher 中的 addDep 办法,并在 Watcher 中有记录 dep 的 deps 数组?

要答复这个问题,让咱们来举个例子🌰,咱们来看一下 Vue 中的vm.$watch

Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ): Function {const watcher = new Watcher(vm, expOrFn, cb, options)
    /* 省略 */
    return function unwatchFn () {
      /* 将本身从所有依赖收集订阅列表删除 */
      watcher.teardown()}
  }
}

/* Watcher/teardown 大抵代码如下 */
/* 将本身从所有依赖收集订阅列表删除 */
  function teardown () {
    let i = this.deps.length
    while (i--) {this.deps[i].removeSub(this)
    }
    this.active = false
  }

这个办法返回一个 unwatchFn 办法用于勾销察看数据,实质就是要把 Watcher 实例从以后正在察看数据的依赖列表中移除。
如果通过 getter 中的依赖收集,仅仅是把 Watcher 记录到了该数据的 Dep(依赖列表) 中,那么只是在 Dep 中晓得了哪些 Watcher 在监听这个数据,但在 Watcher 中却不晓得本人都订阅了谁 (Watcher 实例被收集进了哪些 Dep 中),不晓得本人订阅了谁,那该告诉谁来勾销察看呢?所以咱们 须要在 Dep 和 Watcher 中都记录一下对方,行成多对多的关系

所以咱们在 Dep 中收集依赖是用Dep.target.addDep(this)

function addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {dep.addSub(this)
    }
  }
}

通过记录 newDepIds,newDeps,既在 Watcher 中记录本人被收集进了哪些 Dep 中,最初又通过 dep.addSub(this)把本人记录到了 Dep 中,是不是很奇妙呢!

3. __ob__作用是什么?Observer 实例上的 dep 是干什么的?

首先,__ob__属性上保留的就是 Observer 实例,__ob__的第一个作用是能够用来标记以后 value 是否曾经被 Observer 转换成了响应式数据,防止后续反复操作。

其次,Vue 对于 Obejct 的变动侦测,收集依赖和触发依赖都在一个闭包内能够拜访到 dep 实例

function defineReactive () {
  /* 在闭包中定义一个 dep 对象 */
  const dep = new Dep()

  Object.defineProperty(obj, key, {get: function reactiveGetter () {
        /* 进行依赖收集 */
        dep.depend()},
    set: function reactiveSetter (newVal) {
      /*dep 对象告诉所有的观察者 */
      dep.notify()}
  })
}

但对于 Array,是在 getter 中收集依赖,在拦截器上触发的依赖,不相熟的小伙伴请自行看 Vue 解决数组的代码,大抵代码如下:

[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    /* 数组新插入的元素须要从新进行 observe 能力响应式 */
    const ob = this.__ob__
    
    /*dep 告诉所有注册的观察者进行响应式解决 */
    ob.dep.notify()
    return result
  })
})

咱们能够看到,在拦截器中,没有 dep 实例给咱们拜访,但因为拦截器是对原型的一种封装,所以能够在拦截器中拜访到 以后正在操作的数组(this),所以要把__ob__属性设置到每个被侦测的数据上,这里就能够通过 this.__ob__.dep 获取到 dep 实例。


最初我想说,学习源码一方面是为了更加纯熟的使用 Vue 这个框架去开发,但更重要的是了解框架为什么这么去设计,并使用到当前的日常开发中去,更好的晋升本人!

结尾

我是周小羊,一个前端萌新,写文章是为了记录本人日常工作遇到的问题和学习的内容,晋升本人,如果您感觉本文对你有用的话,麻烦点个赞激励一下哟~

正文完
 0