Vue原理解析之observer模块

AugHagenaue 3年前
   <p>本文是针对 Vue@2.1.8 进行分析</p>    <p>observer 是Vue核心中最重要的一个模块(个人认为),能够实现视图与数据的响应式更新,底层全凭 observer 的支持。</p>    <p>observer 模块在Vue项目中的代码位置是 src/core/observer ,模块共分为这几个部分:</p>    <ul>     <li> <p>Observer : 数据的观察者,让数据对象的读写操作都处于自己的监管之下</p> </li>     <li> <p>Watcher : 数据的订阅者,数据的变化会通知到 Watcher ,然后由 Watcher 进行相应的操作,例如更新视图</p> </li>     <li> <p>Dep : Observer 与 Watcher 的纽带,当数据变化时,会被 Observer 观察到,然后由 Dep 通知到 Watcher</p> </li>    </ul>    <p>示意图如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d14b341435b44c20bee10cc223e07abf.png"></p>    <h2>Observer</h2>    <p>Observer 类定义在 src/core/observer/index.js 中,先来看一下 Observer 的构造函数</p>    <pre>  <code class="language-javascript">constructor (value: any) {    this.value = value    this.dep = new Dep()    this.vmCount = 0    def(value, '__ob__', this)    if (Array.isArray(value)) {        const augment = hasProto        ? protoAugment        : copyAugment      augment(value, arrayMethods, arrayKeys)      this.observeArray(value)    } else {      this.walk(value)    }  }</code></pre>    <p>value 是需要被观察的数据对象,在构造函数中,会给 value 增加 __ob__ 属性,作为数据已经被 Observer 观察的标志。如果 value 是数组,就使用 observeArray 遍历 value ,对 value 中每一个元素调用 observe 分别进行观察。如果 value 是对象,则使用 walk 遍历 value 上每个key,对每个key调用 defineReactive 来获得该key的 set/get 控制权。</p>    <p>解释下上面用到的几个函数的功能:</p>    <ul>     <li> <p>observeArray : 遍历数组,对数组的每个元素调用 observe</p> </li>     <li> <p>observe : 检查对象上是否有 __ob__ 属性,如果存在,则表明该对象已经处于 Observer 的观察中,如果不存在,则 new Observer 来观察对象(其实还有一些判断逻辑,为了便于理解就不赘述了)</p> </li>     <li> <p>walk : 遍历对象的每个key,对对象上每个key的数据调用 defineReactive</p> </li>     <li> <p>defineReactive : 通过 Object.defineProperty 设置对象的key属性,使得能够捕获到该属性值的 set/get 动作。一般是由 Watcher 的实例对象进行 get 操作,此时 Watcher 的实例对象将被自动添加到 Dep 实例的依赖数组中,在外部操作触发了 set 时,将通过 Dep 实例的 notify 来通知所有依赖的 watcher 进行更新。</p> </li>    </ul>    <p>如果不太理解上面的文字描述可以看一下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e5f2cb826b6230076e0b85ff2f045a24.png"></p>    <h2>Dep</h2>    <p>Dep 是 Observer 与 Watcher 之间的纽带,也可以认为 Dep 是服务于 Observer 的订阅系统。 Watcher 订阅某个 Observer 的 Dep ,当 Observer 观察的数据发生变化时,通过 Dep 通知各个已经订阅的 Watcher 。</p>    <p>Dep 提供了几个接口:</p>    <ul>     <li> <p>addSub : 接收的参数为 Watcher 实例,并把 Watcher 实例存入记录依赖的数组中</p> </li>     <li> <p>removeSub : 与 addSub 对应,作用是将 Watcher 实例从记录依赖的数组中移除</p> </li>     <li> <p>depend : Dep.target 上存放这当前需要操作的 Watcher 实例,调用 depend 会调用该 Watcher 实例的 addDep 方法, addDep 的功能可以看下面对 Watcher 的介绍</p> </li>     <li> <p>notify : 通知依赖数组中所有的 watcher 进行更新操作</p> </li>    </ul>    <h2>Watcher</h2>    <p>Watcher 是用来订阅数据的变化的并执行相应操作(例如更新视图)的。 Watcher 的构造器函数定义如下:</p>    <pre>  <code class="language-javascript">constructor (vm, expOrFn, cb, options) {    this.vm = vm    vm._watchers.push(this)    // 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.toString()      : ''    if (typeof expOrFn === 'function') {      this.getter = expOrFn    } else {      this.getter = parsePath(expOrFn)      if (!this.getter) {        this.getter = function () {}        process.env.NODE_ENV !== 'production' && warn(          `Failed watching path: "${expOrFn}" ` +          'Watcher only accepts simple dot-delimited paths. ' +          'For full control, use a function instead.',          vm        )      }    }    this.value = this.lazy      ? undefined      : this.get()  }</code></pre>    <p>参数中, vm 表示组件实例, expOrFn 表示要订阅的数据字段(字符串表示,例如 a.b.c )或是一个要执行的函数, cb 表示watcher运行后的回调函数, options 是选项对象,包含 deep 、 user 、 lazy 等配置。</p>    <p>watcher 实例上有这些方法:</p>    <ul>     <li> <p>get : 将 Dep.target 设置为当前 watcher 实例,在内部调用 this.getter ,如果此时某个被 Observer 观察的数据对象被取值了,那么当前 watcher 实例将会自动订阅数据对象的 Dep 实例</p> </li>     <li> <p>addDep : 接收参数 dep (Dep实例),让当前 watcher 订阅 dep</p> </li>     <li> <p>cleanupDeps : 清除 newDepIds 和 newDep 上记录的对dep的订阅信息</p> </li>     <li> <p>update : 立刻运行 watcher 或者将 watcher 加入队列中等待统一flush</p> </li>     <li> <p>run : 运行 watcher ,调用 this.get() 求值,然后触发回调</p> </li>     <li> <p>evaluate : 调用 this.get() 求值</p> </li>     <li> <p>depend : 遍历 this.deps ,让当前 watcher 实例订阅所有 dep</p> </li>     <li> <p>teardown : 去除当前 watcher 实例所有的订阅</p> </li>    </ul>    <h2>Array methods</h2>    <p>在 src/core/observer/array.js 中,Vue框架对数组的 push 、 pop 、 shift 、 unshift 、 sort 、 splice 、 reverse 方法进行了改造,在调用数组的这些方法时,自动触发 dep.notify() ,解决了调用这些函数改变数组后无法触发更新的问题。在Vue的官方文档中对这个也有说明: <a href="/misc/goto?guid=4959737609866032039" rel="nofollow,noindex">http://cn.vuejs.org/v2/guide/list.html#变异方法</a></p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000008377887</p>    <p> </p>