您好,登錄后才能下訂單哦!
數據初始化
Vue 實例在建立的時候會運行一系列的初始化操作,而在這些初始化操作里面,和數據綁定關聯最大的是 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 } }
在這么多的數據的初始化中,props、methods和data是比較簡單的(所以我就不詳細介紹了☺),而computed 和 watch則相對較難,邏輯較復雜,所以我下面主要講下computed 和 watch(以下代碼部分為簡化后的)。
initState里面主要是對vue實例中的 props, methods, data, computed 和 watch 數據進行初始化。
在初始化props的時候(initProps),會遍歷props中的每個屬性,然后進行類型驗證,數據監測等(提供為props屬性賦值就拋出警告的鉤子函數)。
在初始化methods的時候(initMethods),主要是監測methods中的方法名是否合法。
在初始化data的時候(initData),會運行 observe 函數深度遍歷數據中的每一個屬性,進行數據劫持。
在初始化computed的時候(initComputed),會監測數據是否已經存在data或props上,如果存在則拋出警告,否則調用defineComputed函數,監聽數據,為組件中的屬性綁定getter及setter。如果computed中屬性的值是一個函數,則默認為屬性的getter函數。此外屬性的值還可以是一個對象,他只有三個有效字段set、get和cache,分別表示屬性的setter、getter和是否啟用緩存,其中get是必須的,cache默認為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; //創建一個計算屬性 watcher watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); if(!(key in vm)) { //如果定義的計算屬性不在組件實例上,對屬性進行數據劫持 //defineComputed 很重要,下面我們再說 defineComputed(vm, key, userDef); } else { //如果定義的計算屬性在data和props有,拋出警告 } } }
在初始化watch的時候(initWatch),會調用vm.$watch
函數為watch中的屬性綁定setter回調(如果組件中沒有該屬性則不能成功監聽,屬性必須存在于props、data或computed中)。如果watch中屬性的值是一個函數,則默認為屬性的setter回調函數,如果屬性的值是一個數組,則遍歷數組中的內容,分別為屬性綁定回調,此外屬性的值還可以是一個對象,此時,對象中的handler字段代表setter回調函數,immediate代表是否立即先去執行里面的handler方法,deep代表是否深度監聽。
vm.$watch
函數會直接使用Watcher構建觀察者對象。watch中屬性的值作為watcher.cb存在,在觀察者update的時候,在watcher.run函數中執行。想了解這一過程可以看我上一篇的 vue響應式系統--observe、watcher、dep中關于Watcher的介紹。
function initWatch(vm, watch) { //遍歷watch,為每一個屬性創建偵聽器 for(var key in watch) { var handler = watch[key]; //如果屬性值是一個數組,則遍歷數組,為屬性創建多個偵聽器 //createWatcher函數中封裝了vm.$watch,會在vm.$watch中創建偵聽器 if(Array.isArray(handler)) { for(var i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); } } else { //為屬性創建偵聽器 createWatcher(vm, key, handler); } } } function createWatcher(vm, expOrFn, handler, options) { //如果屬性值是一個對象,則取對象的handler屬性作為回調 if(isPlainObject(handler)) { options = handler; handler = handler.handler; } //如果屬性值是一個字符串,則從組件實例上尋找 if(typeof handler === 'string') { handler = vm[handler]; } //為屬性創建偵聽器 return vm.$watch(expOrFn, handler, options) }
computed
computed本質是一個惰性求值的觀察者,具有緩存性,只有當依賴變化后,第一次訪問 computed 屬性,才會計算新的值
下面將圍繞這一句話來做解釋。
上面代碼中提到過,當計算屬性中的數據存在與data和props中時,會被警告,也就是這種做法是錯誤的。所以一般的,我們都會直接在計算屬性中聲明數據。還是那個代碼片段中,如果定義的計算屬性不在組件實例上,會運行defineComputed函數對數據進行數據劫持。下面我們來看下defineComputed函數中做了什么。
function defineComputed(target, key, userDef) { //是不是服務端渲染 var shouldCache = !isServerRendering(); //如果我們把計算屬性的值寫成一個函數,這時函數默認為計算屬性的get if(typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? //如果不是服務端渲染,則默認使用緩存,設置get為createComputedGetter創建的緩存函數 createComputedGetter(key) : //否則不使用緩存,直接設置get為userDef這個我們定義的函數 userDef; //設置set為空函數 sharedPropertyDefinition.set = noop; } else { //如果我們把計算屬性的值寫成一個對象,對象中可能包含set、get和cache三個字段 sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? //如果我們傳入了get字段,且不是服務端渲染,且cache不為false,設置get為createComputedGetter創建的緩存函數 createComputedGetter(key) : //如果我們傳入了get字段,但是是服務端渲染或者cache設為了false,設置get為userDef這個我們定義的函數 userDef.get : //如果沒有傳入get字段,設置get為空函數 noop; //設置set為我們傳入的傳入set字段或空函數 sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } //雖然這里可以get、set都可以設置為空函數 //但是在項目中,get為空函數對數據取值會報錯,set為空函數對數據賦值會報錯 //而computed主要作用就是計算取值的,所以get字段是必須的 //數據劫持 Object.defineProperty(target, key, sharedPropertyDefinition); }
在上一篇的 vue響應式系統--observe、watcher、dep 中,我有關于Watcher的介紹中提到,計算屬性 watcher實例化的時候,會把options.lazy設置為true,這里是計算屬性惰性求值,且可緩存的關鍵,當然前提是cache不為false。
cache不為false,會調用createComputedGetter函數創建計算屬性的getter函數computedGetter,
先來看一段代碼
function createComputedGetter(key) { return function computedGetter() { var watcher = this._computedWatchers && this._computedWatchers[key]; if(watcher) { if(watcher.dirty) { //watcher.evaluate中更新watcher的值,并把watcher.dirty設置為false //這樣等下次依賴更新的時候才會把watcher.dirty設置為true,然后進行取值的時候才會再次運行這個函數 watcher.evaluate(); } //依賴追蹤 if(Dep.target) { watcher.depend(); } //返回watcher的值 return watcher.value } } } //對于計算屬性,當取值計算屬性時,發現計算屬性的watcher的dirty是true //說明數據不是最新的了,需要重新計算,這里就是重新計算計算屬性的值。 Watcher.prototype.evaluate = function evaluate() { this.value = this.get(); this.dirty = false; }; //當一個依賴改變的時候,通知它update Watcher.prototype.update = function update() { //三種watcher,只有計算屬性 watcher的lazy設置了true,表示啟用惰性求值 if(this.lazy) { this.dirty = true; } else if(this.sync) { //標記為同步計算的直接運行run,三大類型暫無,所以基本會走下面的queueWatcher this.run(); } else { //將watcher推入觀察者隊列中,下一個tick時調用。 //也就是數據變化不是立即就去更新的,而是異步批量去更新的 queueWatcher(this); } };
當options.lazy設置為true之后(僅計算屬性watcher的options.lazy設置為true),每次依賴更新,都不會主動觸發run函數,而是把watcher.dirty設置為true。這樣,當對計算屬性進行取值時,就會運行computedGetter函數,computedGetter函數中有一個關于watcher.dirty的判斷,當watcher.dirty為true時會運行watcher.evaluate進行值的更新,并把watcher.dirty設置為false,這樣就完成了惰性求值的過程。后面只要依賴不更新,就不會運行update,就不會把watcher.dirty為true,那么再次取值的時候就不會運行watcher.evaluate進行值的更新,從而達到了緩存的效果。
綜上,我們了解到cache不為false的時候,計算屬性都是惰性求值且具有緩存性的,而cache默認是true,我們也大多使用這個默認值,所以我們說 computed本質是一個惰性求值的觀察者,具有緩存性,只有當依賴變化后,第一次訪問 computed 屬性,才會計算新的值 。
總結
以上所述是小編給大家介紹的vue數據初始化initState的實例詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,請注明出處,謝謝!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。