vue源码学习——响应式数据

系列文章目录

vue源码学习——初始化data
vue源码学习——响应式数据




前言

《vue源码学习——初始化data》一文中,知道了在new Vue()时做了一系列初始化操作,其中在初始化data数据时,利用observe(data,true)方法,对数据属性进行了观察。下面来具体看下是如何对data进行的观察,从而实现数据驱动~
vue版本:v2.5.21
在这里插入图片描述


一、observe()

代码如下(src/core/observer/index.js):

export function observe (value: any, asRootData: ?boolean): Observer | void {
    
    
  if (!isObject(value) || value instanceof VNode) {
    
    
    return
  }
  let ob: Observer | void  // 定义observer实例 并最后返回
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    
    
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&  // 判断是否服务端渲染,node环境
    (Array.isArray(value) || isPlainObject(value)) && // 只有当数据对象是数组或纯对象的时候
    Object.isExtensible(value) &&  // 对象可扩展 即不是冻结对象等等
    !value._isVue  //不是Vue实例
  ) {
    
    
    ob = new Observer(value)  // 创建一个 Observer 实例
  }
  if (asRootData && ob) {
    
    
    ob.vmCount++
  }
  return ob
}
  • 本函数,接收2个参数: value【纯对象数据】、asRootData【是否是根级数据,用来统计vm数量ob.vmCount++
  • 经过一系列入参判断,最终返回一个ob对象,ob为一个Observer实例对象:ob = new Observer(value)

二、Observer类

代码如下(src/core/observer/index.js):

export class Observer {
    
    
  value: any;   // 纯对象
  dep: Dep;  
  vmCount: number; // number of vms that have this object as root $data,vm数量

