Vue2.6x源码解析(三):深入响应式原理

系列文章:

本节将深入理解Vue2的响应式系统构成。

Vue2的响应式数据依赖于原生JavaScript方法Ojbect.defineProperty,这个方法提供了数据拦截的作用,没有这个原生方法就没有Vue的响应式数据,也就没有Vue的响应式系统构成。

扩展:Ojbect.defineProperty方法在追踪集合数据类型变化有缺陷,所以vue3替换为了ES6的proxy方法,但是其核心思想不变。后面在做Vue3源码解析的时候会讲解,本节不做具体了解。

根据Vue源码分析可知,Vue的响应式系统由三大核心模块构成:ObserverDepWatcher,本节将针对每个模块展开讲解。

Vue2.6x源码解析(三):深入响应式原理

1,Observer

我们还是从初始化data起步来深入整个的响应式系统:

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = getData(data, vm) || {}
  // 调用observe 函数将data 转换成响应式数据 【data初始化完成】
  observe(data, true /* asRootData */)
}

可以看见initData里面,最后调用了一个observe方法将整个data对象作为参数传入,将所有变量转换成响应式数据。

observe()

查看observe源码:

// observe方法:为对象创建Observer实例  value:就是一个属性参数
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 主要判断:如果value参数不是对象,不会进行转换
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  # 声明一个变量:来存储Observer实例
  let ob: Observer | void
  // 1,已存在情况: 如果当前value有__ob__这个属性,并且这个属性值是Observer实例:就把当前的__ob__值赋值给ob变量
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    # 2,正常的普通对象,创建一个Observer实例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  # 返回Observer实例对象
  return ob
}

根据我们传入的observe(data),会进入else if分支调用Observer类,new Observer()创建一个Observer实例。

class Observer

继续查看Observer类源码:

// Observer 类:Observer会递归将组件里data对象的所有属性key转换为getter/setter
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data
​
  constructor (value: any) {
    this.value = value
    # 初始化dep属性,为一个Dep实例,作用是收集响应式数据相关的watcher依赖
    this.dep = new Dep()
    this.vmCount = 0
    # 给当前Value对象定义一个__ob__属性, 并且设置值为当前Observer实例
    def(value, '__ob__', this)
    // 判断参数类型:对数组先进行处理
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 处理数组
      this.observeArray(value)
    } else {
      # 大部分情况会走else分支;调用walk方法
      this.walk(value)
    }
  }
​
  // 这个方法只有在数据类型为Object时被调用
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      # 循环:调用defineReactive方法,将对象的每个属性都转换成getter/setter
      defineReactive(obj, keys[i])
    }
  }
​
  // 为数组每一项,调用observe方法:如果item是obj,则为当前项item创建Observer实例
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

new Observer()创建实例的时候,会调用constructor构造器,给data定义了一个__ob__属性,然后调用了类里面的walk函数,根据源码可知,walk会获取参数对象的所有keys,然后在for循环里面对每个key属性执行defineReactive方法,整个方法的作用是将每个key属性设置为getter/setter,即响应式数据。

注意: 我们可以看见首先是在observe方法内部调用new Observer创建Observer实例,并且在创建Observer实例内部又调用了observe方法,如此递归调用是为了将整个data对象的所有key都转换成响应式数据。

defineReactive

下面我们继续分析defineReactive方法源码:

# 定义响应式数据API
export function defineReactive (
  obj: Object,  // 目标对象
  key: string,  // 属性key
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  # 生成一个dep实例,来收集当前属性key相关的watcher
  const dep = new Dep()
  // 使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    # 如果当前的configurable为false,则不会进行任何转换操作
    return
  }
