亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

Vue.set的副作用有哪些

發布時間:2021-10-28 16:26:24 來源:億速云 閱讀:126 作者:iii 欄目:web開發

本篇內容介紹了“Vue.set的副作用有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

Vue雖然用挺久了,還是會踩到坑,來看下面這段很簡單的?:點擊a和b按鈕,下面代碼會提示什么?

<html> <head>     <meta charset="utf-8">     <script src="https://cdn.staticfile.org/vue/2.5.17/vue.min.js"></script> </head>  <body> <div id="app">     <p>{{ JSON.stringify(this.testObj) }}</p>     <button @click="set('a')">設置testObj屬性a</button>     <button @click="set('b')">設置testObj屬性b</button> </div>  <script>   new Vue({     el: '#app',     data: {       testObj: {},     },     watch: {       'testObj.a'() {         alert('a')       },       'testObj.b'() {         alert('b')       },     },     methods: {       set(val) {         Vue.set(this.testObj, val, {});       }     },   }) </script> </body> </html>

答案是:

點a的時候alert a,點b的時候alert a,接著alert b。

如果再接著點a,點b,提示什么?

答案是:

點a的時候alert a,點b的時候alert b。

我們把代碼做一個很小的改動:把Vue.set的值由對象改為true。這時候點擊a和b按鈕,下面代碼會提示什么?

<html> <head>     <meta charset="utf-8">     <script src="https://cdn.staticfile.org/vue/2.5.17/vue.min.js"></script> </head>  <body> <div id="app">     <p>{{ JSON.stringify(this.testObj) }}</p>     <button @click="set('a')">設置testObj屬性a</button>     <button @click="set('b')">設置testObj屬性b</button> </div>  <script>   new Vue({     el: '#app',     data: {       testObj: {},     },     watch: {       'testObj.a'() {         alert('a')       },       'testObj.b'() {         alert('b')       },     },     methods: {       set(val) {         Vue.set(this.testObj, val, true);       }     },   }) </script> </body> </html>

答案是:

點a的時候alert a,點b的時候alert b。

如果再接著點a,點b,提示什么?

答案是:

沒有提示。

先總結一下發現的現象:用Vue.set為對象o添加屬性,如果添加的屬性是一個對象,那么o的所有屬性會被觸發響應。

是不是不明白?且請聽我講解一下。

要回答上面這些問題,我們首先需要理解一下Vue的響應式原理。

Vue.set的副作用有哪些

從Vue官網這幅圖上我們可以看出:當我們訪問data里某個數據屬性p時,會通過getter將這個屬性對應的Watcher加入該屬性的依賴列表;當我們修改屬性p的值時,通過setter通知p依賴的Watcher觸發相應的回調函數,從而讓虛擬節點重新渲染。

所以響不響應關鍵是看依賴列表有沒有這個屬性的watcher。

為了把依賴列表和實際的數據結構聯系起來,我畫出了vue響應式的主要數據結構,箭頭表示它們之間的包含關系:

Vue.set的副作用有哪些

Vue里的依賴就是一個Dep對象,它內部有一個subs數組,這個數組里每個元素都是一個Watcher,分別對應對象的每個屬性。Dep對象里的這個subs數組就是依賴列表。

從圖中我們可以看到這個Dep對象來自于__ob__對象的dep屬性,這個__ob__對象又是怎么來的呢?這就是我們new  Vue對象時候Vue初始化做的工作了。Vue初始化最重要的工作就是讓對象的每個屬性成為響應式,具體則是通過observe函數對每個屬性調用下面的defineReactive來完成的:

