vue2源码浅读(三):数据响应式的原理与实现

vue2源码浅读(三):数据响应式

数据响应式入口

  • 紧接着上一篇,在initState中初始化data,调用了initData()方法:
  • src\core\instance\state.js
function initData (vm) {
  let data = vm.$options.data
  // 获取到用户传入的options 取到data属性, data的写法如果是函数形式 那么就使用.call指向传入的vm 拿到返回值 
 // 如果不是函数形式就是对象了不处理, 将数据绑定到data 和 vm._data(绑定到_data统一管理)上
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    // 检查data里的数据是否与methods里的方法名冲突
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 检查data里的数据是否与props里的数据冲突
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
     // 如果不是保留字段,将data里的属性代理到vm实例上,使得可以通过this获取
      proxy(vm, `_data`, key)
    }
  }
  // 响应式处理第一步
  observe(data, true /* asRootData */)
}
  • vue 设计, 不希望访问 _ 开头的数据,_ 开头的数据是私有数据
  • 那么,经过proxy后, data挂载vm的实例上,访问app.xxx就相当于访问app._data.xxx
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
  • 找到observe方法,这里才是响应式处理入口
  • src\core\observer\index.js
function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob;
  //已经被做过的响应式处理,数据会有__ob__属性,且__ob__为Observer实例,直接返回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
  ) {
    // 创建一个Observer(观察者)实例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

响应式处理

  • Vue的响应式数据分为两类:对象和数组
  • 遍历对象的所有属性,并为每个属性设置getter和setter,以便将来的获取和设置,如果属性的值也是对象,则递归为属性值上的每个key设置getter和setter
  • 接下来进入正题, Observer 类的具体实现
  • src\core\observer\index.js
class Observer {
  value;
  dep;
  vmCount: number; // number of vms that have this object as root $data
  constructor (value) {
    this.value = value
    // 实例化一个dep,即Observer都会有一个Dep依赖
    this.dep = new Dep()
    this.vmCount = 0
    //添加'__ob__'
    def(value, '__ob__', this)

    // 数组需要单独处理
    if (Array.isArray(value)) {
      if (hasProto) {
        // 如果是现代的浏览器,复杂类型数据有原型,调用arrayMethods,重写相关方法,具体见下边
        protoAugment(value, arrayMethods)
      } else {
        // 如果是老旧浏览器,没有原型,直接给数组上的方法给重写替换掉
        copyAugment(value, arrayMethods, arrayKeys)
      }
        // 调用数组观察方法
      this.observeArray(value)
    } else {
      // 对象的响应式处理
      this.walk(value)
    }
  }

  // 对象响应式处理:遍历所有属性,并为每个属性添加geeter/setter。
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  // 数组观察方法
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

数组响应式处理

  • 众所周知,Object.defineProperty不能监听数组的变化,具体为使用push、unshift、pop、shift、splice, sort, revers,是触发不了set的。
  • 通过重写数组操作方法实现数组监听
  • src\core\observer\array.js

// 获取数组的原型Array.prototype
const arrayProto = Array.prototype

// 创建一个空对象arrayMethods,并将arrayMethods的原型指向arrayProto
export const arrayMethods = Object.create(arrayProto)

// 这些方法需要重写覆盖
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
  	// 执行原始行为的逻辑
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 新加入的元素做响应式处理
    if (inserted) ob.observeArray(inserted)
    // 内部的dep去通知更新
    ob.dep.notify()
    return result
  })
})

对象响应式处理

  • 在Observer类里边,walk方法里调用了defineReactive方法
  • 获取数据时:在dep中添加相关的watcher
  • 设置数据时:再由dep通知相关的watcher去更新
  • src\core\observer\index.js
function defineReactive (obj,key,valcustomSetter,shallow) {
  const dep = new Dep()// 每个key都会实例化一个Dep
  // 获取属性相关描述
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // configurable为false时,该属性相关的配置不能再被更改,也不能被删除
  if (property && property.configurable === false) {
    return
  }
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  let childOb = !shallow && observe(val)
  // 拦截对obj[key]的获取和设置
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 拦截对obj[key]的获取操作
    get: function reactiveGetter () {
      // 获取obj[key]的值
      const value = getter ? getter.call(obj) : val
      // 依赖收集
      // 如果存在,则说明此次调用触发者是一个Watcher实例
      if (Dep.target) {
      // 依赖关系的创建,建立dep和Dep.target之间的依赖关系(把dep添加到watcher中,也将watcher添加到dep中)
        dep.depend()
        if (childOb) {
        // 建立是ob内部的dep和Dep.target之间的依赖关系,也就是嵌套对象的依赖收集
          childOb.dep.depend()
          if (Array.isArray(value)) {
          // 如果是数组,数组内部的所有项都需要做依赖收集处理
            dependArray(value)
          }
        }
      }
      return value
    },
    // 拦截对obj[key]的设置操作
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // 如果新值和老值相等则不做处理 直接返回
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      // 如果setter不存在,说明只能获取不能设置,也直接返回
      if (getter && !setter) return
      // 设置为新的值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 对新值也做响应式处理
      childOb = !shallow && observe(newVal)
      // 通知更新
      dep.notify()
    }
  })
}
  • 收集对数组元素的依赖项
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