​
  // cater for pre-defined getter/setters
  // 预定义的getter/setter
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  # 这里又调用了observe方法:这里主要是针对当前属性key是否为对象,
  // 如果是对象就会开始递归操作,回到observe方法,继续new Observer(value)
  // 如果不是对象,则不会进行递归转换
  let childOb = !shallow && observe(val)
  # 重点;为当前obj对象定义一个访问器属性key
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 调用getter,获取最新的value值
      const value = getter ? getter.call(obj) : val
      # 在getter中收集依赖
      if (Dep.target) {
        // 收集到当前key的dep依赖里面
        dep.depend()
        // 如果是childOb存在
        if (childOb) {
          childOb.dep.depend() // 收集依赖watcher
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 获取旧的value
      const value = getter ? getter.call(obj) : val
      # 如果set设置值没有变化,则退出执行
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      # 处理不带setter的访问器属性,直接return
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        // 设置新值
        val = newVal
      }
      #  再次调用observe方法:这里主要是针对当前属性key设置的新值是否为对象,如果为对象就转换为响应式数据
      childOb = !shallow && observe(newVal)
      // 在setter中触发依赖
      dep.notify()
    }
  })
}

我们按传入的data对象来分析:

defineReactive(data, key)

根据defineReactive源码分析可知,这个方法最重要的就是为目标对象设置访问器属性key,并且在getter中使用dep收集依赖,在setter中使用dep触发依赖。dep我们放在下节里面讲,这里我们主要关注data对象的响应式处理。

注意:根据在initData代码中data = vm._data,所以对data对象的设置,就是在vm_data中设置了真正的响应式数据。

测试数据:

data() {
    return {
        name: 'tom',
        obj: {
            age: 20
        }
    }
}

InitData处理后:

data {
  name: 'tom',
  obj: {
    age: 20
  }
}

Vue2.6x源码解析(三):深入响应式原理

根据打印结果可以看出:observe(data)内部对data对象递归处理转换成响应式数据,最终将转换后的数据存储在vm._data里面。

Vue2.6x源码解析(三):深入响应式原理

注意:_data和obj对象都有一个__ob__属性,这个属性的值就是其对应的Observer实例,这个是在属性是在Observer实例被创建的时候设置的,即Observer类的constructor构造器里面。

# 给当前Value对象定义一个__ob__属性, 并且设置值为当前Observer实例
this.value = value
# 创建一个dep实例
this.dep = new Dep()
this.vmCount = 0
​
def(value, '__ob__', this)

而且Observer实例有三个属性valuedepvmCount。其中最重要的就是dep属性,dep属性中存储的是Dep实例,用于收集响应式数据相关的依赖watcher

Vue2的dep收集的是watcher实例,Vue3的dep收集的是effect实例。

2,Dep

我们已经知道:observer主要作用就是将data对象中的所有数据属性key转换为getter/setter。但是在其内部有几个地方使用了dep类,我们还没有分析其作用,这节我们主要讲解Dep源码:

# Dep类:dependencies依赖:专门收集依赖,管理依赖,删除依赖和触发依赖等
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
​
  constructor () {
    this.id = uid++
    # 初始化一个依赖收集列表:watcher实例
    this.subs = []
  }
​
  // 添加依赖:添加watcher实例
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  
  // 删除依赖
  removeSub (sub: Watcher) {
    // 从数组中移除一项: remove方法在shared/util.js中
    remove(this.subs, sub)
  }
  
  // 这个方法作用:同时将当前dep实例添加到watcher的deps中
  depend () {
    if (Dep.target) {
      # addDep方法
      Dep.target.addDep(this)
    }
  }
  
  // 通知依赖, 触发watcher的update更新方法
  notify () {
    # 复制一份依赖列表(subs里面都是watcher)
    const subs = this.subs.slice()
    // 循环所有依赖:触发watcher实例的更新方法
    for (let i = 0, l = subs.length; i < l; i++) {
      // 也就是Watcher中的update方法。
      subs[i].update()
    }
  }
}

根据源码可知:Dep实例有两个重要的属性idsubs。其中最重要的就是subs属性,这个属性是一个数组,专门用来收集依赖【watcher实例】,可以看见Dep类里面的方法都是围绕subs属性展开的,可以添加依赖,删除依赖。

