首先讓我們從最簡單的一個執行個體 Vue
入手:
const app = new Vue({
// options 傳入一個選項obj.這個obj即對于這個vue執行個體的初始化
})
通過查閱文檔,我們可以知道這個
options
可以接受:
- 選項/資料
- data
- props
- propsData(友善測試使用)
- computed
- methods
- watch
- 選項 / DOM
- 選項 / 生命周期鈎子
- 選項 / 資源
- 選項 / 雜項
具體未展開的内容請自行查閱相關文檔,接下來讓我們來看看傳入的
選項/資料
是如何管理資料之間的互相依賴的。
const app = new Vue({
el: '#app',
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
},
data: {
msg1: 'Hello world!',
arr: {
arr1: 1
}
},
watch: {
a (newVal, oldVal) {
console.log(newVal, oldVal)
}
},
methods: {
go () {
console.log('This is simple demo')
}
}
})
我們使用
Vue
這個構造函數去執行個體化了一個
vue
執行個體
app
。傳入了
props
,
data
watch
methods
等屬性。在執行個體化的過程中,
Vue
提供的構造函數就使用我們傳入的
options
去完成資料的依賴管理,初始化的過程隻有一次,但是在你自己的程式當中,資料的依賴管理的次數不止一次。
那
Vue
的構造函數到底是怎麼實作的呢?Vue
// 構造函數
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// 對Vue這個class進行mixin,即在原型上添加方法
// Vue.prototype.* = function () {}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
當我們調用
new Vue
的時候,事實上就調用的
Vue
原型上的
_init
方法.
// 原型上提供_init方法,建立一個vue執行個體并傳入options參數
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
// 将傳入的這些options選項挂載到vm.$options屬性上
vm.$options = mergeOptions(
// components/filter/directive
resolveConstructorOptions(vm.constructor),
// this._init()傳入的options
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm // 自身的執行個體
// 接下來所有的操作都是在這個執行個體上添加方法
initLifecycle(vm) // lifecycle初始化
initEvents(vm) // events初始化 vm._events, 主要是提供vm執行個體上的$on/$emit/$off/$off等方法
initRender(vm) // 初始化渲染函數,在vm上綁定$createElement方法
callHook(vm, 'beforeCreate') // 鈎子函數的執行, beforeCreate
initInjections(vm) // resolve injections before data/props
initState(vm) // Observe data添加對data的監聽, 将data轉化為getters/setters
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 鈎子函數的執行, created
// vm挂載的根元素
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
其中在
this._init()
方法中調用
initState(vm)
,完成對
vm
這個執行個體的資料的監聽,也是本文所要展開說的具體内容。
export function initState (vm: Component) {
// 首先在vm上初始化一個_watchers數組,緩存這個vm上的所有watcher
vm._watchers = []
// 擷取options,包括在new Vue傳入的,同時還包括了Vue所繼承的options
const opts = vm.$options
// 初始化props屬性
if (opts.props) initProps(vm, opts.props)
// 初始化methods屬性
if (opts.methods) initMethods(vm, opts.methods)
// 初始化data屬性
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
// 初始化computed屬性
if (opts.computed) initComputed(vm, opts.computed)
// 初始化watch屬性
if (opts.watch) initWatch(vm, opts.watch)
}
initProps
我們在執行個體化
app
的時候,在構造函數裡面傳入的
options
中有
props
屬性:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
}
function initProps (vm: Component, propsOptions: Object) {
// propsData主要是為了友善測試使用
const propsData = vm.$options.propsData || {}
// 建立vm._props對象,可以通過app執行個體去通路
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
// 緩存的prop key
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
observerState.shouldConvert = isRoot
for (const key in propsOptions) {
// this._init傳入的options中的props屬性
keys.push(key)
// 注意這個validateProp方法,不僅完成了prop屬性類型驗證的,同時将prop的值都轉化為了getter/setter,并傳回一個observer
const value = validateProp(key, propsOptions, propsData, vm)
// 将這個key對應的值轉化為getter/setter
defineReactive(props, key, value)
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
// 如果在vm這個執行個體上沒有key屬性,那麼就通過proxy轉化為proxyGetter/proxySetter, 并挂載到vm執行個體上,可以通過app._props[key]這種形式去通路
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
observerState.shouldConvert = true
}
接下來看下
validateProp(key, propsOptions, propsData, vm)
方法内部到底發生了什麼。
export function validateProp (
key: string,
propOptions: Object, // $options.props屬性
propsData: Object, // $options.propsData屬性
vm?: Component
): any {
const prop = propOptions[key]
// 如果在propsData測試props上沒有緩存的key
const absent = !hasOwn(propsData, key)
let value = propsData[key]
// 處理boolean類型的資料
// handle boolean props
if (isType(Boolean, prop.type)) {
if (absent && !hasOwn(prop, 'default')) {
value = false
} else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {
value = true
}
}
// check default value
if (value === undefined) {
// default屬性值,是基本類型還是function
// getPropsDefaultValue見下面第一段代碼
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldConvert = observerState.shouldConvert
observerState.shouldConvert = true
// 将value的所有屬性轉化為getter/setter形式
// 并添加value的依賴
// observe方法的分析見下面第二段代碼
observe(value)
observerState.shouldConvert = prevShouldConvert
}
if (process.env.NODE_ENV !== 'production') {
assertProp(prop, key, value, vm, absent)
}
return value
}
// 擷取prop的預設值
function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
// no default, return undefined
// 如果沒有default屬性的話,那麼就傳回undefined
if (!hasOwn(prop, 'default')) {
return undefined
}
const def = prop.default
// the raw prop value was also undefined from previous render,
// return previous default value to avoid unnecessary watcher trigger
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined) {
return vm._props[key]
}
// call factory function for non-Function types
// a value is Function if its prototype is function even across different execution context
// 如果是function 則調用def.call(vm)
// 否則就傳回default屬性對應的值
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
Vue
提供了一個
observe
方法,在其内部執行個體化了一個
Observer
類,并傳回
Observer
的執行個體。每一個
Observer
執行個體對應記錄了
props
中這個的
default value
的所有依賴(僅限
object
類型),這個
Observer
實際上就是一個觀察者,它維護了一個數組
this.subs = []
用以收集相關的
subs(訂閱者)
(即這個觀察者的依賴)。通過将
default value
轉化為
getter/setter
形式,同時添加一個自定義
__ob__
屬性,這個屬性就對應
Observer
執行個體。
說起來有點繞,還是讓我們看看我們給的
demo
裡傳入的
options
配置:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
}
在往上數的第二段代碼裡面的方法
obervse(value)
,即對
{key1: 'a', key2: {a: 'b'}}
進行依賴的管理,同時将這個
obj
所有的屬性值都轉化為
getter/setter
形式。此外,
Vue
還會将
props
屬性都代理到
vm
執行個體上,通過
vm.key1
,
vm.key2
就可以通路到這個屬性。
此外,還需要了解下在
Vue
中管理依賴的一個非常重要的類:
Dep
export default class Dep {
constructor () {
this.id = uid++
this.subs = []
}
addSub () {...} // 添加訂閱者(依賴)
removeSub () {...} // 删除訂閱者(依賴)
depend () {...} // 檢查目前Dep.target是否存在以及判斷這個watcher已經被添加到了相應的依賴當中,如果沒有則添加訂閱者(依賴),如果已經被添加了那麼就不做處理
notify () {...} // 通知訂閱者(依賴)更新
}
在
Vue
的整個生命周期當中,你所定義的響應式的資料上都會綁定一個
Dep
執行個體去管理其依賴。它實際上就是
觀察者
和
訂閱者
聯系的一個橋梁。
剛才談到了對于依賴的管理,它的核心之一就是觀察者
Observer
這個類:
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
// dep記錄了和這個value值的相關依賴
this.dep = new Dep()
this.vmCount = 0
// value其實就是vm._data, 即在vm._data上添加__ob__屬性
def(value, '__ob__', this)
// 如果是數組
if (Array.isArray(value)) {
// 首先判斷是否能使用__proto__屬性
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
// 周遊數組,并将obj類型的屬性改為getter/setter實作
this.observeArray(value)
} else {
// 周遊obj上的屬性,将每個屬性改為getter/setter實作
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// 将每個property對應的屬性都轉化為getter/setters,隻能是當這個value的類型為Object時
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
// 監聽array中的item
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
walk
方法裡面調用
defineReactive
方法:通過周遊這個
object
的
key
,并将對應的
value
getter/setter
形式,通過閉包維護一個
dep
,在
getter
方法當中定義了這個
key
是如何進行依賴的收集,在
setter
方法中定義了當這個
key
對應的值改變後,如何完成相關依賴資料的更新。但是從源碼當中,我們卻發現當
getter
函數被調用的時候并非就一定會完成依賴的收集,其中還有一層判斷,就是
Dep.target
是否存在。
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
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
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
// 遞歸去将val轉化為getter/setter
// childOb将子屬性也轉化為Observer
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 定義getter -->> reactiveGetter
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 定義相應的依賴
if (Dep.target) {
// Dep.target.addDep(this)
// 即添加watch函數
// dep.depend()及調用了dep.addSub()隻不過中間需要判斷是否這個id的dep已經被包含在内了
dep.depend()
// childOb也添加依賴
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
return value
},
// 定義setter -->> reactiveSetter
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 對得到的新值進行observe
childOb = observe(newVal)
// 相應的依賴進行更新
dep.notify()
}
})
}
在上文中提到了
Dep
類是連結
觀察者
訂閱者
的橋梁。同時在
Dep
的實作當中還有一個非常重要的屬性就是
Dep.target
,它事實就上就是一個訂閱者,隻有當
Dep.target
(訂閱者)存在的時候,調用屬性的
getter
函數的時候才能完成依賴的收集工作。
Dep.target = null
const targetStack = []
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
那麼
Vue
是如何來實作
訂閱者
的呢?
Vue
裡面定義了一個類:
Watcher
Vue
的整個生命周期當中,會有4類地方會執行個體化
Watcher
:
-
執行個體化的過程中有Vue
選項watch
-
Vue
計算屬性選項computed
-
原型上有挂載Vue
方法: Vue.prototype.$watch,可以直接通過執行個體調用$watch
方法this.$watch
-
生成了Vue
函數,更新視圖時render
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
// 緩存這個執行個體vm
this.vm = vm
// vm執行個體中的_watchers中添加這個watcher
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
....
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
}
}
// 通過get方法去擷取最新的值
// 如果lazy為true, 初始化的時候為undefined
this.value = this.lazy
? undefined
: this.get()
}
get () {...}
addDep () {...}
update () {...}
run () {...}
evaluate () {...}
run () {...}
Watcher
接收的參數當中
expOrFn
定義了用以擷取
watcher
getter
函數。
expOrFn
可以有2種類型:
string
或
function
.若為
string
類型,首先會通過
parsePath
方法去對
string
進行分割(僅支援
.
号形式的對象通路)。在除了
computed
選項外,其他幾種執行個體化
watcher
的方式都是在執行個體化過程中完成求值及依賴的收集工作:
this.value = this.lazy ? undefined : this.get()
.在
Watcher
get
方法中:
!!!前方高能
get () {
// pushTarget即設定目前的需要被執行的watcher
pushTarget(this)
let value
const vm = this.vm
if (this.user) {
try {
// $watch(function () {})
// 調用this.getter的時候,觸發了屬性的getter函數
// 在getter中進行了依賴的管理
value = this.getter.call(vm, vm)
console.log(value)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
// 如果是建立模闆函數,則會動态計算模闆與data中綁定的變量,這個時候就調用了getter函數,那麼就完成了dep的收集
// 調用getter函數,則同時會調用函數内部的getter的函數,進行dep收集工作
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
// 讓每個屬性都被作為dependencies而tracked, 這樣是為了deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
一進入
get
方法,首先進行
pushTarget(this)
的操作,此時
Vue
當中
Dep.target = 目前這個watcher
,接下來進行
value = this.getter.call(vm, vm)
操作,在這個操作中就完成了依賴的收集工作。還是拿文章一開始的
demo
來說,在
vue
執行個體化的時候傳入了
watch
選項:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
},
watch: {
a (newVal, oldVal) {
console.log(newVal, oldVal)
}
},
Vue
initState()
開始執行後,首先會初始化
props
的屬性為
getter/setter
函數,然後在進行
initWatch
初始化的時候,這個時候初始化
watcher
執行個體,并調用
get()
方法,設定
Dep.target = 目前這個watcher執行個體
,進而到
value = this.getter.call(vm, vm)
的操作。在調用
this.getter.call(vm, vm)
的方法中,便會通路
props
選項中的
a
屬性即其
getter
函數。在
a
屬性的
getter
函數執行過程中,因為
Dep.target
已經存在,那麼就進入了
依賴收集
的過程:
if (Dep.target) {
// Dep.target.addDep(this)
// 即添加watch函數
// dep.depend()及調用了dep.addSub()隻不過中間需要判斷是否這個id的dep已經被包含在内了
dep.depend()
// childOb也添加依賴
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
dep
是一開始初始化的過程中,這個屬性上的
dep
屬性。調用
dep.depend()
函數:
depend () {
if (Dep.target) {
// Dep.target為一個watcher
Dep.target.addDep(this)
}
}
Dep.target
也就剛才的那個
watcher
執行個體,這裡也就相當于調用了
watcher
執行個體的
addDep
方法:
watcher.addDep(this)
,并将
dep
觀察者傳入。在
addDep
方法中完成依賴收集:
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)
}
}
}
這個時候依賴完成了收集,當你去修改
a
屬性的值時,會調用
a
setter
函數,裡面會執行
dep.notify()
,它會周遊所有的訂閱者,然後調用訂閱者上的
update
函數。
initData
過程和
initProps
類似,具體可參見源碼。
initComputed
以上就是在
initProps
過程中
Vue
是如何進行依賴收集的,
initData
的過程和
initProps
類似,下來再來看看
initComputed
的過程.
computed
屬性初始化的過程當中,會為每個屬性執行個體化一個
watcher
:
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
// 建立_computedWatchers屬性
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
// 如果computed為funtion,即取這個function為getter函數
// 如果computed為非function.則可以單獨為這個屬性定義getter/setter屬性
let getter = typeof userDef === 'function' ? userDef : userDef.get
// create internal watcher for the computed property.
// lazy屬性為true
// 注意這個地方傳入的getter參數
// 執行個體化的過程當中不去完成依賴的收集工作
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
但是這個
watcher
在執行個體化的過程中,由于傳入了
{lazy: true}
的配置選項,那麼一開始是不會進行求值與依賴收集的:
this.value = this.lazy ? undefined : this.get()
initComputed
的過程中,
Vue
會将
computed
屬性定義到
vm
執行個體上,同時将這個屬性定義為
getter/setter
。當你通路
computed
屬性的時候調用
getter
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 是否需要重新計算
if (watcher.dirty) {
watcher.evaluate()
}
// 管理依賴
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
watcher
存在的情況下,首先判斷
watcher.dirty
屬性,這個屬性主要是用于判斷這個
computed
屬性是否需要重新求值,因為在上一輪的依賴收集的過程當中,觀察者已經将這個
watcher
添加到依賴數組當中了,如果觀察者發生了變化,就會
dep.notify()
,通知所有的
watcher
,而對于
computed
watcher
接收到變化的請求後,會将
watcher.dirty = true
即表明觀察者發生了變化,當再次調用
computed
getter
函數的時候便會重新計算,否則還是使用之前緩存的值。
initWatch
initWatch
的過程中其實就是執行個體化
new Watcher
完成觀察者的依賴收集的過程,在内部的實作當中是調用了原型上的
Vue.prototype.$watch
方法。這個方法也适用于
vm
執行個體,即在
vm
執行個體内部調用
this.$watch
方法去執行個體化
watcher
,完成依賴的收集,同時監聽
expOrFn
的變化。
總結:
Vue
執行個體初始化的過程中實作依賴管理的分析。大緻的總結下就是:
-
的過程中,将initState
,props
computed
等屬性通過data
來改造其Object.defineProperty
屬性,并為每一個響應式屬性執行個體化一個getter/setter
觀察者。這個observer
内部observer
記錄了這個響應式屬性的所有依賴。dep
- 當響應式屬性調用
函數時,通過setter
方法去周遊所有的依賴,調用dep.notify()
去完成資料的動态響應。watcher.update()
這篇文章主要從初始化的資料層面上分析了
Vue
是如何管理依賴來到達資料的動态響應。下一篇文章來分析下
Vue
中模闆中的指令和響應式資料是如何關聯來實作由資料驅動視圖,以及資料是如何響應視圖變化的。