  constructor (value: any) {
    
    
    this.value = value      // 纯对象,observe函数已经判定过
    this.dep = new Dep()    // 依赖
    this.vmCount = 0        // vm数量,observe函数赋值过此值
    def(value, '__ob__', this)  // 给数据定义了__ob__属性,值为当前的Observe实例
    
    if (Array.isArray(value)) {
    
      // 若此对象是个数组
      if (hasProto) {
    
       // [util/env.js] : hasProto = '__proto__' in {}
        protoAugment(value, arrayMethods)  // [./array.js] value.__proto__ = arrayMethods , 重写了数组的7个方法,
      } else {
    
    
        copyAugment(value, arrayMethods, arrayKeys)  // 
      }
      this.observeArray(value)   // 观察数组,遍历数组,依次观察各项:observe(items[i])
    } else {
    
      // 纯对象,调用walk,继续走流程
      this.walk(value)
    }
  }
  • 在实例初始化constructor时,初始化了3个自身属性:value【观察的对象】、dep【依赖收集容器】、vmCount【vm个数】
  • def(value, '__ob__', this),value对象下,挂载__ob__属性,对应的值为实例本身(this)。也就是在vue中所有data下都挂载一个 __ob__ 属性,为观察数据添加观察者引用,如下:
const data = {
    
    
     a: 1,
     __ob__: {
    
    
       value: data, // value 属性指向 data 数据对象本身,这是一个循环引用
       dep: dep实例对象, // new Dep()
       vmCount: 0
     }
}
  • Array.isArray(value)判定是否是数组对象,这里对数组对象做了特殊处理,重写了数组的7个方法(在observer/array.js),observeArray(value)遍历数组各项进行观察observe(items[i]),又回到了最初流程。
  • 若value不是数组对象,walk(value)继续走流程:
 walk (obj: Object) {
    
    
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
    
    
      defineReactive(obj, keys[i])  // 遍历可枚举属性,为每个属性 defineReactive
    }
  } 
  • defineReactive(obj, keys[i])为每个对象属性进行getter,setter设置,把每个属性数据变成了响应式
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
    
    
  const dep = new Dep()
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    
     // 当且仅当指定对象的属性描述可以被改变或者属性可被删除时,configurable为true。
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get     // 获取该属性的访问器函数(getter)
  const setter = property && property.set     // 获取该属性的设置器函数(setter)。 如果没有设置器, 该值为undefined
  if ((!getter || setter) && arguments.length === 2) {
    
    
    val = obj[key]
  }
  // !shallow时,observe(val)递归调用深度监听
  let childOb = !shallow && observe(val)
  
  Object.defineProperty(obj, key, {
    
    
    enumerable: true,    // 可枚举
    configurable: true,  // 可修改
    get: function reactiveGetter () {
    
    
      const value = getter ? getter.call(obj) : val // 有 `getter ` 就是自定义的`get`函数,如果没有就是取`val`的值。
      if (Dep.target) {
    
      // Dep.target 全局变量指向的就是当前正在解析指令的Complie生成的 Watcher
        dep.depend()    // 添加依赖
        if (childOb) {
    
    
          childOb.dep.depend()
          if (Array.isArray(value)) {
    
    
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
    
    
      const value = getter ? getter.call(obj) : val  // 首先是判断了是使用有 `getter ` 就是自定义的`get`函数,如果没有就是取`val`的值。
    /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
    
    
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
    
    
        customSetter()
      }
     
      if (getter && !setter) return    // 有getter,没setter时,为只读状态,不可修改,继而无需观察
      if (setter) {
    
       // 如果有`setter`就调用`setter`处理,否则直接复制给`val`。
        setter.call(obj, newVal) 
      } else {
    
    
        val = newVal
      }
      childOb = !shallow && observe(newVal)   // `shallow `为false的话,就重新监听`newVal`的值。
      dep.notify()  // 调用 Dep 实例的 notify 方法, 更新
    }
  })
}
  • 通过Object.getOwnPropertyDescriptor(obj, key)获取该字段可能已有的属性描述对象,属性key对应的属性描述符,若属性存在于obj上,返回其属性描述符对象(property descriptor),否则返 undefined。
  • Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
  • property.getproperty.set获取该属性的访问器函数(getter),设置器函数(setter),若无返回undefined
  • getter中,Dep.target【实则是一个Watcher】存在时,在依赖容器dep中添加依赖dep.depend() (下面讲到)
  • setter中,更新完最新值后,调用了dep.notify()发出了DOM更新通知
  • 这里多个逻辑涉及到了dep,下面看下Dep类

三、Dep类

代码如下(src/core/observer/dep.js):

let uid = 0     // 全局变量,唯一标识

export default class Dep {
    
    
  static target: ?Watcher;   // 当前正在计算的一个watcher对象,
  id: number;  // 每个Dep实例都有唯一的ID
  subs: Array<Watcher>;  // subs用于存放依赖

  constructor () {
    
    
    this.id = uid++    // 保证每个Dep实例都有唯一的ID
    this.subs = []
  }
  // 添加依赖
  addSub (sub: Watcher) {
    
    
    this.subs.push(sub)
  }
  // 移除依赖
  removeSub (sub: Watcher) {
    
    
    remove(this.subs, sub)
  }
  depend () {
    
    
    if (Dep.target) {
    
      // 对应的就是下面的全局变量Dep.target对应的watcher
      Dep.target.addDep(this)  // 调用当前watcher的appDep(this)
    }
  }
  // 通知更新,defineReactive dep=new Dep() ==>setter dep.notify() =>watcher update() ==>  ...更新UI
  notify () {
    
    
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
    
    
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
    
    
      subs[i].update() // 通知所有绑定 Watcher。调用watcher的update()
    }
  }
}
// 这是全局变量,因为任何时候都可能只有一个watcher正在评估。
Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
    
    
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
    
    
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
  • Dep是数据订阅和通知watcher的事件处理方
  • Observer实例中,对象属性getter中调用Dep实例的dep.depend(),最终调用的是Dep.target.addDep(this)Dep.target全局变量存储的是当前的Watcher实例,也就是调用的watcher的addDep()方法(下面提到)
  • Observer实例中,对象属性setter中调用Dep实例的dep.notify(),最终调用的是subs[i].update()subs存储的所有的watchers,数组遍历调用watcher的update()方法,也就是说当observer对应setter触发时,会向对应的dep中全部watchers发送更新消息
  • Dep.target代表着当前正在计算的watcher实例。通过在watcher中调用Dep的pushTarget()popTarget()更改Dep.target

三、Watcher

代码如下(src/core/observer/watcher.js):

export default class Watcher {
    
    
 //...
 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)
      }
    }
  }
 update () {
    
    
    if (this.lazy) {
    
    
      this.dirty = true
    } else if (this.sync) {
    
    
      this.run()
    } else {
    
    
      queueWatcher(this)
    }
  }
  //...
}

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

  • 订阅者。通过Dep,在观察数据执行getter时添加订阅关系。
  • watcher的addDep()方法,绕了一圈,又调用的是dep.addSub(this),Dep实例的addSub()方法就是在this.subs依赖数组里面增加一个依赖。
  • watcher的notify()方法,会触发组件重新渲染。

总结

大体流程:
Observer、Dep、Watcher

猜你喜欢

转载自blog.csdn.net/qq_36131788/article/details/113743854