依赖管理Dep类

  • 接着找到依赖管理Dep类,主要实现提供依赖收集 ( depend ) 和 派发更新 ( notify ) 的功能。
  • 每个在页面上使用了的数据都会有一个Dep 类,主要访问属性的时候 get 方法会收集对应的 watcher
  • 在获取数据的时候知道自己(Dep)依赖的watcher都有谁,同时在数据变更的时候通知自己(Dep)依赖的这些watcher去执行对应update,以在页面多组件情况下实现局部渲染。
  • src\core\observer\dep.js
class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
  constructor () {
    this.id = uid++
    // subs用于存放依赖
    this.subs = []
  }
  // 在dep中添加watcher
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  // 删除dep中的watcher
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
  // 在watcher中添加dep
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  // 遍历dep的所有watcher 然后执行他们的update 
  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()
    }
  }
}

Dep.target = null
const targetStack = []
// 渲染阶段,访问页面上的属性变量时,给对应的 Dep 添加 watcher
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}
// 访问结束后删除
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

观察者watcher

  • 页面访问的属性,将该属性的watcher 关联到该属性的 dep 中
  • 同时, 将 dep 也存储 watcher 中. ( 互相引用的关系 )
  • 在 Watcher 调用 get 方法的时候, 将当前 Watcher 放到全局, 在 get 之前结束的时候(之后), 将这个 全局的 watcher 移除
  • src\core\observer\watcher.js
class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    // // 当前Watcher添加到vue实例上
    vm._watchers.push(this)
    // 参数配置,options默认false
    if (options) {
      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()
      : ''
    // 如果exporfn是函数的话,就会把这个函数赋值给getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
       // 如果不是函数是字符串的话,会调用parsePath方法,
      // parsePath方法会把我们传入的path节分为数组,通过patch来访问到我们的对象。
      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
        )
      }
    }
    // Watcher不止会监听Observer,还会直接把值计算出来放在this.value上
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  get () {
    // 将Dep的target添加到targetStack,同时Dep的target赋值为当前watcher对象
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 去访问我们给属性重写的 get 方法,添加 watcher 依赖
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      if (this.deep) {
        traverse(value)
      }
      // update执行完成后,弹出target,防止data上每个属性都产生依赖,只有页面上使用的变量需要依赖
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
  // 添加依赖
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      // watcher添加它和dep的关系
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
      	// dep添加它和watcher的关系
        dep.addSub(this)
      }
    }
  }
  //  清理依赖项收集
  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 () {
    if (this.lazy) {
    // 懒执行,computed
      this.dirty = true
    } else if (this.sync) {
    // 同步执行
      this.run()
    } else {
    // 将watcher放到watcher队列中
      queueWatcher(this)
    }
  }
// 更新视图
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        const oldValue = this.value
        this.value = value
        if (this.user) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
  // 懒执行的watcher会调用该方法
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }
// 依赖这个观察者收集的所有deps
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
  // // 从所有依赖项的订阅者列表中把自己删除
  teardown () {
    if (this.active) {
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

在这里插入图片描述

梳理整个过程

  • 在initState方法中,首先是用户传入参数有没有data,没有则observe(vm._data = {}, true /* asRootData */),将一个空的data代理到vm实例上,有则调用initData方法。
  • 在initData方法中,主要遍历data中的属性,通过proxy(vm,_data, key),并将其代理到vm实例上,然后对data进行观察observe(data, true /* asRootData */);
  • 在observe方法中,主要判断传入的value有没有__ob__属性,有则说明已经被观察过了,没有则进行new Observer(value)
  • 在Observer类中,主要实现了对数组和对象的响应式处理。
    • 首先是调用 def(value, '__ob__', this)给value添加__ob__属性;
    • 判断value如果是数组,再判断当前环境
      • 如果是现代的浏览器,复杂类型数据有原型,调用arrayMethods,重写相关方法
        • 将数组原型指向空对象上,对push、unshift、pop、shift、splice, sort, revers方法重写覆盖。具体还是执行原来的行为逻辑,主要是拦截对其做响应式处理:调用 ob.dep.notify()通知更新,如果是新插入的元素调用ob.observeArray(inserted)。
      • 如果是老旧浏览器,没有原型,直接给数组上的方法给重写替换掉
      • 最后调用observeArray方法,对每个数组元素进行observe
    • 判断value如果是对象
    • 遍历对象,对每个键调用defineReactive(obj, keys[i])方法,做响应式处理
  • 在defineReactive(obj, keys[i])方法中,首先对key都会实例化一个Dep。
    • 在get方法中,收集依赖,把dep添加到watcher中,也将watcher添加到dep中dep.depend()
      • 如果有子对象,则进行嵌套对象的依赖收集childOb.dep.depend()
      • 如果是数组,数组内部的所有项都需要做依赖收集 dependArray(value)
    • 在set方法中,如果新值和老值相等则不做处理直接返回,否则赋新值,且对新值做响应式处理,dep.notify()通知更新
  • 在Dep类中,访问属性的时候 get 方法会收集对应的 watcher,在数据变更的时候通知自己(Dep)依赖的这些watcher去执行对应update,实现局部渲染。
  • Watcher类中,将Dep也存储 watcher 中,形成相互引用的关系,提供了update方法
    • Watcher中 不止会监听Observer,还会直接把值计算出来放在this.value上。其中get方法主要应用于computed计算和watch监听。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值