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

╄→尐↘猪︶ㄣ 提交于 2020-02-26 14:56:16

我们实际开发中发现,在data中定义的所有数据,后续无论是在template中使用,还是在methods中使用,都能随着数据的变化而变化。为了理解这其中的原理,研究源码后整理出这篇文章,欢迎大家及时指正。
第一步:数据注册监听
vue 2.x 版本使用的是 Object.defineProperty 详细API文档见Object.defineProperty
用于绑定Object类型数据,比如定义一个person:
let person = {
name: ‘usm’,
age: 12
}
复制代码现在希望person的name和age发生改变时,可以触发一些操作,就可以通过 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) {
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
  }

  ...
}

})
}
复制代码get和set中省略部分就是数据发生改变后所做的操作。
其中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.depend,depend方法中调用了Watcher类的addDep方法,addDep方法最终调用addSub方法添加依赖并注册监听。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!