vue數(shù)據(jù)初始化initState的實(shí)例詳解
數(shù)據(jù)初始化
Vue 實(shí)例在建立的時(shí)候會(huì)運(yùn)行一系列的初始化操作,而在這些初始化操作里面,和數(shù)據(jù)綁定關(guān)聯(lián)最大的是 initState。
首先,來看一下他的代碼:
function initState(vm) {
vm._watchers = [];
var opts = vm.$options;
if(opts.props) {
initProps(vm, opts.props); //初始化props
}
if(opts.methods) {
initMethods(vm, opts.methods); //初始化methods
}
if(opts.data) {
initData(vm); //初始化data
} else {
observe(vm._data = {}, true /* asRootData */ );
}
if(opts.computed) {
initComputed(vm, opts.computed); //初始化computed
}
if(opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch); //初始化watch
}
}
在這么多的數(shù)據(jù)的初始化中,props、methods和data是比較簡單的(所以我就不詳細(xì)介紹了☺),而computed 和 watch則相對較難,邏輯較復(fù)雜,所以我下面主要講下computed 和 watch(以下代碼部分為簡化后的)。
initState里面主要是對vue實(shí)例中的 props, methods, data, computed 和 watch 數(shù)據(jù)進(jìn)行初始化。
在初始化props的時(shí)候(initProps),會(huì)遍歷props中的每個(gè)屬性,然后進(jìn)行類型驗(yàn)證,數(shù)據(jù)監(jiān)測等(提供為props屬性賦值就拋出警告的鉤子函數(shù))。
在初始化methods的時(shí)候(initMethods),主要是監(jiān)測methods中的方法名是否合法。
在初始化data的時(shí)候(initData),會(huì)運(yùn)行 observe 函數(shù)深度遍歷數(shù)據(jù)中的每一個(gè)屬性,進(jìn)行數(shù)據(jù)劫持。
在初始化computed的時(shí)候(initComputed),會(huì)監(jiān)測數(shù)據(jù)是否已經(jīng)存在data或props上,如果存在則拋出警告,否則調(diào)用defineComputed函數(shù),監(jiān)聽數(shù)據(jù),為組件中的屬性綁定getter及setter。如果computed中屬性的值是一個(gè)函數(shù),則默認(rèn)為屬性的getter函數(shù)。此外屬性的值還可以是一個(gè)對象,他只有三個(gè)有效字段set、get和cache,分別表示屬性的setter、getter和是否啟用緩存,其中g(shù)et是必須的,cache默認(rèn)為true。
function initComputed(vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
for(var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
//創(chuàng)建一個(gè)計(jì)算屬性 watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
if(!(key in vm)) {
//如果定義的計(jì)算屬性不在組件實(shí)例上,對屬性進(jìn)行數(shù)據(jù)劫持
//defineComputed 很重要,下面我們再說
defineComputed(vm, key, userDef);
} else {
//如果定義的計(jì)算屬性在data和props有,拋出警告
}
}
}
在初始化watch的時(shí)候(initWatch),會(huì)調(diào)用vm.$watch函數(shù)為watch中的屬性綁定setter回調(diào)(如果組件中沒有該屬性則不能成功監(jiān)聽,屬性必須存在于props、data或computed中)。如果watch中屬性的值是一個(gè)函數(shù),則默認(rèn)為屬性的setter回調(diào)函數(shù),如果屬性的值是一個(gè)數(shù)組,則遍歷數(shù)組中的內(nèi)容,分別為屬性綁定回調(diào),此外屬性的值還可以是一個(gè)對象,此時(shí),對象中的handler字段代表setter回調(diào)函數(shù),immediate代表是否立即先去執(zhí)行里面的handler方法,deep代表是否深度監(jiān)聽。
vm.$watch函數(shù)會(huì)直接使用Watcher構(gòu)建觀察者對象。watch中屬性的值作為watcher.cb存在,在觀察者update的時(shí)候,在watcher.run函數(shù)中執(zhí)行。想了解這一過程可以看我上一篇的 vue響應(yīng)式系統(tǒng)--observe、watcher、dep中關(guān)于Watcher的介紹。
function initWatch(vm, watch) {
//遍歷watch,為每一個(gè)屬性創(chuàng)建偵聽器
for(var key in watch) {
var handler = watch[key];
//如果屬性值是一個(gè)數(shù)組,則遍歷數(shù)組,為屬性創(chuàng)建多個(gè)偵聽器
//createWatcher函數(shù)中封裝了vm.$watch,會(huì)在vm.$watch中創(chuàng)建偵聽器
if(Array.isArray(handler)) {
for(var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
//為屬性創(chuàng)建偵聽器
createWatcher(vm, key, handler);
}
}
}
function createWatcher(vm, expOrFn, handler, options) {
//如果屬性值是一個(gè)對象,則取對象的handler屬性作為回調(diào)
if(isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
//如果屬性值是一個(gè)字符串,則從組件實(shí)例上尋找
if(typeof handler === 'string') {
handler = vm[handler];
}
//為屬性創(chuàng)建偵聽器
return vm.$watch(expOrFn, handler, options)
}
computed
computed本質(zhì)是一個(gè)惰性求值的觀察者,具有緩存性,只有當(dāng)依賴變化后,第一次訪問 computed 屬性,才會(huì)計(jì)算新的值
下面將圍繞這一句話來做解釋。
上面代碼中提到過,當(dāng)計(jì)算屬性中的數(shù)據(jù)存在與data和props中時(shí),會(huì)被警告,也就是這種做法是錯(cuò)誤的。所以一般的,我們都會(huì)直接在計(jì)算屬性中聲明數(shù)據(jù)。還是那個(gè)代碼片段中,如果定義的計(jì)算屬性不在組件實(shí)例上,會(huì)運(yùn)行defineComputed函數(shù)對數(shù)據(jù)進(jìn)行數(shù)據(jù)劫持。下面我們來看下defineComputed函數(shù)中做了什么。
function defineComputed(target, key, userDef) {
//是不是服務(wù)端渲染
var shouldCache = !isServerRendering();
//如果我們把計(jì)算屬性的值寫成一個(gè)函數(shù),這時(shí)函數(shù)默認(rèn)為計(jì)算屬性的get
if(typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache ?
//如果不是服務(wù)端渲染,則默認(rèn)使用緩存,設(shè)置get為createComputedGetter創(chuàng)建的緩存函數(shù)
createComputedGetter(key) :
//否則不使用緩存,直接設(shè)置get為userDef這個(gè)我們定義的函數(shù)
userDef;
//設(shè)置set為空函數(shù)
sharedPropertyDefinition.set = noop;
} else {
//如果我們把計(jì)算屬性的值寫成一個(gè)對象,對象中可能包含set、get和cache三個(gè)字段
sharedPropertyDefinition.get = userDef.get ?
shouldCache && userDef.cache !== false ?
//如果我們傳入了get字段,且不是服務(wù)端渲染,且cache不為false,設(shè)置get為createComputedGetter創(chuàng)建的緩存函數(shù)
createComputedGetter(key) :
//如果我們傳入了get字段,但是是服務(wù)端渲染或者cache設(shè)為了false,設(shè)置get為userDef這個(gè)我們定義的函數(shù)
userDef.get :
//如果沒有傳入get字段,設(shè)置get為空函數(shù)
noop;
//設(shè)置set為我們傳入的傳入set字段或空函數(shù)
sharedPropertyDefinition.set = userDef.set ?
userDef.set :
noop;
}
//雖然這里可以get、set都可以設(shè)置為空函數(shù)
//但是在項(xiàng)目中,get為空函數(shù)對數(shù)據(jù)取值會(huì)報(bào)錯(cuò),set為空函數(shù)對數(shù)據(jù)賦值會(huì)報(bào)錯(cuò)
//而computed主要作用就是計(jì)算取值的,所以get字段是必須的
//數(shù)據(jù)劫持
Object.defineProperty(target, key, sharedPropertyDefinition);
}
在上一篇的 vue響應(yīng)式系統(tǒng)--observe、watcher、dep 中,我有關(guān)于Watcher的介紹中提到,計(jì)算屬性 watcher實(shí)例化的時(shí)候,會(huì)把options.lazy設(shè)置為true,這里是計(jì)算屬性惰性求值,且可緩存的關(guān)鍵,當(dāng)然前提是cache不為false。
cache不為false,會(huì)調(diào)用createComputedGetter函數(shù)創(chuàng)建計(jì)算屬性的getter函數(shù)computedGetter,
先來看一段代碼
function createComputedGetter(key) {
return function computedGetter() {
var watcher = this._computedWatchers && this._computedWatchers[key];
if(watcher) {
if(watcher.dirty) {
//watcher.evaluate中更新watcher的值,并把watcher.dirty設(shè)置為false
//這樣等下次依賴更新的時(shí)候才會(huì)把watcher.dirty設(shè)置為true,然后進(jìn)行取值的時(shí)候才會(huì)再次運(yùn)行這個(gè)函數(shù)
watcher.evaluate();
}
//依賴追蹤
if(Dep.target) {
watcher.depend();
}
//返回watcher的值
return watcher.value
}
}
}
//對于計(jì)算屬性,當(dāng)取值計(jì)算屬性時(shí),發(fā)現(xiàn)計(jì)算屬性的watcher的dirty是true
//說明數(shù)據(jù)不是最新的了,需要重新計(jì)算,這里就是重新計(jì)算計(jì)算屬性的值。
Watcher.prototype.evaluate = function evaluate() {
this.value = this.get();
this.dirty = false;
};
//當(dāng)一個(gè)依賴改變的時(shí)候,通知它update
Watcher.prototype.update = function update() {
//三種watcher,只有計(jì)算屬性 watcher的lazy設(shè)置了true,表示啟用惰性求值
if(this.lazy) {
this.dirty = true;
} else if(this.sync) {
//標(biāo)記為同步計(jì)算的直接運(yùn)行run,三大類型暫無,所以基本會(huì)走下面的queueWatcher
this.run();
} else {
//將watcher推入觀察者隊(duì)列中,下一個(gè)tick時(shí)調(diào)用。
//也就是數(shù)據(jù)變化不是立即就去更新的,而是異步批量去更新的
queueWatcher(this);
}
};
當(dāng)options.lazy設(shè)置為true之后(僅計(jì)算屬性watcher的options.lazy設(shè)置為true),每次依賴更新,都不會(huì)主動(dòng)觸發(fā)run函數(shù),而是把watcher.dirty設(shè)置為true。這樣,當(dāng)對計(jì)算屬性進(jìn)行取值時(shí),就會(huì)運(yùn)行computedGetter函數(shù),computedGetter函數(shù)中有一個(gè)關(guān)于watcher.dirty的判斷,當(dāng)watcher.dirty為true時(shí)會(huì)運(yùn)行watcher.evaluate進(jìn)行值的更新,并把watcher.dirty設(shè)置為false,這樣就完成了惰性求值的過程。后面只要依賴不更新,就不會(huì)運(yùn)行update,就不會(huì)把watcher.dirty為true,那么再次取值的時(shí)候就不會(huì)運(yùn)行watcher.evaluate進(jìn)行值的更新,從而達(dá)到了緩存的效果。
綜上,我們了解到cache不為false的時(shí)候,計(jì)算屬性都是惰性求值且具有緩存性的,而cache默認(rèn)是true,我們也大多使用這個(gè)默認(rèn)值,所以我們說 computed本質(zhì)是一個(gè)惰性求值的觀察者,具有緩存性,只有當(dāng)依賴變化后,第一次訪問 computed 屬性,才會(huì)計(jì)算新的值 。
總結(jié)
以上所述是小編給大家介紹的vue數(shù)據(jù)初始化initState的實(shí)例詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,請注明出處,謝謝!
相關(guān)文章
vue自定義tap指令及tap事件的實(shí)現(xiàn)
Vue提供自定義實(shí)現(xiàn)指令的功能, 和組件類似,可以是全局指令和局部指令,這篇文章主要介紹了vue自定義tap指令及tap事件的實(shí)現(xiàn) ,需要的朋友可以參考下2018-09-09
Vue導(dǎo)出excel的兩個(gè)常用方式介紹與對比
這篇文章主要為大家詳細(xì)介紹了Vue導(dǎo)出excel的兩個(gè)常用方式,分別為前端vue+XLSX導(dǎo)出excel和vue+后端POI?導(dǎo)出excel,感興趣的小伙伴可以了解下2025-01-01
vue中的dom節(jié)點(diǎn)和window對象
這篇文章主要介紹了vue中的dom節(jié)點(diǎn)和window對象,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
tdesign和vue的子組件關(guān)閉是父組件執(zhí)行方法
這篇文章主要介紹了tdesign和vue的子組件關(guān)閉是父組件執(zhí)行方法,需要的朋友可以參考下2006-06-06
Vue項(xiàng)目中數(shù)據(jù)的深度監(jiān)聽或?qū)ο髮傩缘谋O(jiān)聽實(shí)例
這篇文章主要介紹了Vue項(xiàng)目中數(shù)據(jù)的深度監(jiān)聽或?qū)ο髮傩缘谋O(jiān)聽實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
淺談 Vue v-model指令的實(shí)現(xiàn)原理
vue的v-model是一個(gè)十分強(qiáng)大的指令,它可以自動(dòng)讓原生表單組件的值自動(dòng)和你選擇的值綁定,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06

