先期
前段时间一两年在写作 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) {
操作方式/
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) {
te预览快照 */
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 源标识符自学