/**  * Define a reactive property on an Object.  */ function defineReactive (   obj,   key,   val,   customSetter,   shallow ) {   var dep = new Dep();    var property = Object.getOwnPropertyDescriptor(obj, key);   if (property && property.configurable === false) {     return   }    // cater for pre-defined getter/setters   var getter = property && property.get;   if (!getter && arguments.length === 2) {     val = obj[key];   }   var setter = property && property.set;    var childOb = !shallow && observe(val);   Object.defineProperty(obj, key, {     enumerable: true,     configurable: true,     get: function reactiveGetter () {       var 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) {       var 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();       }       if (setter) {         setter.call(obj, newVal);       } else {         val = newVal;       }       childOb = !shallow && observe(newVal);       dep.notify();     }   }); }

讓一個對象成為響應式其實就是給對象的所有屬性加上getter和setter(defineReactive做的工作),然后在對象里加__ob__屬性(observe做的工作),因為__ob__里包含了對象的依賴列表,所以這個對象就可以響應數據變化。

可以看到defineReactive里也調用了observe,所以讓一個對象成為響應式這個動作是遞歸的。即如果這個對象的屬性又是一個對象,那么屬性對象也會成為響應式。就是說這個屬性對象也會加__ob__然后所有屬性加上getter和setter。

剛才說有沒有響應看“依賴列表有沒有這個屬性的watcher”,但是實際上,ob  只存在屬性所在的對象上,所以依賴列表是在對象上的依賴列表,通過依賴列表里Watcher的expression關聯到對應屬性(見圖2)。說以準確的說:有沒有響應應該是看“對象的依賴列表里有沒有屬性的watcher”。

注意我們在data里只定義了testObj空對象,testObj并沒有任何屬性,所以testObj的依賴列表一開始是空的。

但是因為代碼有定義Vue對象的watch,初始化代碼會對每個watch屬性新建watcher,并添加到testObj的依賴隊列__ob__.dep.subs里。這里的添加方法非常巧妙:新建watcher時候會一層層訪問watch的屬性。比如watch  'testObj.a',vue會先訪問testObj,再訪問testObj.a。因為testObj已經初始化成響應式的,訪問testObj時會調用defineReactive里定義的getter,getter又會調用dep.depend()從而把testObj.a對應的watcher加到依賴隊列__ob__.dep.subs里。于是新建watcher的同時完成了把watcher自動添加到對應對象的依賴列表這個動作。

小結一下:Vue對象初始化時會給data里對象的所有屬性加上getter和setter,添加__ob__屬性,并把watch屬性對應的watcher放到__ob__.dep.subs依賴列表里。

所以經過初始化,testObj的依賴列表里已經有了屬性a和b對應的watcher。

有了以上基礎知識我們再來看Vue.set也就是下面的set函數做了些什么。

/**  * Set a property on an object. Adds the new property and  * triggers change notification if the property doesn't  * already exist.  */ function set (target, key, val) {   if (process.env.NODE_ENV !== 'production' &&     (isUndef(target) || isPrimitive(target))   ) {     warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));   }   if (Array.isArray(target) && isValidArrayIndex(key)) {     target.length = Math.max(target.length, key);     target.splice(key, 1, val);     return val   }   if (key in target && !(key in Object.prototype)) {     target[key] = val;     return val   }   var ob = (target).__ob__;   if (target._isVue || (ob && ob.vmCount)) {     process.env.NODE_ENV !== 'production' && warn(       'Avoid adding reactive properties to a Vue instance or its root $data ' +       'at runtime - declare it upfront in the data option.'     );     return val   }   if (!ob) {     target[key] = val;     return val   }   defineReactive(ob.value, key, val);   ob.dep.notify();   return val }

我們關心的主要就最后這兩句:defineReactive(ob.value, key, val); 和ob.dep.notify();。

defineReactive的作用就是讓一個對象屬性成為響應式。ob.dep.notify()則是通知對象依賴列表里面所有的watcher:數據變化了,看看你是不是要做點啥?具體做什么就是圖2  Watcher里面的cb。當我們在vue 里面寫了 watch: { p: function(oldValue, newValue) {} }  時候我們就是為p的watcher添加了cb。

所以Vue.set實際上就做了這兩件事:

  • 把屬性變成響應式 。

  • 通知對象依賴列表里所有watcher數據發生變化。

那么問題來了,既然依賴列表一直包含a和b的watcher,那應該每次Vue.set時候,a和b的cb都應該被調用,為什么結果不是這樣呢?奧妙就藏在下面的watcher的run函數里。

