vue核心原理-监测数据变化

2020/2/26 11:15:47

本文主要是介绍vue核心原理-监测数据变化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

简要的描述加复杂的代码表达核心思想
本文源码来自 vue/src/core/observer/*.js


前言

我们实际开发中发现,在data中定义的所有数据,后续无论是在template中使用,还是在methods中使用,都能随着数据的变化而变化。为了理解这其中的原理,研究源码后整理出这篇文章,欢迎大家及时指正。

第一步:数据注册监听

vue 2.x 版本使用的是 Object.defineProperty 详细API文档见Object.defineProperty
用于绑定Object类型数据,比如定义一个person:

let person = {
    name: 'usm',
    age: 12
}
复制代码

现在希望personnameage发生改变时,可以触发一些操作,就可以通过 Object.defineProperty 实现:

Object.defineProperty(person, 'name', {
    enumerable: true,
    configurable: true,
    get() {
        console.log('get name's value');
    },
    set(val) {
        console.log(`set value ${val}`);
    }
});

person.name             // get name's value
person.name = 'new'     // set value new
复制代码

其中enumerable属性表示此属性设置为可枚举,configurable表示此属性可被修改/删除。
至此,person对象中的name属性发生读/写操作时,都可以被监听到,并执行对应的代码。

回到源码,vue中实现了一个Observer对象,用来对vue实例中的每个数据添加监听。

class Observer {
  constructor (value) {
    this.value = value
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  // 遍历Object中每个属性,添加监听
  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])
    }
  }
}
复制代码

其中def(value, '__ob__', this)用于给当前对象添加一个__ob__属性,值就是当前的Observer,目的时用来标记为已添加监听。

可以看到针对Object类型的对象,遍历后对每个属性调用了defineReactive方法。

// defineReactive方法部分内容
function defineReactive (obj, key, val) {

  ......

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = val;

      ...

      return value
    },
    set: function reactiveSetter (newVal) {
      const value = val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }

      ...
    }
  })
}
复制代码

getset中省略部分就是数据发生改变后所做的操作。
其中set时做了优化,判断数据是否变化,无变化或无set函数时不做操作;当已存在set函数时直接执行,避免重复监听。


第二步:绑定监听器

第一步中实现了数据的监听,第二步就要根据数据的变化,来通知对应的dom进行更新。所以我们要先知道通知谁,也就是谁依赖了这个数据,由于获取数据时会触发get函数,因此我们就在get函数中收集依赖。

vue中实现了一个Dep类,用来管理当前数据的依赖,只需要对每个添加监听的数据创建一个Dep类,再当“谁”调用了当前数据,就把“谁”添加到Dep中,触发set时再通知Dep中存放的依赖们。

首先实现Dep类:

class Dep {
  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub) {
    this.subs.push(sub)
  }

  removeSub (sub) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
复制代码

Dep类比较简单,定义了subs用于存放依赖数组,收集依赖时,触发addSub方法,派发通知时调用notify方法,对数组中每个依赖调用update方法。

第三步:Watcher

Dep中,我们发现每个方法都是在处理一个依赖,而这个依赖从何而来,查看源码后发现,vue还定义了一个Watcher类,也就是我们说的依赖。

class Watcher {
  constructor (vm, cb) {
    this.vm = vm
    this.cb = cb
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.value = this.get()
  }

  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    value = this.getter.call(vm, vm)
    popTarget()
    return value
  }

  addDep (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 () {
    
    ...

    this.run()
  }

  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

}
复制代码

其中在实例化时,调用了get方法获取value,这里调用了一个pushTarget方法,和一个对应的popTarget方法,位于源码中dep.js文件中

function pushTarget (target) {
  targetStack.push(target)
  Dep.target = target
}

function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
复制代码

我们看到调用pushTarget方法时,将Dep的静态属性target设置为当前的Watcher对象,同时推入一个target栈中,调用popTarget时再弹栈。

回到Observer类的源码中

// 截取Object.defineProperty部分
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = val
      if (Dep.target) {
        dep.depend()
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      dep.notify()
    }
  })
复制代码

得出整体流程如下,初始化Watcher类,调用get方法,在get方法中,调用数据的getter,而在getter中,调用了dep.dependdepend方法中调用了Watcher类的addDep方法,addDep方法最终调用addSub方法添加依赖并注册监听。

派发通知时,触发update方法,从而更新DOM

流程图如下:

流程图

总结

本文分析了vue源码中对Object类型数据的绑定过程。

  • 针对vue中定义的各项数据,收集使用到该数据的依赖并注册监听
  • 当数据发生变化时,通知所有依赖进行更新。

因此当data中定义的数据发生变化时,所有用到该数据的地方都能发生变化,也就实现了vue中数据绑定的功能。



这篇关于vue核心原理-监测数据变化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程