溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Vue 理解之白話 getter/setter詳解

發(fā)布時間:2020-09-04 10:06:19 來源:腳本之家 閱讀:157 作者:韓子遲 欄目:web開發(fā)

當(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 可以被哪些東西依賴呢?目前看來有三

  1. template 模版中
  2. computed
  3. watch

我們可以打印 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)站的支持!

向AI問一下細(xì)節(jié)

免責(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)容。

AI