Vue 2.0的數(shù)據(jù)依賴(lài)實(shí)現(xiàn)原理代碼簡(jiǎn)析
首先讓我們從最簡(jiǎn)單的一個(gè)實(shí)例Vue入手:
const app = new Vue({
// options 傳入一個(gè)選項(xiàng)obj.這個(gè)obj即對(duì)于這個(gè)vue實(shí)例的初始化
})
通過(guò)查閱文檔,我們可以知道這個(gè)options可以接受:
- 選項(xiàng)/數(shù)據(jù)
- data
- props
- propsData(方便測(cè)試使用)
- computed
- methods
- watch
- 選項(xiàng) / DOM
- 選項(xiàng) / 生命周期鉤子
- 選項(xiàng) / 資源
- 選項(xiàng) / 雜項(xiàng)
具體未展開(kāi)的內(nèi)容請(qǐng)自行查閱相關(guān)文檔,接下來(lái)讓我們來(lái)看看傳入的選項(xiàng)/數(shù)據(jù)是如何管理數(shù)據(jù)之間的相互依賴(lài)的。
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這個(gè)構(gòu)造函數(shù)去實(shí)例化了一個(gè)vue實(shí)例app。傳入了props, data, watch, methods等屬性。在實(shí)例化的過(guò)程中,Vue提供的構(gòu)造函數(shù)就使用我們傳入的options去完成數(shù)據(jù)的依賴(lài)管理,初始化的過(guò)程只有一次,但是在你自己的程序當(dāng)中,數(shù)據(jù)的依賴(lài)管理的次數(shù)不止一次。
那Vue的構(gòu)造函數(shù)到底是怎么實(shí)現(xiàn)的呢?Vue
// 構(gòu)造函數(shù)
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)
}
// 對(duì)Vue這個(gè)class進(jìn)行mixin,即在原型上添加方法
// Vue.prototype.* = function () {}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
當(dāng)我們調(diào)用new Vue的時(shí)候,事實(shí)上就調(diào)用的Vue原型上的_init方法.
// 原型上提供_init方法,新建一個(gè)vue實(shí)例并傳入options參數(shù)
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選項(xiàng)掛載到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 // 自身的實(shí)例
// 接下來(lái)所有的操作都是在這個(gè)實(shí)例上添加方法
initLifecycle(vm) // lifecycle初始化
initEvents(vm) // events初始化 vm._events, 主要是提供vm實(shí)例上的$on/$emit/$off/$off等方法
initRender(vm) // 初始化渲染函數(shù),在vm上綁定$createElement方法
callHook(vm, 'beforeCreate') // 鉤子函數(shù)的執(zhí)行, beforeCreate
initInjections(vm) // resolve injections before data/props
initState(vm) // Observe data添加對(duì)data的監(jiān)聽(tīng), 將data轉(zhuǎn)化為getters/setters
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 鉤子函數(shù)的執(zhí)行, created
// vm掛載的根元素
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
其中在this._init()方法中調(diào)用initState(vm),完成對(duì)vm這個(gè)實(shí)例的數(shù)據(jù)的監(jiān)聽(tīng),也是本文所要展開(kāi)說(shuō)的具體內(nèi)容。
export function initState (vm: Component) {
// 首先在vm上初始化一個(gè)_watchers數(shù)組,緩存這個(gè)vm上的所有watcher
vm._watchers = []
// 獲取options,包括在new Vue傳入的,同時(shí)還包括了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
我們?cè)趯?shí)例化app的時(shí)候,在構(gòu)造函數(shù)里面?zhèn)魅氲膐ptions中有props屬性:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
}
function initProps (vm: Component, propsOptions: Object) {
// propsData主要是為了方便測(cè)試使用
const propsData = vm.$options.propsData || {}
// 新建vm._props對(duì)象,可以通過(guò)app實(shí)例去訪(fǎng)問(wèn)
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)
// 注意這個(gè)validateProp方法,不僅完成了prop屬性類(lèi)型驗(yàn)證的,同時(shí)將prop的值都轉(zhuǎn)化為了getter/setter,并返回一個(gè)observer
const value = validateProp(key, propsOptions, propsData, vm)
// 將這個(gè)key對(duì)應(yīng)的值轉(zhuǎn)化為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這個(gè)實(shí)例上沒(méi)有key屬性,那么就通過(guò)proxy轉(zhuǎn)化為proxyGetter/proxySetter, 并掛載到vm實(shí)例上,可以通過(guò)app._props[key]這種形式去訪(fǎng)問(wèn)
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
observerState.shouldConvert = true
}
接下來(lái)看下validateProp(key, propsOptions, propsData, vm)方法內(nèi)部到底發(fā)生了什么。
export function validateProp (
key: string,
propOptions: Object, // $options.props屬性
propsData: Object, // $options.propsData屬性
vm?: Component
): any {
const prop = propOptions[key]
// 如果在propsData測(cè)試props上沒(méi)有緩存的key
const absent = !hasOwn(propsData, key)
let value = propsData[key]
// 處理boolean類(lèi)型的數(shù)據(jù)
// 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屬性值,是基本類(lèi)型還是function
// getPropsDefaultValue見(jiàn)下面第一段代碼
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的所有屬性轉(zhuǎn)化為getter/setter形式
// 并添加value的依賴(lài)
// observe方法的分析見(jiàn)下面第二段代碼
observe(value)
observerState.shouldConvert = prevShouldConvert
}
if (process.env.NODE_ENV !== 'production') {
assertProp(prop, key, value, vm, absent)
}
return value
}
// 獲取prop的默認(rèn)值
function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
// no default, return undefined
// 如果沒(méi)有default屬性的話(huà),那么就返回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 則調(diào)用def.call(vm)
// 否則就返回default屬性對(duì)應(yīng)的值
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
Vue提供了一個(gè)observe方法,在其內(nèi)部實(shí)例化了一個(gè)Observer類(lèi),并返回Observer的實(shí)例。每一個(gè)Observer實(shí)例對(duì)應(yīng)記錄了props中這個(gè)的default value的所有依賴(lài)(僅限object類(lèi)型),這個(gè)Observer實(shí)際上就是一個(gè)觀察者,它維護(hù)了一個(gè)數(shù)組this.subs = []用以收集相關(guān)的subs(訂閱者)(即這個(gè)觀察者的依賴(lài))。通過(guò)將default value轉(zhuǎn)化為getter/setter形式,同時(shí)添加一個(gè)自定義__ob__屬性,這個(gè)屬性就對(duì)應(yīng)Observer實(shí)例。
說(shuō)起來(lái)有點(diǎn)繞,還是讓我們看看我們給的demo里傳入的options配置:
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
}
在往上數(shù)的第二段代碼里面的方法obervse(value),即對(duì){key1: 'a', key2: {a: 'b'}}進(jìn)行依賴(lài)的管理,同時(shí)將這個(gè)obj所有的屬性值都轉(zhuǎn)化為getter/setter形式。此外,Vue還會(huì)將props屬性都代理到vm實(shí)例上,通過(guò)vm.key1,vm.key2就可以訪(fǎng)問(wèn)到這個(gè)屬性。
此外,還需要了解下在Vue中管理依賴(lài)的一個(gè)非常重要的類(lèi): Dep
export default class Dep {
constructor () {
this.id = uid++
this.subs = []
}
addSub () {...} // 添加訂閱者(依賴(lài))
removeSub () {...} // 刪除訂閱者(依賴(lài))
depend () {...} // 檢查當(dāng)前Dep.target是否存在以及判斷這個(gè)watcher已經(jīng)被添加到了相應(yīng)的依賴(lài)當(dāng)中,如果沒(méi)有則添加訂閱者(依賴(lài)),如果已經(jīng)被添加了那么就不做處理
notify () {...} // 通知訂閱者(依賴(lài))更新
}
在Vue的整個(gè)生命周期當(dāng)中,你所定義的響應(yīng)式的數(shù)據(jù)上都會(huì)綁定一個(gè)Dep實(shí)例去管理其依賴(lài)。它實(shí)際上就是觀察者和訂閱者聯(lián)系的一個(gè)橋梁。
剛才談到了對(duì)于依賴(lài)的管理,它的核心之一就是觀察者Observer這個(gè)類(lèi):
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記錄了和這個(gè)value值的相關(guān)依賴(lài)
this.dep = new Dep()
this.vmCount = 0
// value其實(shí)就是vm._data, 即在vm._data上添加__ob__屬性
def(value, '__ob__', this)
// 如果是數(shù)組
if (Array.isArray(value)) {
// 首先判斷是否能使用__proto__屬性
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
// 遍歷數(shù)組,并將obj類(lèi)型的屬性改為getter/setter實(shí)現(xiàn)
this.observeArray(value)
} else {
// 遍歷obj上的屬性,將每個(gè)屬性改為getter/setter實(shí)現(xiàn)
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// 將每個(gè)property對(duì)應(yīng)的屬性都轉(zhuǎn)化為getter/setters,只能是當(dāng)這個(gè)value的類(lèi)型為Object時(shí)
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.
*/
// 監(jiān)聽(tīng)array中的item
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
walk方法里面調(diào)用defineReactive方法:通過(guò)遍歷這個(gè)object的key,并將對(duì)應(yīng)的value轉(zhuǎn)化為getter/setter形式,通過(guò)閉包維護(hù)一個(gè)dep,在getter方法當(dāng)中定義了這個(gè)key是如何進(jìn)行依賴(lài)的收集,在setter方法中定義了當(dāng)這個(gè)key對(duì)應(yīng)的值改變后,如何完成相關(guān)依賴(lài)數(shù)據(jù)的更新。但是從源碼當(dāng)中,我們卻發(fā)現(xiàn)當(dāng)getter函數(shù)被調(diào)用的時(shí)候并非就一定會(huì)完成依賴(lài)的收集,其中還有一層判斷,就是Dep.target是否存在。
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
// 每個(gè)屬性新建一個(gè)dep實(shí)例,管理這個(gè)屬性的依賴(lài)
const dep = new Dep()
// 或者屬性描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
// 如果這個(gè)屬性是不可配的,即無(wú)法更改
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
// 遞歸去將val轉(zhuǎn)化為getter/setter
// childOb將子屬性也轉(zhuǎn)化為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
// 定義相應(yīng)的依賴(lài)
if (Dep.target) {
// Dep.target.addDep(this)
// 即添加watch函數(shù)
// dep.depend()及調(diào)用了dep.addSub()只不過(guò)中間需要判斷是否這個(gè)id的dep已經(jīng)被包含在內(nèi)了
dep.depend()
// childOb也添加依賴(lài)
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
}
// 對(duì)得到的新值進(jìn)行observe
childOb = observe(newVal)
// 相應(yīng)的依賴(lài)進(jìn)行更新
dep.notify()
}
})
}
在上文中提到了Dep類(lèi)是鏈接觀察者和訂閱者的橋梁。同時(shí)在Dep的實(shí)現(xiàn)當(dāng)中還有一個(gè)非常重要的屬性就是Dep.target,它事實(shí)就上就是一個(gè)訂閱者,只有當(dāng)Dep.target(訂閱者)存在的時(shí)候,調(diào)用屬性的getter函數(shù)的時(shí)候才能完成依賴(lài)的收集工作。
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是如何來(lái)實(shí)現(xiàn)訂閱者的呢?Vue里面定義了一個(gè)類(lèi): Watcher,在Vue的整個(gè)生命周期當(dāng)中,會(huì)有4類(lèi)地方會(huì)實(shí)例化Watcher:
- Vue實(shí)例化的過(guò)程中有watch選項(xiàng)
- Vue實(shí)例化的過(guò)程中有computed計(jì)算屬性選項(xiàng)
- Vue原型上有掛載$watch方法: Vue.prototype.$watch,可以直接通過(guò)實(shí)例調(diào)用this.$watch方法
- Vue生成了render函數(shù),更新視圖時(shí)
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
// 緩存這個(gè)實(shí)例vm
this.vm = vm
// vm實(shí)例中的_watchers中添加這個(gè)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 () {}
}
}
// 通過(guò)get方法去獲取最新的值
// 如果lazy為true, 初始化的時(shí)候?yàn)閡ndefined
this.value = this.lazy
? undefined
: this.get()
}
get () {...}
addDep () {...}
update () {...}
run () {...}
evaluate () {...}
run () {...}
Watcher接收的參數(shù)當(dāng)中expOrFn定義了用以獲取watcher的getter函數(shù)。expOrFn可以有2種類(lèi)型:string或function.若為string類(lèi)型,首先會(huì)通過(guò)parsePath方法去對(duì)string進(jìn)行分割(僅支持.號(hào)形式的對(duì)象訪(fǎng)問(wèn))。在除了computed選項(xiàng)外,其他幾種實(shí)例化watcher的方式都是在實(shí)例化過(guò)程中完成求值及依賴(lài)的收集工作:this.value = this.lazy ? undefined : this.get().在Watcher的get方法中:
!!!前方高能
get () {
// pushTarget即設(shè)置當(dāng)前的需要被執(zhí)行的watcher
pushTarget(this)
let value
const vm = this.vm
if (this.user) {
try {
// $watch(function () {})
// 調(diào)用this.getter的時(shí)候,觸發(fā)了屬性的getter函數(shù)
// 在getter中進(jìn)行了依賴(lài)的管理
value = this.getter.call(vm, vm)
console.log(value)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
// 如果是新建模板函數(shù),則會(huì)動(dòng)態(tài)計(jì)算模板與data中綁定的變量,這個(gè)時(shí)候就調(diào)用了getter函數(shù),那么就完成了dep的收集
// 調(diào)用getter函數(shù),則同時(shí)會(huì)調(diào)用函數(shù)內(nèi)部的getter的函數(shù),進(jìn)行dep收集工作
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
// 讓每個(gè)屬性都被作為dependencies而tracked, 這樣是為了deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
一進(jìn)入get方法,首先進(jìn)行pushTarget(this)的操作,此時(shí)Vue當(dāng)中Dep.target = 當(dāng)前這個(gè)watcher,接下來(lái)進(jìn)行value = this.getter.call(vm, vm)操作,在這個(gè)操作中就完成了依賴(lài)的收集工作。還是拿文章一開(kāi)始的demo來(lái)說(shuō),在vue實(shí)例化的時(shí)候傳入了watch選項(xiàng):
props: {
a: {
type: Object,
default () {
return {
key1: 'a',
key2: {
a: 'b'
}
}
}
}
},
watch: {
a (newVal, oldVal) {
console.log(newVal, oldVal)
}
},
在Vue的initState()開(kāi)始執(zhí)行后,首先會(huì)初始化props的屬性為getter/setter函數(shù),然后在進(jìn)行initWatch初始化的時(shí)候,這個(gè)時(shí)候初始化watcher實(shí)例,并調(diào)用get()方法,設(shè)置Dep.target = 當(dāng)前這個(gè)watcher實(shí)例,進(jìn)而到value = this.getter.call(vm, vm)的操作。在調(diào)用this.getter.call(vm, vm)的方法中,便會(huì)訪(fǎng)問(wèn)props選項(xiàng)中的a屬性即其getter函數(shù)。在a屬性的getter函數(shù)執(zhí)行過(guò)程中,因?yàn)镈ep.target已經(jīng)存在,那么就進(jìn)入了依賴(lài)收集的過(guò)程:
if (Dep.target) {
// Dep.target.addDep(this)
// 即添加watch函數(shù)
// dep.depend()及調(diào)用了dep.addSub()只不過(guò)中間需要判斷是否這個(gè)id的dep已經(jīng)被包含在內(nèi)了
dep.depend()
// childOb也添加依賴(lài)
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
dep是一開(kāi)始初始化的過(guò)程中,這個(gè)屬性上的dep屬性。調(diào)用dep.depend()函數(shù):
depend () {
if (Dep.target) {
// Dep.target為一個(gè)watcher
Dep.target.addDep(this)
}
}
Dep.target也就剛才的那個(gè)watcher實(shí)例,這里也就相當(dāng)于調(diào)用了watcher實(shí)例的addDep方法: watcher.addDep(this),并將dep觀察者傳入。在addDep方法中完成依賴(lài)收集:
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)
}
}
}
這個(gè)時(shí)候依賴(lài)完成了收集,當(dāng)你去修改a屬性的值時(shí),會(huì)調(diào)用a屬性的setter函數(shù),里面會(huì)執(zhí)行dep.notify(),它會(huì)遍歷所有的訂閱者,然后調(diào)用訂閱者上的update函數(shù)。
initData過(guò)程和initProps類(lèi)似,具體可參見(jiàn)源碼。
initComputed
以上就是在initProps過(guò)程中Vue是如何進(jìn)行依賴(lài)收集的,initData的過(guò)程和initProps類(lèi)似,下來(lái)再來(lái)看看initComputed的過(guò)程.
在computed屬性初始化的過(guò)程當(dāng)中,會(huì)為每個(gè)屬性實(shí)例化一個(gè)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,即取這個(gè)function為getter函數(shù)
// 如果computed為非function.則可以單獨(dú)為這個(gè)屬性定義getter/setter屬性
let getter = typeof userDef === 'function' ? userDef : userDef.get
// create internal watcher for the computed property.
// lazy屬性為true
// 注意這個(gè)地方傳入的getter參數(shù)
// 實(shí)例化的過(guò)程當(dāng)中不去完成依賴(lài)的收集工作
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)
}
}
}
但是這個(gè)watcher在實(shí)例化的過(guò)程中,由于傳入了{(lán)lazy: true}的配置選項(xiàng),那么一開(kāi)始是不會(huì)進(jìn)行求值與依賴(lài)收集的: this.value = this.lazy ? undefined : this.get().在initComputed的過(guò)程中,Vue會(huì)將computed屬性定義到vm實(shí)例上,同時(shí)將這個(gè)屬性定義為getter/setter。當(dāng)你訪(fǎng)問(wèn)computed屬性的時(shí)候調(diào)用getter函數(shù):
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 是否需要重新計(jì)算
if (watcher.dirty) {
watcher.evaluate()
}
// 管理依賴(lài)
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
在watcher存在的情況下,首先判斷watcher.dirty屬性,這個(gè)屬性主要是用于判斷這個(gè)computed屬性是否需要重新求值,因?yàn)樵谏弦惠喌囊蕾?lài)收集的過(guò)程當(dāng)中,觀察者已經(jīng)將這個(gè)watcher添加到依賴(lài)數(shù)組當(dāng)中了,如果觀察者發(fā)生了變化,就會(huì)dep.notify(),通知所有的watcher,而對(duì)于computed的watcher接收到變化的請(qǐng)求后,會(huì)將watcher.dirty = true即表明觀察者發(fā)生了變化,當(dāng)再次調(diào)用computed屬性的getter函數(shù)的時(shí)候便會(huì)重新計(jì)算,否則還是使用之前緩存的值。
initWatch
initWatch的過(guò)程中其實(shí)就是實(shí)例化new Watcher完成觀察者的依賴(lài)收集的過(guò)程,在內(nèi)部的實(shí)現(xiàn)當(dāng)中是調(diào)用了原型上的Vue.prototype.$watch方法。這個(gè)方法也適用于vm實(shí)例,即在vm實(shí)例內(nèi)部調(diào)用this.$watch方法去實(shí)例化watcher,完成依賴(lài)的收集,同時(shí)監(jiān)聽(tīng)expOrFn的變化。
總結(jié):
以上就是在Vue實(shí)例初始化的過(guò)程中實(shí)現(xiàn)依賴(lài)管理的分析。大致的總結(jié)下就是:
- initState的過(guò)程中,將props,computed,data等屬性通過(guò)Object.defineProperty來(lái)改造其getter/setter屬性,并為每一個(gè)響應(yīng)式屬性實(shí)例化一個(gè)observer觀察者。這個(gè)observer內(nèi)部dep記錄了這個(gè)響應(yīng)式屬性的所有依賴(lài)。
- 當(dāng)響應(yīng)式屬性調(diào)用setter函數(shù)時(shí),通過(guò)dep.notify()方法去遍歷所有的依賴(lài),調(diào)用watcher.update()去完成數(shù)據(jù)的動(dòng)態(tài)響應(yīng)。
這篇文章主要從初始化的數(shù)據(jù)層面上分析了Vue是如何管理依賴(lài)來(lái)到達(dá)數(shù)據(jù)的動(dòng)態(tài)響應(yīng)。下一篇文章來(lái)分析下Vue中模板中的指令和響應(yīng)式數(shù)據(jù)是如何關(guān)聯(lián)來(lái)實(shí)現(xiàn)由數(shù)據(jù)驅(qū)動(dòng)視圖,以及數(shù)據(jù)是如何響應(yīng)視圖變化的。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- 深入理解vue.js雙向綁定的實(shí)現(xiàn)原理
- 解析Vue2.0雙向綁定實(shí)現(xiàn)原理
- 如何理解Vue的作用域插槽的實(shí)現(xiàn)原理
- Vue底層實(shí)現(xiàn)原理總結(jié)
- 淺談vue中數(shù)據(jù)雙向綁定的實(shí)現(xiàn)原理
- Vue.js雙向綁定實(shí)現(xiàn)原理詳解
- Vue中的基礎(chǔ)過(guò)渡動(dòng)畫(huà)及實(shí)現(xiàn)原理解析
- Vue分頁(yè)器實(shí)現(xiàn)原理詳解
- 淺析vue中的MVVM實(shí)現(xiàn)原理
- 淺析vue-router實(shí)現(xiàn)原理及兩種模式
- Vue簡(jiǎn)單實(shí)現(xiàn)原理詳解
相關(guān)文章
vue學(xué)習(xí)筆記五:在vue項(xiàng)目里面使用引入公共方法詳解
這篇文章主要介紹了在vue項(xiàng)目里面使用引入公共方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
詳解使用jest對(duì)vue項(xiàng)目進(jìn)行單元測(cè)試
這篇文章主要介紹了詳解使用jest對(duì)vue項(xiàng)目進(jìn)行單元測(cè)試,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
axios無(wú)法加載響應(yīng)數(shù)據(jù):no?data?found?for?resource?with?given?i
最近在在做一個(gè)小查詢(xún)功能的時(shí)候踩了一個(gè)坑,所以這篇文章主要給大家介紹了關(guān)于axios無(wú)法加載響應(yīng)數(shù)據(jù):no?data?found?for?resource?with?given?identifier報(bào)錯(cuò)的解決方法,需要的朋友可以參考下2022-11-11
vue函數(shù)input輸入值請(qǐng)求時(shí)延遲1.5秒請(qǐng)求問(wèn)題
這篇文章主要介紹了vue函數(shù)input輸入值請(qǐng)求時(shí)延遲1.5秒請(qǐng)求問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
Vue倒計(jì)時(shí)3秒后返回首頁(yè)Demo(推薦)
這篇文章主要介紹了Vue倒計(jì)時(shí)3秒后返回首頁(yè)Demo,倒計(jì)時(shí)結(jié)束后要清除計(jì)時(shí)器,防止內(nèi)存泄漏,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-11-11
使用vue-aplayer插件時(shí)出現(xiàn)的問(wèn)題的解決
這篇文章主要介紹了使用vue-aplayer插件時(shí)出現(xiàn)的問(wèn)題的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
vue3解決跨域問(wèn)題詳細(xì)代碼親測(cè)有效
跨域,跨的是不同域,也就是協(xié)議或主機(jī)或或端口號(hào)不同造成的現(xiàn)象,本文給大家分享vue3解決跨域問(wèn)題詳細(xì)代碼親測(cè)有效,感興趣的朋友跟隨小編一起看看吧2022-11-11

