In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the relevant knowledge of "what are the side effects of Vue.set". In the operation of actual cases, many people will encounter such a dilemma. Then let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Although Vue has been used for a long time, it will still step on the pit. Let's take a look at the following simple paragraph: click on the an and b buttons, what will the following code suggest?
{{JSON.stringify (this.testObj)}}
Set testObj property a set testObj property b new Vue ({el:'# app', data: {testObj: {},}, watch: {'testObj.a' () {alert (' a')}, 'testObj.b' () {alert (' b')},} Methods: {set (val) {Vue.set (this.testObj, val, {}) }})
The answer is:
Alert a when you click a, alert a when you click b, then alert b.
If you click an and b, what does it suggest?
The answer is:
Alert a when you click an and alert b when you click b.
Let's make a small change in the code: change the value of Vue.set from object to true. At this point, click the an and b buttons, what will the following code indicate?
{{JSON.stringify (this.testObj)}}
Set testObj property a set testObj property b new Vue ({el:'# app', data: {testObj: {},}, watch: {'testObj.a' () {alert (' a')}, 'testObj.b' () {alert (' b')},} Methods: {set (val) {Vue.set (this.testObj, val, true) }})
The answer is:
Alert a when you click an and alert b when you click b.
If you click an and b, what does it suggest?
The answer is:
There's no hint.
Let's sum up what we found: add attributes to the object o with Vue.set. If the added attribute is an object, then all the properties of o will be triggered.
Don't you understand? Please let me explain.
To answer these questions, we first need to understand the responsive principle of Vue.
We can see from the image on Vue's official website that when we visit a data attribute p in data, the Watcher corresponding to this attribute will be added to the dependency list of this attribute through getter; when we modify the value of attribute p, the corresponding callback function will be triggered through setter to notify the Watcher on which p depends, thus causing the virtual node to render again.
So the key to whether to respond or not is to see if the dependency list has a watcher for this attribute.
To associate the dependency list with the actual data structure, I drew the main data structure of the vue response, with arrows indicating the inclusion relationship between them:
The dependency in Vue is a Dep object, which has an subs array inside, and each element in this array is a Watcher, corresponding to each attribute of the object. The subs array in the Dep object is the dependency list.
From the figure, we can see that the Dep object comes from the dep property of the _ _ ob__ object. How did this _ _ ob__ object come from? This is what Vue initialization does when we new Vue the object. The most important task of Vue initialization is to make each property of the object responsive, specifically by calling the following defineReactive for each property through the observe function:
/ * 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 ();}});}
To make an object responsive is to add getter and setter to all the properties of the object (work done by defineReactive), and then add the _ _ ob__ attribute (work done by observe) to the object. Because _ _ ob__ contains a list of dependencies of the object, the object can respond to data changes.
You can see that observe is also called in defineReactive, so the action of making an object responsive is recursive. That is, if the property of the object is another object, then the property object will also become responsive. That is to say, the property object also adds _ _ ob__ and then all properties add getter and setter.
I just said that there is no response to see "there is no watcher for this attribute in the dependency list", but in fact, ob only exists on the object where the attribute resides, so the dependency list is a dependency list on the object, which is associated with the corresponding attribute through the expression of Watcher in the dependency list (see figure 2). To be precise: whether there is a response or not should be based on "whether there is a watcher for attributes in the object's dependency list".
Notice that we only define testObj empty objects in data, and testObj does not have any attributes, so testObj's dependency list is initially empty.
But because the code has a watch that defines the Vue object, the initialization code creates a new watcher for each watch property and adds it to the testObj's dependency queue _ _ ob__.dep.subs. The method of adding here is very clever: when you create a new watcher, you will access the properties of the watch layer by layer. For example, watch 'testObj.a',vue will visit testObj first and then testObj.a. Because testObj has been initialized to be responsive, the getter,getter defined in defineReactive and dep.depend () will be called when accessing testObj to add the watcher corresponding to testObj.a to the dependency queue _ _ ob__.dep.subs. So when you create a new watcher, you automatically add the watcher to the dependency list of the corresponding object.
Summary: when initializing the Vue object, you will add getter and setter to all the attributes of the object in data, add the _ _ ob__ attribute, and put the watcher corresponding to the watch attribute in the _ _ ob__.dep.subs dependency list.
So after initialization, testObj already has watcher for attributes an and b in its dependency list.
With the above basics, let's take a look at what Vue.set, that is, the following set function, does.
/ * 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}
We are mainly concerned about the last two sentences: defineReactive (ob.value, key, val); and ob.dep.notify ();.
The purpose of defineReactive is to make an object property responsive. Ob.dep.notify () notifies all the watcher in the object dependency list: the data has changed and see if you want to do something. Exactly what to do is the cb in figure 2 Watcher. When we write watch: {p: function (oldValue, newValue) {}} in vue, we add cb to the watcher of p.
So Vue.set actually did these two things:
Change the attribute to be responsive.
Notifies the object that all watcher data in the dependency list has changed.
So the question is, since the dependency list always contains the watcher of an and b, the cb of an and b should be called every time Vue.set is called. Why is this not the case? The secret lies in the run function of watcher below.
/ * 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);
When dep.notify notifies watcher, watcher executes the run function, which is where cb is actually called. We can see that an if (value! = = this.value | | isObject (value) | | this.deep) is triggered when the value is not equal or the value is an object or depth watch. So when we use Vue.set to add new object attributes to an object, every watcher in the dependency list will pass this judgment (the newly added attribute {}! = = {} so value! = = this.value holds, and the existing attribute because isObject (value)) will trigger a cb callback. When Vue.set adds new non-object attributes to an object, only the newly added attributes are judged by value! = = this.value to trigger cb, while other attributes do not trigger cb callbacks because their values remain unchanged. This explains why the effect of scenario one is different from that of scenario two when you click button b for the first time.
So since the dependency list has not become what the second click of the button, the effect is different?
This is the role of this judgment in the set function:
If (key in target & &! (key in Object.prototype)) {target [key] = val; return val}
This judgment will determine whether the object property already exists, and if so, just do an assignment. Do not go to the defineReactive (ob.value, key, val); and ob.dep.notify (); below, so that watcher does not receive the notify and will not trigger the cb callback. So where did the callback of the second button click be triggered? Remember the setter defined in defineReactive just now? Because testObj has become responsive, performing an attribute assignment triggers the property's setter with a dep.notify () at the end of the set function; it notifies watcher and triggers the cb callback.
Even so, shouldn't the second click be triggered by both an and b? Doesn't the dependency list always contain watcher with an and b?
Here we will involve another concept, "dependency collection". Unlike the dependency list of _ _ ob__.dep.subs, responsive objects also have a dependency list, that is, var dep defined in defineReactive. Each attribute has a dep, which appears in the form of a closure. I'll call it an internal dependency list for the time being. In the previous set function judgment, it is judged that the assignment statement that will execute target [key] = val; will first trigger getter and add the watcher corresponding to the attribute key to the internal dependency list, which is the "collect as dependencies" in the picture on the Vue official website; then trigger setter and call dep.notify () to notify watcher to execute watcher.run. Because at this time, the internal dependency list has only one watcher, that is, the watcher corresponding to the attribute. So only the callback of the property itself is triggered.
Based on the above analysis, let's restore two scenarios:
Scene 1:Vue.set an object attribute
Click the button a: Vue.set to make attribute a responsive, notify the dependent list data change, rely on the list watcher-a to find the data change, and execute a callback.
Click the button b: Vue.set to make attribute b responsive to notify the data changes in the dependency list. Watcher-a finds that an is an object in the dependency list, and watcher-b finds that the data changes meet the trigger cb conditions, so the callbacks of an and b are executed.
Then click the button a: Vue.set to assign an attribute to trigger getter to collect dependencies, internal dependency list to collect dependencies watcher-a, trigger setter to notify internal dependency list data changes, watcher-a discovers data changes, and executes callback of a.
Then click the button b: Vue.set to assign a value to the b attribute, trigger getter to collect dependencies, collect dependencies from the internal dependency list, trigger setter to notify the internal dependency list data changes, watcher-b discovers the data changes, and executes the callback of b.
A non-object attribute of the scene 2:Vue.set
Click the button a: Vue.set to make attribute a responsive, notify the dependent list data change, rely on the list watcher-a to find the data change, and execute a callback.
Click the button b: Vue.set to change the attribute b to responsive, notify the dependent list data change, watcher-b finds the data change, and executes the callback of b.
Then click the button a: Vue.set to assign an attribute, trigger getter to collect dependencies, collect dependency watcher-an in the internal dependency list, trigger setter, find that the data remains unchanged, and return.
Then click the button b: Vue.set to assign a value to the b attribute, trigger getter to collect dependencies, collect dependency watcher-b in the internal dependency list, trigger setter, find that the data has not changed, and return.
Summary of the reasons:
Vue responsive objects have internal and external dependency lists.
Vue.set has two functions: adding attributes and modifying attributes.
There is a difference between object attributes and non-object attributes when Watcher determines whether a callback needs to be triggered.
This is the end of the content of "what are the side effects of Vue.set". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.