初步
最近一段时间在阅读 Vue 源码,从它的核心原理入手,开始了源码的学习,而其核心原理就是其数据的响应式,讲到 Vue 的响应式原理,我们可以从它的兼容性说起,Vue不支持 IE8 以下版本的浏览器,因为 Vue 是基于Object.defineProperty来实现数据响应的,而 Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因;Vue 通过 Object.defineProperty 的getter/setter对收集的依赖项进行监听,在属性被访问和修改时通知变化,进而更新视图数据;
受现代 JavaScript 的限制(以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
我们这里是根据Vue2.3源码进行分析,Vue数据响应式变化主要涉及 Observer, Watcher , Dep 这三个主要的类;因此要弄清Vue响应式变化需要明白这个三个类之间是如何运作联系的;以及它们的原理,负责的逻辑操作。那么我们从一个简单的Vue实例的代码来分析Vue的响应式原理
Vue 初始化实例
根据 Vue 的 生命周期我们知道,Vue 首先会进行 init 初始化操作;源码在src/core/instance/init.js中
以上代码可以看到initState(vm)是用来初始化 props、methods、data、computed 和 watch,src/core/instance/state.js
1、initData
现在我们重点分析下initData,这里主要做了两件事,一是将 _data 上面的数据代理到 vm 上,二是通过执行 observe(data, true /* asRootData */)将所有data变成可观察的,即对 data 定义的每个属性进行 getter/setter 操作,这里就是 Vue 实现响应式的基础;observe 的实现如下src/core/observer/index.js
这里 new Observer(value) 就是实现响应式的核心方法之一了,通过它将 data 转变可以成观察的,而这里正是我们开头说的,用了 Object.defineProperty 实现了data的 getter/setter 操作,通过 Watcher 来观察数据的变化,进而更新到视图中。
2、Observer
Observer 类是将每个目标对象(即 data)的键值转换成 getter/setter 形式,用于进行依赖收集以及调度更新。src/core/observer/index.js
首先将 Observer 实例绑定到 data 的 ob 属性上面去,防止重复绑定;
若 data 为数组,先实现对应的变异方法(这里变异方法是指Vue重写了数组的7种原生方法,这里不做赘述,后续再说明),再将数组的每个成员进行 observe,使之成响应式数据;
否则执行 walk() 方法,遍历data所有的数据,进行 getter/setter 绑定,这里的核心方法就是 defineReative(obj, keys[i], obj[keys[i]])
exportfunctiondefineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
/在闭包中定义一个dep对象/
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。/
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/对象的子对象递归进行observe并返回子节点的Observer对象/
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: functionreactiveGetter () {
/如果原本对象拥有getter方法则执行/
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/进行依赖收集/
dep.depend()
if (childOb) {
/子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend/
childOb.dep.depend()
}
if (Array.isArray(value)) {
/是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。/
dependArray(value)
}
}
return value
},
set: functionreactiveSetter (newVal) {
/通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作/
const value = getter ? getter.call(obj) : 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 (setter) {
/如果原本对象拥有setter方法则执行setter/
setter.call(obj, newVal)
} else {
val = newVal
}
/新的值需要重新进行observe,保证数据响应式/
childOb = observe(newVal)
/dep对象通知所有的观察者/
dep.notify()
}
})
}其中 getter 方法:
那么问题来了,我们为啥要收集相关依赖呢?
我们可以从以上代码看出,data 中 text3 并没有被模板实际用到,为了提高代码执行效率,我们没有必要对其进行响应式处理,因此,依赖收集简单点理解就是收集只在实际页面中用到的data数据,然后打上标记,这里就是标记为 Dep.target。
在 setter 方法中:
在 Observer 类中,我们可以看到在 getter 时,dep 会收集相关依赖,即收集依赖的 watcher,然后在 setter 操作时候通过 dep 去通知 watcher,此时 watcher 就执行变化,我们用一张图描述这三者之间的关系。
从图我们可以简单理解:Dep 可以看做是书店,Watcher 就是书店订阅者,而 Observer 就是书店的书,订阅者在书店订阅书籍,就可以添加订阅者信息,一旦有新书就会通过书店给订阅者发送消息。
3、Watcher
Watcher 是一个观察者对象。依赖收集以后 Watcher 对象会被保存在 Dep 的 subs 中,数据变动的时候 Dep 会通知 Watcher 实例,然后由 Watcher 实例回调 cb 进行视图的更新。src/core/observer/watcher.js
exportdefaultclassWatcher{
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/*_watchers存放订阅者实例*/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} 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 = newSet()
this.newDepIds = newSet()
this.expression = process.env.NODE_ENV !== production
? expOrFn.toString()
:
// parse expression for getter
/*把表达式expOrFn解析成getter*/
if (typeof expOrFn === function) {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
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
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
/*获得getter的值并且重新进行依赖收集*/
get () {
/*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
pushTarget(this)
let value
const vm = this.vm
/*执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。
在将Dep.target设置为自生观察者实例以后,执行getter操作。
譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c,
那么在执行getter的时候就会触发a跟c两个数据的getter函数,
在getter函数中即可判断Dep.target是否存在然后完成依赖收集,
将该观察者对象放入闭包中的Dep的subs中去。*/
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher “${this.expression}”`)
}
} else {
value = this.getter.call(vm, vm)
}
// “touch” every property so they are all tracked as
// dependencies for deep watching
/*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/
if (this.deep) {
/*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/
traverse(value)
}
/*将观察者实例从target栈中取出并设置给Dep.target*/
popTarget()
this.cleanupDeps()
return value
}
/**
* Add a dependency to this directive.
*/
/*添加一个依赖关系到Deps集合中*/
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)
}
}
}
/**
* Clean up for dependency collection.
*/
/*清理依赖收集*/
cleanupDeps () {
/*移除所有观察者对象*/
…
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
/*
调度者接口,当依赖发生改变的时候进行回调。
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} elseif (this.sync) {
/*同步则执行run直接渲染视图*/
this.run()
} else {
/*异步推送到观察者队列中,下一个tick时调用。*/
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
/*
调度者工作接口,将被调度者回调。
*/
run () {
if (this.active) {
/* get操作在获取value本身也会执行getter从而调用update更新视图 */
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
/*
即便值相同,拥有Deep属性的观察者以及在对象/数组上的观察者应该被触发更新,因为它们的值可能发生改变。
*/
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
/*设置新的值*/
this.value = value
/*触发回调*/
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher “${this.expression}”`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
/*获取观察者的值*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
/*收集该watcher的所有deps依赖*/
depend () {
let i = this.deps.length
while (i–) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies subscriber list.
*/
/*将自身从所有依赖收集订阅列表删除*/
teardown () {
…
}
}4、Dep
被 Observer 的 data 在触发getter时,Dep就会收集依赖的Watcher,其实Dep就像刚才说的是一个书店,可以接受多个订阅者的订阅,当有新书时即在data变动时,就会通过Dep给Watcher发通知进行更新。src/core/observer/dep.js
总结
其实在 Vue 中初始化渲染时,视图上绑定的数据就会实例化一个 Watcher,依赖收集就是是通过属性的 getter 函数完成的,文章一开始讲到的 Observer 、Watcher 、Dep 都与依赖收集相关。其中 Observer 与 Dep 是一对一的关系, Dep 与 Watcher 是多对多的关系,Dep 则是 Observer 和 Watcher 之间的纽带。依赖收集完成后,当属性变化会执行被 Observer 对象的 dep.notify() 方法,这个方法会遍历订阅者(Watcher)列表向其发送消息, Watcher 会执行 run 方法去更新视图,我们再来看一张图总结一下:
参考
Vue 源码Vue 文档Vue 源码学习
-
扫码下载安卓APP
-
微信扫一扫关注我们
微信扫一扫打开小程序
手Q扫一扫打开小程序
-
返回顶部
发表评论