您好,登錄后才能下訂單哦!
當你把一個普通的 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉為 getter/setter。Object.defineProperty 是 ES5 中一個無法 shim 的特性,這也就是為什么 Vue 不支持 IE8 以及更低版本瀏覽器
以上摘自 深入響應式原理
那么,把這些屬性全部轉為 getter/setter 具體是怎樣一個過程呢?本文不深入具體,簡單大致了解其過程,旨在整體把握,理解其主要思路
假設代碼如下:
const vm = new Vue({ el: '#app', data: { msg: 'hello world' } })
data 選項可以接收一個對象或者方法,這里以對象為例(其實最后都會轉為對象)
首先,這個對象的所有鍵值對都會被掛載在 vm._data 上(此外 vm._data 對象上還有個 __ob__ key,暫時可以忽視),這樣我們便能用 vm._data.msg 訪問到數據
但是通常我們是用 vm.msg 這樣訪問數據,如何做到的呢?其實就是做了個代理,將 data 鍵值對中的 vm[key] 的訪問都代理到 vm._data[key] 上
proxy(vm, `_data`, key) export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
通常 vm._data (下劃線變量)用作內部程序,對外暴露的 API 是 vm.$data,其實這兩者也是一個東西,也是做了個代理,代碼大概這樣:
const dataDef = {} dataDef.get = function () { return this._data } Object.defineProperty(Vue.prototype, '$data', dataDef) if (process.env.NODE_ENV !== 'production') { dataDef.set = function () { warn( 'Avoid replacing instance root $data. ' + 'Use nested data properties instead.', this ) } }
簡單理解就是訪問 vm.data.msg 其實就是訪問 vm._data.msg。如果直接在開發環境對 vm.data = xxx這樣的賦值,而不是vm.$data.msg = xxx` 這樣的賦值,后者是沒問題的)
至此,我們理解了為什么能用 vm.msg、vm._data.msg 以及 vm.$data.msg 三種方式獲取/改變數據,最原始的數據是 vm._data.msg,而另外兩者即代理了 _data 的數據,vm.$data.msg 即為 Vue 向外提供的 API,一般情況下開發我們直接用 vm.msg 這樣比較多,也方便,如果要獲取整個 data,程序中需要用 this.$data,而不是 this.data
接下來說 getter/setter
將 demo 稍微添點東西:
const vm = new Vue({ el: '#app', data: { msg: 'hello world' }, computed: { msg2() { return this.msg + '123' } } })
msg2 是依賴于 msg 的,當 msg 改變的時候,msg2 的值需要自動更新,msg 的改變可以在 vm._data.msg 的 setter 中監聽到,但是怎么知道 msg2 是依賴于 msg 的呢?
直觀地我們可以想到,遍歷所有 computed 對象的鍵值對,然后進行分析,理論上似乎可行,但是我尋思著這可能需要解析 AST 啊,或者正則去匹配,看看是否用到了 this.msg,也可能是 this.$data.msg 啊,還可能是 this._data.msg,而且還要遍歷 data 中的所有 key,這看起來也太麻煩了吧,而且,如果程序中沒有用到 msg2,那不是多此一舉了?
事實上,Vue 初始化的時候會對 vm._data 的每個鍵值對設置 getter/setter,大概代碼如下:
// obj 即為 vm._data const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } })
Vue 響應式核心就是,setter 的時候會收集依賴,getter 的時候會觸發依賴更新
我們還是以上面的 computed msg2 為例,當我們第一次去取值 msg2 時(注意,必須是取值行為,可以是在 template,也可以是程序中),勢必需要去取值 this.msg,這就會觸發 msg 的 getter,此時我們就可以確定 msg2 依賴于 msg
msg 可以被哪些東西依賴呢?目前看來有三
我們可以打印 vm._watchers 查看,是一個 Watcher 實例數組,直接看實例的 expression 值,其實就是觸發這個表達式的時候,會觸發 msg 的 getter
而這個表達式就對應上述的三種情況,因為 msg 改變的時候,這些表達式需要重新求值,所以這些依賴項都要保存起來,所以源碼中定于了這個 Watcher 類
A watcher parses an expression, collects dependencies, and fires callback when the expression value changes. This is used for both the $watch() api and directives.
watcher.deps 數組表示該 watcher 的依賴項,值為 Dep 實例,可以理解成和 Watcher 實例的表達式有關的 data 數據。注意,deps 數組可能是空,對于 template 而言,可以是 template 中不依賴于 data,對于 computed 而言,可以是這個 computed 數據還沒被獲取(比如我定義了 msg2,但是程序中沒有用,這時 deps 為空,這表明我如果改變了 msg,但是不需要通知到 msg2,因為 msg2 根本沒用到嘛,但是我在控制臺輸入 vm.msg2,從而觸發了 msg 的 getter,繼而進行了依賴收集,這時 deps 就不為空了,這表明我已經使用了 msg2,下次 msg 更新時需要通知到 msg2 進行改變)
而對于 watch 而言,我試了下任何情況下 deps 都不為空,這需要進一步查看源碼確認
deps 數組元素是 Dep 實例,該實例有個 subs 屬性,是 Watcher 實例數組,表示依賴于這個 Dep 的項目
Watcher 和 Dep 比較難理解,可以暫時這樣理解,Dep 和 data 掛鉤,每一個 Dep 實例就對應 data 的一個鍵值對,Watcher 實例則依賴于 Dep,那么有三個情況會依賴,也就是以上三種(想想是不是這樣,當數據更新的時候,是不是只有這三處需要同時更新,或者同時響應)
總結下:我們會對 data 中所有鍵值對設置 getter/setter,getter 的時候我們會收集依賴(依賴項為上面三項,并不是任何情況下都會收集依賴,比如在鉤子中打印 msg,這時候就沒依賴,所以源碼中這里還有復雜判斷),setter 的時候我們會將收集的依賴項觸發,從而更新數據,理解了這些,就能初步理解 Vue 的響應式原理
以上所述是小編給大家介紹的Vue getter setter詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。