前言
读这篇文章前,最好是先读vue数据绑定源码,因为本篇是接这章写的,放在一篇文章里,篇幅太大,我只好分成两章了。
初始化vue实例
Vue.prototype._init = function (options) {
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化数据的入口
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 挂载组件
}
}
// mountComponent是在挂载组件时调用的方法
export function mountComponent (vm, el ,hydrating) {
callHook(vm, 'beforeMount')
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
initState
上一篇文章,我们已经了解了Observer
、Dep
、Watcher
都是负责什么?如何互相协作?
接下来,我们从数据的入口开始,了解vue是如何使用这几个类完成的数据驱动视图更新的?
初始化state,就是初始化这几个属性。
先讲下什么是可观察者对象呢?
具备两个条件:
1、取值的时候,能把要取值的watcher
(观察者对象)加入它的dep
(依赖,也可叫观察者管理器)管理的subs
列表里(即观察者列表);
2、设置值的时候,有了变化,所有依赖于它的对象(即它的dep
里收集到的观察者watcher
)都得到通知。watcher
里面存储它都观察了谁(dep
),dep
里面存储了都谁观察了自己。
initProps
循环每个props
属性,对每个属性调用defineReactive
,把每个属性加上get
和set
装饰器,变为可观察者对象,如果属性值是对象也会递归转化。
initData
observe
就是循环把data
中的所有项都转换成可观察者对象,如果子项是对象或数组就递归转化,确保data
里的每一项及其后代都转化成了可观察者对象
初始化数据时,不管data里的数据渲染用没用到,都会转成可观察者对象。
对于props
和data
,只有哪个watcher
用到了去读取时,才会把该watcher
加到他的观察者列表中。
data
和props
里都调用了proxy
这个方法,他是做什么的呢?proxy(vm,’_data’,key)
proxy
就是把data
和props
下的属性都代理到了vm实例下,vm._data.a
等价于vm.a
原理就是Object.defineProperty
给vm
的key
属性设置get
和set
方法,当访问get
的时候,返回的是vm._data[key]
;当访问set
的时候,设置的是vm._data[key]
的值。
initComputed
初始化计算属性,就是为每一个计算属性定义一个Watcher
观察者对象。这个对象是lazy
的,不会立即就去执行计算(即get方法),等到用的时候才会去计算,这个时候就会去读取这个计算属性依赖的可观察属性的值来计算,读取的时候就会把这些依赖添加进这个计算watcher
里,同时这些依赖的订阅者列表也会加入这个计算watcher
。所以当依赖变化时,通知到他的所有订阅watcher
。计算watcher
接到依赖发生变化了,不会立即计算新值,而是标记dirty
为true
,读取这个计算属性的时候,发现dirty
为true
,就是说数据已经不是最新的了,需要重新计算,然后才去计算,否则直接取上一次计算的值value
。
如果某个data
通过计算属性间接的被用到了渲染里,那么这个data
也会被加入到渲染watcher
的依赖列表,它的订阅者列表也会保存渲染watcher
。
只有当模版里使用了该计算属性,这个计算属性依赖的可观察者才会被加入到渲染watcher
的依赖列表。如果模版里没有直接或间接用到可观察者对象属性,那么当你set
它的时候,也就不会触发更新操作。
initWatch
初始化watch
,就是为每个watch
属性创建一个观察者对象,这个expOrFn
解析取值表达式去取值,然后就会调用相关data/prop
属性的get
方法,get
方法又会在他的观察者列表里加上该watcher
,一旦这些依赖属性值变化就会通知该watcher
执行update
方法。即会执行他的回调方法cb
,也就是watch
属性的handler
方法。
到这里,数据的初始化就完成了。
mountComponent
这个方法是在,所有的数据初始化完成后,执行挂载组件时调用,创建一个渲染watcher
,每个组件有且仅有一个渲染watcher
。updateComponent
是watcher
的getter
属性,创建后,立即调用get
方法,即是调用updateComponent
,也就是调用render
方法,render
里使用了的数据就会读取相关的data
的get
方法,就会把data
的依赖加进这个渲染watcher
的依赖列表里,data
的subs
也会加入渲染watcher
,如此,当设置data
时拦截的set
方法就会通知渲染watcher
调用update
方法。
我总结下:
一个数据变更后,通知他的Watcher去执行update。
不同类型的Watcher职责不同,vue里的Watcher可以分为3类:
渲染Watcher、计算Watcher、侦听器Watcher,注意这3中Watcher的getter属性分别是什么。
- 渲染Watcher的update,负责重新渲染,执行render;getter是updateComponent。
- 计算Watcher的update,负责标记dirty,告诉它数据不是最新的需要重新计算了;getter是计算属性的get方法。
- 侦听器Watcher的update,负责执行回调方法,也就是watch的handler;getter是watch的取值表达式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。