Vue2.6x源码解析(三):深入响应式原理

再次查看之前的截图:可以看到dep实例中的subs属性存储就是依赖列表,依赖就是watcher实例。

注意: Dep实例中还有一个非常重要的方法就是notify(),这个方法的作用是通知subs依赖列表中的每个依赖即watcher实例,去调用自己的upadte方法,执行回调函数。那么这个方法是在哪里调用的呢?

回到我们在defineReactive方法中定义响应式数据时,在响应式数据的setter最后调用了这个方法:

set: function reactiveSetter (newVal) {
    ...
    # 通知依赖更新
    dep.notify()
}

我们已经知道了在响应式数据的setter中通知依赖更新,要知道具体怎么更新的就得继续深入了解Watcher类的源码。

3,Watcher

本节我们开始讲解watcher类源码:

// Watcher类:
export default class Watcher {
 vm: Component;
 expression: string;
 cb: Function; // 回调函数
 id: number; // 实例id
 deep: boolean; // 深度监听标识
 user: boolean; // 用户定义的watcher标识,比如watch
 lazy: boolean; // 计算属性watcher标识
 sync: boolean; 
 dirty: boolean; // 给computed计算属性使用的,重新计算值
 active: boolean;
 deps: Array<Dep>; // 收集dep实例
 newDeps: Array<Dep>;
 depIds: SimpleSet;
 newDepIds: SimpleSet;
 before: ?Function; // 存储组件的beforeMount钩子
 getter: Function;
 value: any;
​
 constructor (
   vm: Component,
   expOrFn: string | Function,
   cb: Function,
   options?: ?Object,
   isRenderWatcher?: boolean
) {
   this.vm = vm
   if (isRenderWatcher) {
     # 如果是组件的renderWatcher,则把当前watcher实例存储到_watcher属性 (专门用于组件更新)
     vm._watcher = this
   }
   // 同时将各种watcher备份到_watchers数组中
   vm._watchers.push(this)
   // options
   if (options) {
     // !!操作符:没有定义则返回false
     this.deep = !!options.deep
     this.user = !!options.user
     this.lazy = !!options.lazy
     this.sync = !!options.sync
     this.before = options.before
   } 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()
     : ''
   // parse expression for getter
   if (typeof expOrFn === 'function') {
     this.getter = expOrFn  // 函数名
   } else {
     // parsePath解析对象:'a.b.c' 拿取c属性的getter
     this.getter = parsePath(expOrFn)  // 对象
     if (!this.getter) {
       this.getter = noop
       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
       )
     }
   }
   # 默认初始化执行一次get,【计算属性的watcher除外】
   this.value = this.lazy
     ? undefined
     : this.get()
}
 // 返回最新的value值
 get () {
   pushTarget(this)
   let value
   const vm = this.vm
   try {
     # 执行getter方法【重点】
     value = this.getter.call(vm, vm)
   } catch (e) {
     if (this.user) {
       handleError(e, vm, `getter for watcher "${this.expression}"`)
     } else {
       throw e
     }
   } finally {
     // "touch" every property so they are all tracked as
     // dependencies for deep watching
     if (this.deep) {
       traverse(value)
     }
     popTarget()
     this.cleanupDeps()
   }
   // 返回最新的值
   return value
}
​
 // 收集dep实例
 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方法
       dep.addSub(this)
     }
   }
}
​
 // 清空deps
 cleanupDeps () {
   let i = this.deps.length
   while (i--) {
     const dep = this.deps[i]
     if (!this.newDepIds.has(dep.id)) {
       dep.removeSub(this)
     }
   }
   let tmp = this.depIds
   this.depIds = this.newDepIds
   this.newDepIds = tmp
   this.newDepIds.clear()
   tmp = this.deps
   this.deps = this.newDeps
   this.newDeps = tmp
   this.newDeps.length = 0
}
​
 # 依赖变化,调用update更新方法
 update () {
   /* istanbul ignore else */
   if (this.lazy) {
     this.dirty = true
   } else if (this.sync) {
     // 同步执行回调
     this.run()
   } else {
     # 异步执行回调:将当前watcher实例推送到队列【Vue2都是走的这里更新】
     queueWatcher(this)
   }
}
​
 # 执行回调【watch选项的回调】
 run () {
   if (this.active) {
     const value = this.get()
     if (value !== this.value || isObject(value) || this.deep) {
       // 保存旧值
       const oldValue = this.value
       // 设置新值
       this.value = value
       // 判断是不是用户定义的watcher,比如watch ,如果是就需要用可以处理error的方法来处理cb
       # run方法会执行watcher实例自身的回调函数,将value和oldValue两个参数传到回调函数中。
       if (this.user) {
         const info = `callback for watcher "${this.expression}"`
         invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
       } else {
         // 内部生成的watcher,直接调用cb
         this.cb.call(this.vm, value, oldValue)
       }
     }
   }
}
 
 // watcher的depend主要是给计算属性观察依赖使用的,在创建createComputedGetter时候调用
 depend () {
   let i = this.deps.length
   while (i--) {
     # watcher.depend方法最终的作用是:将dep添加到自身的deps数组中,同时是又将自身添加到dep实例的subs中
     this.deps[i].depend()
   }
}
}

