您好,登錄后才能下訂單哦!
當(dāng)你把一個普通的 JavaScript 對象傳給 Vue 實(shí)例的 data 選項(xiàng),Vue 將遍歷此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter。Object.defineProperty 是 ES5 中一個無法 shim 的特性,這也就是為什么 Vue 不支持 IE8 以及更低版本瀏覽器
以上摘自 深入響應(yīng)式原理
那么,把這些屬性全部轉(zhuǎn)為 getter/setter 具體是怎樣一個過程呢?本文不深入具體,簡單大致了解其過程,旨在整體把握,理解其主要思路
假設(shè)代碼如下:
const vm = new Vue({ el: '#app', data: { msg: 'hello world' } })
data 選項(xiàng)可以接收一個對象或者方法,這里以對象為例(其實(shí)最后都會轉(zhuǎn)為對象)
首先,這個對象的所有鍵值對都會被掛載在 vm._data 上(此外 vm._data 對象上還有個 __ob__ key,暫時可以忽視),這樣我們便能用 vm._data.msg 訪問到數(shù)據(jù)
但是通常我們是用 vm.msg 這樣訪問數(shù)據(jù),如何做到的呢?其實(shí)就是做了個代理,將 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 (下劃線變量)用作內(nèi)部程序,對外暴露的 API 是 vm.$data,其實(shí)這兩者也是一個東西,也是做了個代理,代碼大概這樣:
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 其實(shí)就是訪問 vm._data.msg。如果直接在開發(fā)環(huán)境對 vm.data = xxx這樣的賦值,而不是vm.$data.msg = xxx` 這樣的賦值,后者是沒問題的)
至此,我們理解了為什么能用 vm.msg、vm._data.msg 以及 vm.$data.msg 三種方式獲取/改變數(shù)據(jù),最原始的數(shù)據(jù)是 vm._data.msg,而另外兩者即代理了 _data 的數(shù)據(jù),vm.$data.msg 即為 Vue 向外提供的 API,一般情況下開發(fā)我們直接用 vm.msg 這樣比較多,也方便,如果要獲取整個 data,程序中需要用 this.$data,而不是 this.data
接下來說 getter/setter
將 demo 稍微添點(diǎn)東西:
const vm = new Vue({ el: '#app', data: { msg: 'hello world' }, computed: { msg2() { return this.msg + '123' } } })
msg2 是依賴于 msg 的,當(dāng) msg 改變的時候,msg2 的值需要自動更新,msg 的改變可以在 vm._data.msg 的 setter 中監(jiān)聽到,但是怎么知道 msg2 是依賴于 msg 的呢?
直觀地我們可以想到,遍歷所有 computed 對象的鍵值對,然后進(jìn)行分析,理論上似乎可行,但是我尋思著這可能需要解析 AST 啊,或者正則去匹配,看看是否用到了 this.msg,也可能是 this.$data.msg 啊,還可能是 this._data.msg,而且還要遍歷 data 中的所有 key,這看起來也太麻煩了吧,而且,如果程序中沒有用到 msg2,那不是多此一舉了?
事實(shí)上,Vue 初始化的時候會對 vm._data 的每個鍵值對設(shè)置 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 響應(yīng)式核心就是,setter 的時候會收集依賴,getter 的時候會觸發(fā)依賴更新
我們還是以上面的 computed msg2 為例,當(dāng)我們第一次去取值 msg2 時(注意,必須是取值行為,可以是在 template,也可以是程序中),勢必需要去取值 this.msg,這就會觸發(fā) msg 的 getter,此時我們就可以確定 msg2 依賴于 msg
msg 可以被哪些東西依賴呢?目前看來有三
我們可以打印 vm._watchers 查看,是一個 Watcher 實(shí)例數(shù)組,直接看實(shí)例的 expression 值,其實(shí)就是觸發(fā)這個表達(dá)式的時候,會觸發(fā) msg 的 getter
而這個表達(dá)式就對應(yīng)上述的三種情況,因?yàn)?msg 改變的時候,這些表達(dá)式需要重新求值,所以這些依賴項(xiàng)都要保存起來,所以源碼中定于了這個 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 數(shù)組表示該 watcher 的依賴項(xiàng),值為 Dep 實(shí)例,可以理解成和 Watcher 實(shí)例的表達(dá)式有關(guān)的 data 數(shù)據(jù)。注意,deps 數(shù)組可能是空,對于 template 而言,可以是 template 中不依賴于 data,對于 computed 而言,可以是這個 computed 數(shù)據(jù)還沒被獲?。ū热缥叶x了 msg2,但是程序中沒有用,這時 deps 為空,這表明我如果改變了 msg,但是不需要通知到 msg2,因?yàn)?msg2 根本沒用到嘛,但是我在控制臺輸入 vm.msg2,從而觸發(fā)了 msg 的 getter,繼而進(jìn)行了依賴收集,這時 deps 就不為空了,這表明我已經(jīng)使用了 msg2,下次 msg 更新時需要通知到 msg2 進(jìn)行改變)
而對于 watch 而言,我試了下任何情況下 deps 都不為空,這需要進(jìn)一步查看源碼確認(rèn)
deps 數(shù)組元素是 Dep 實(shí)例,該實(shí)例有個 subs 屬性,是 Watcher 實(shí)例數(shù)組,表示依賴于這個 Dep 的項(xiàng)目
Watcher 和 Dep 比較難理解,可以暫時這樣理解,Dep 和 data 掛鉤,每一個 Dep 實(shí)例就對應(yīng) data 的一個鍵值對,Watcher 實(shí)例則依賴于 Dep,那么有三個情況會依賴,也就是以上三種(想想是不是這樣,當(dāng)數(shù)據(jù)更新的時候,是不是只有這三處需要同時更新,或者同時響應(yīng))
總結(jié)下:我們會對 data 中所有鍵值對設(shè)置 getter/setter,getter 的時候我們會收集依賴(依賴項(xiàng)為上面三項(xiàng),并不是任何情況下都會收集依賴,比如在鉤子中打印 msg,這時候就沒依賴,所以源碼中這里還有復(fù)雜判斷),setter 的時候我們會將收集的依賴項(xiàng)觸發(fā),從而更新數(shù)據(jù),理解了這些,就能初步理解 Vue 的響應(yīng)式原理
以上所述是小編給大家介紹的Vue getter setter詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對億速云網(wǎng)站的支持!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。