/**  * Scheduler job interface.  * Will be called by the scheduler.  */ Watcher.prototype.run = function run () {   if (this.active) {     var value = this.get();     if (       value !== this.value ||       // Deep watchers and watchers on Object/Arrays should fire even       // when the value is the same, because the value may       // have mutated.       isObject(value) ||       this.deep     ) {       // set new value       var oldValue = this.value;       this.value = value;       if (this.user) {         try {           this.cb.call(this.vm, value, oldValue);         } catch (e) {           handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));         }       } else {         this.cb.call(this.vm, value, oldValue);       }     }   } };

dep.notify通知watcher后,watcher會執行run函數,這個函數才是真正調用cb的地方。我們可以看到有這樣一個判斷 if (value  !==this.value || isObject(value) || this.deep)  就是說值不相等或者值是對象或者是深度watch的時候,都會觸發cb回調。所以當我們用Vue.set給對象添加新的對象屬性的時候,依賴列表里的每個watcher都會通過這個判斷(新添加屬性因為{}  !== {} 所以value  !==this.value成立,已有屬性因為isObject(value)),都會觸發cb回調。而當我們Vue.set給對象添加新的非對象屬性的時候,只有新添加的屬性通過value  !==this.value 判斷會觸發cb,其他屬性因為值沒變所以不會觸發cb回調。這就解釋了為什么第一次點擊按鈕b的時候場景一和場景二的效果不一樣了。

那既然依賴列表沒變為什么第二次點擊按鈕效果就不一樣了呢?

這就是set函數里面這個判斷起的作用了:

if (key in target && !(key in Object.prototype)) {   target[key] = val;   return val }

這個判斷會判斷對象屬性是否已經存在,如果存在的話只是做一個賦值操作。不會走到下面的defineReactive(ob.value, key, val);  和ob.dep.notify();里,這樣watcher沒收到notify,就不會觸發cb回調了。那第二次點擊按鈕的回調是哪里觸發的呢?還記得剛才的defineReactive里定義的setter嗎?因為testObj已經成為了響應式,所以進行屬性賦值操作會觸發這個屬性的setter,在set函數最后有個dep.notify();就是它通知了watcher從而觸發cb回調。

就算是這樣第二次點擊不是應該a和b都觸發的嗎?依賴列表不是一直包含有a和b的watcher嗎?

這里就要涉及到另一個概念“依賴收集”,不同于__ob__.dep.subs這個依賴列表,響應式對象還有一個依賴列表,就是defineReactive里面定義的var  dep,每個屬性都有一個dep,以閉包形式出現,我暫且稱它為內部依賴列表。在前面的set函數判斷里,判斷通過會執行target[key]= val;  這句賦值語句會首先觸發getter,把屬性key對應的watcher添加到內部依賴列表,這個步驟就是Vue官網那張圖里的“collect as  dependencies”;然后觸發setter,調用dep.notify()通知watcher執行watcher.run。因為這時候內部依賴列表只有一個watcher也就是屬性對應的watcher。所以只觸發了屬性本身的回調。

根據以上分析我們還原一下兩個場景:

場景1:Vue.set 一個對象屬性

  • 點擊按鈕a: Vue.set把屬性a變成響應式,通知依賴列表數據變化,依賴列表中watcher-a發現數據變化,執行a的回調。

  • 點擊按鈕b:  Vue.set把屬性b變成響應式,通知依賴列表數據變化,依賴列表中watcher-a發現a是對象,watcher-b發現數據變化,均滿足觸發cb條件,于是執行a和b的回調。

  • 再點擊按鈕a:  Vue.set給a屬性賦值,觸發getter收集依賴,內部依賴列表收集到依賴watcher-a,觸發setter通知內部依賴列表數據變化,watcher-a發現數據變化,執行a的回調。

  • 再點擊按鈕b:  Vue.set給b屬性賦值,觸發getter收集依賴,內部依賴列表收集到依賴watcher-b,觸發setter通知內部依賴列表數據變化,watcher-b發現數據變化,執行b的回調。

場景2:Vue.set 一個非對象屬性

  • 點擊按鈕a: Vue.set把屬性a變成響應式,通知依賴列表數據變化,依賴列表中watcher-a發現數據變化,執行a的回調。

  • 點擊按鈕b: Vue.set把屬性b變成響應式,通知依賴列表數據變化,watcher-b發現數據變化,執行b的回調。

  • 再點擊按鈕a:  Vue.set給a屬性賦值,觸發getter收集依賴,內部依賴列表收集到依賴watcher-a,觸發setter,發現數據沒變化,返回。

  • 再點擊按鈕b:  Vue.set給b屬性賦值,觸發getter收集依賴,內部依賴列表收集到依賴watcher-b,觸發setter,發現數據沒變化,返回。

原因總結:

  • Vue響應式對象有內部、外部兩個依賴列表。

  • Vue.set有添加屬性、修改屬性兩種功能。

  • Watcher在判斷是否需要觸發回調時有對象屬性、非對象屬性的區別。

“Vue.set的副作用有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

如皋市| 错那县| 临沧市| 周口市| 东乡| 张家港市| 柯坪县| 鲜城| 错那县| 望奎县| 巴林左旗| 游戏| 万载县| 河间市| 天等县| 虹口区| 威远县| 合水县| 荔波县| 临桂县| 江北区| 云霄县| 根河市| 孟村| 谢通门县| 瓮安县| 舒城县| 霍州市| 镇康县| 合肥市| 梧州市| 温泉县| 治县。| 汾西县| 胶州市| 裕民县| 招远市| 新兴县| 哈巴河县| 南通市| 鄱阳县|