watcher类的源码是三个中最多的一个,有非常多的属性和方法。

这里我们跟着前面的节奏继续分析update方法,可以看见这个方法内部有三个分支:

  • 第一个分支dirty属性是给计算属性使用,主要就是用于重新计算。
  • 第二个分支是同步回调,直接执行run方法,但是实际上Vue内部都是异步更新回调。
  • 第三个分支是将当前watcher实例推入到一个队列,异步更新回调。具体执行过程我们在下节分析,这里我们只要知道其内部最终也是执行的watcher实例的run方法 (重点)

run方法只要是用于执行watch的回调:获取最新的value,执行watch对应的watcher实例身上的cb回调函数,并且将value和oldValue两个值作为参数传到回调函数中。

关于queueWatcher的相关内容,我们在《Vue2.6x源码解析(四):异步更新队列》文章中解析。

我们继续看depend方法:watcher中有一个depend方法,而Dep中也有一个depend方法。那么这两个方法有什么区别呢?

// watcher :watcher的depend主要是给计算属性观察依赖使用的,在创建createComputedGetter时候调用watcher.depend()
 depend () {
   let i = this.deps.length
   while (i--) {
     # dep实例的depend方法
     this.deps[i].depend()
   }
}
​
// dep : dep的depend方法是给data响应式数据使用的,用于依赖收集
 depend () {
   if (Dep.target) {
     # watcher实例的addDep方法: 将dep自身添加到目标watcher的deps数组中,同时将watcher添加到自身的subs中
     Dep.target.addDep(this)
   }
}

根据这里我们可以得出两个结论:

  • Dep实例中的subs属性在收集watcher实例。
  • watcher实例中的deps属性又在收集dep实例,两者互相关联。

到这里,我们知道了setter的触发依赖,再回顾一下前面的收集依赖:

// 在getter中收集依赖
if (Dep.target) {
 # 调用dep.depend方法互相收集依赖
 dep.depend();
 // 如果是childOb存在
 if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
 dependArray(value);
}
}
}

最后我们一句话总结Vue的响应式系统原理:响应式数据在getter中收集依赖,在setter中触发依赖。

下节我们继续分析Vue的异步更新原理。

原文链接:https://juejin.cn/post/7221435748905697340 作者:江北__张小凡

(0)
上一篇 2023年4月14日 上午11:09
下一篇 2023年4月15日 上午10:00

相关推荐

发表评论

登录后才能评论