数据劫持

初始化数据

检测属性是否被重复声明,并对属性进行观测

export function initState(vm: Component) {
  vm._watchers = []
  const opts = vm.$options

  // vue 的数据来源:属性、方法、数据、计算属性、watch(按顺序解析)
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm) // 数据初始化工作
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

// 数据初始化工作
function initData(vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}

  // 1.数据不是对象则发生异常
  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

  // 2.校验数据是否在 method 中已经声明过
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }

    // 3.校验数据是否在属性中已经声明过
    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)) {
      // 4.将 _data 代理到实例上
      proxy(vm, `_data`, key)
    }
  }

  // 5.观测数据
  observe(data, true /* asRootData */)
}

数据观测

export function observe(value: any, asRootData: ?boolean): Observer | void {
  // 1.如果不是对象直接 return
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void

  // 2.如果已经观测过则直接返回上次观测的实例
  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
  ) {
    // 3.如果可以观测就进行观测
    ob = new Observer(value)
  }

  // 4.如果是根数据  vmCount 标注为 1
  if (asRootData && ob) {
    ob.vmCount++
  }

  return ob
}


// 只观测对象数据类型,已经观测的不在进行观测,不能扩展的属性不进行观测
export class Observer {
  constructor(value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)

    // 1.数组:重写数组原型方法 
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 2.观测数组中是对象类型的数据
      this.observeArray(value)
    } else {
      // 3.对象的话使用 defineProperty 重新定义属性
      this.walk(value)
    }
  }
  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])
    }
  }
}

对象的观测

对象的观测就是将所有属性使用 defineProperty 进行重新定义,想减少观测可以使用 Object.freeze 冻结对象;

export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 1.如果对象不可配置则直接退出
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 2.获取 getter 和 setter
  const getter = property && property.get
  const setter = property && property.set

  // 3.重新定义 set 和 get 方法
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val
      return value
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
    }
  })
}

数组的观测

通过重写原型方法来实现的

let oldArrayProtoMethods = Array.prototype;
export let arrayMethods = Object.create(oldArrayProtoMethods);

let methods = [
    'push',
    'pop',
    'shift',
    'unshift',
    'reverse',
    'sort',
    'splice'
];

methods.forEach(method => {
    arrayMethods[method] = function (...args) {
        const result = oldArrayProtoMethods[method].apply(this, args);
        const ob = this.__ob__;
        let inserted;
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args;
                break;
            case 'splice':
                inserted = args.slice(2)
            default:
                break;
        }

        if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
        return result
    }
})

依赖收集

图解

vue 的渲染过程是通过渲染 watcher 来实现的

创建 watcher 时,会对变量进行取值;

export function mountComponent( vm: Component, el: ?Element, hydrating?: boolean): Component {
  vm.$el = el
  let updateComponent = () => { // 执行时会去实例上取值
    vm._update(vm._render(), hydrating)
  }

  // 调用 updateComponent 这个方法
  new Watcher(vm, updateComponent, noop, {}, true /* isRenderWatcher */)

  hydrating = false
  return vm
}

对象依赖收集

对于对象而言,取值就会触发 get 方法,在 defineProperty 的 get 中进行依赖收集,在 set 中通知watcher 进行更新操作;

TS
TS
class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function, // updateComponent
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher)  vm._watcher = this
    
    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()
      : ''

    // 将 updateComponent 放到 this.getter 上
    this.getter = expOrFn
    this.value = this.lazy ? undefined : this.get() // 执行 get 方法
  }
  get() {
    pushTarget(this)  // Dep.target = 渲染 watcher
    let value
    const vm = this.vm
    try {
      // 开始取值,那么在 get 方法中就可以获取到这个全局变量 Dep.target 
      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)
      }
      popTarget() // 结束后进行清理操作
      this.cleanupDeps()
    }
    return value
  }
}
// Observer
// 渲染 watcher 默认会调用 Observer 中属性的 get 方法也就是传入的 updateComponent 方法,在调用此方法前先将 watcher 存到全局中,这样再取值时可以获取到这个 watcher
const dep = new Dep()

get: function reactiveGetter() {
  const value = getter ? getter.call(obj) : val
  if (Dep.target) { // 如果有 watcher,将 dep 和 watcher 对应起来
    dep.depend()
  }
  return value
}
set: function reactiveSetter(newVal) {
  dep.notify();    // 当属性更新时通知 dep 中的所有 watcher 进行更新操作
}

数组的依赖收集

watcher 和 dep 的关系是多对多的关系,一个属性一个 dep,每个 dep 里存放着多个 watcher,同时 watcher 也会记住对应的 dep

JavaScript
JavaScript
TS
// Observer
let childOb = !shallow && observe(val)
get: function reactiveGetter() {
  const value = getter ? getter.call(obj) : val
  if (Dep.target) {
    dep.depend()
    if (childOb) { // 如果值也是个对象的话,对这个值进行依赖收集
      childOb.dep.depend()
      if (Array.isArray(value)) { // 如果是数组对数组中的内容继续进行依赖收集
        dependArray(value)
      }
    }
  }
  return value
}
// 调用数组方法时进行 watcher 的更新操作
methodsToPatch.forEach(function (method) {
  ob.dep.notify()
})
export default class Dep {
  constructor() {
    this.id = uid++
    this.subs = []
  }
  addSub(sub: Watcher) {
    this.subs.push(sub)
  }
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this) // 让 watcher 记住自己
    }
  }
  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update() // 让存储的 watcher 依次调用更新方法
    }
  }
}
class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function, // updateComponent
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // 1.将 updateComponent 放到 this.getter 上
    this.getter = expOrFn
    this.value = this.lazy ? undefined : this.get() // 2.执行 get 方法
  }
  addDep(dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)

      // watcher 中会进行虑重操作,实现 watcher 和 dep 互相记忆
      if (!this.depIds.has(id)) {
        // 3.让 dep 记录 watcher
        dep.addSub(this)
      }
    }
  }
  update() {
    queueWatcher(this)
  }
}

异步更新

为了防止多次更改同一个属性(他们依赖的 watcher 相同)或者多次修改不同属性会导致频繁更新渲染;

export function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  // 1.判断 watcher 是否已经存放了,对相同 watcher 进行过滤操作
  if (has[id] == null) {
    has[id] = true

    // 2.将 watcher 存放到队列中
    queue.push(watcher)

    if (!waiting) { // 当同步的更改状态完毕时再去更新 watcher
      waiting = true
      nextTick(flushSchedulerQueue) // 在下一队列中清空 queue
    }
  }
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 数据劫持
    1. 1.1. 初始化数据
    2. 1.2. 数据观测
    3. 1.3. 对象的观测
    4. 1.4. 数组的观测
  2. 2. 依赖收集
    1. 2.1. 图解
    2. 2.2. vue 的渲染过程是通过渲染 watcher 来实现的
    3. 2.3. 对象依赖收集
    4. 2.4. 数组的依赖收集
  3. 3. 异步更新