Home > Article > Web Front-end > What is the responsiveness principle of Vue?
I have read a lot of articles on Vue principles recently. With the help of these articles, I have tried many times to understand the source code of Vue myself. Finally, I think it’s time to output the content myself. I hope I can familiarize everyone with Vue from a different perspective than other articles.
var Dep = function Dep() { this.id = uid++ this.subs = [] }
The meaning of Dep is naturally dependency (that is, dependence, a noun in the computer field).
Just like writing a node.js program, dependencies from the npm warehouse are often used. In Vue, dependencies specifically refer to reactively processed data. As will be mentioned later, one of the key functions of reactive processing is defineReactive, which is mentioned in many Vue principle articles.
After Dep is bound to each responsive data, the responsive data will become a dependency (noun). When introducing Watcher below, it will be mentioned that responsive data may be watched, computed, or in the template. Use 3 cases of dependence (verb).
#subs
There is a subs attribute under the Dep object, which is an array. It is easy to guess that it means the subscriber list. Subscribers may be watch functions, computed functions, or view update functions.
Watcher is the subscriber mentioned in Dep (not to be confused with the Observer observer later).
Because the function of Watcher is to respond to Dep updates in a timely manner, just like the subscription push of some apps. If you (Watcher) subscribe to certain information (Dep), you will be reminded to read when the information is updated.
deps
Similar to Dep having the subs attribute, the Watcher object also has the deps attribute. This constitutes a many-to-many relationship between Watcher and Dep. The reason for recording each other is that when one party is cleared, the related objects can be updated in time.
How to generate Watcher
The watch, computed, and rendering templates mentioned many times above generate Watcher, which are all concise and easy to understand in the Vue source code:
Observer is an observer, he is responsible for recursively observing (or processing) reactive objects (or arrays). In the printed example, you can notice that the reactive objects will have an __ob__, which is proof of what has been observed. Observers are not as important as Dep and Watcher above, just a little understanding is enough.
walk
Observer.prototype.walk is the core method of recursive processing during Observer initialization, but this method is used to process objects, and there is also Observer.prototype.observeArray Process arrays.
According to the relationship between the above concepts, how to match them and how to achieve responsive data update?
First set our goal: naturally when the data is updated, the view will be automatically refreshed to display the latest data.
This is the relationship between Dep and Watcher mentioned above. The data is Dep, and Watcher triggers the page rendering function (this is the most important watcher).
But a new question arises, how does Dep know that any Watchers depend on him?
Vue adopts a very interesting method:
Before running the Watcher's callback function, first write down what the current Watcher is (through Dep.target)
If responsive data is used in the running callback function, the getter function of the responsive data will inevitably be called.
In the getter function of the responsive data You can write down the current Watcher and establish the relationship between Dep and Watcher
After that, when the responsive data is updated, the setter function of the responsive data will inevitably be called
Based on the previously established relationship, the callback function corresponding to the Watcher can be triggered in the setter function
The above logic is in the defineReactive function middle. There are many entrances to this function. Let’s talk about the more important observe function first.
In the observe function, a new Observer object will be created, in which Observer.prototype.walk is used to process the values in the object one by one in a responsive manner, and the defineReactive function is used.
Because the defineReactive function is so important and not long, it is more convenient to post it here directly.
function defineReactive(obj, key, val, customSetter, shallow) { var dep = new Dep() depsArray.push({ dep, obj, key }) var property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get 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 // 后半部分诡异的条件是用于判断新旧值都是 NaN 的情况 if (newVal === value || (newVal !== newVal && value !== value)) { return } // customSetter 用于提醒你设置的值可能存在问题 if ('development' !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() }, }) }
First of all, each responsive value is a "dependency", so in the first step, we use the ability of closure to create a Dep for each value. (With Vue 3, there is no need for closures)
Then look at the three core parameters:
obj The object where the value currently needs to be processed responsively
key value key
val current value
This value may have been defined before own getters and setters, so when doing Vue’s responsive processing, the original getters and setters are processed first.
As mentioned above in the core process, the getter function will establish the relationship between Dep and Watcher, specifically relying on dep.depend().
Below are several methods for Dep and Watcher to call each other:
Dep.prototype.depend = function depend() { if (Dep.target) { Dep.target.addDep(this) } } Watcher.prototype.addDep = function addDep(dep) { var id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } Dep.prototype.addSub = function addSub(sub) { this.subs.push(sub) }
通过这几个函数,可以领略到了 Dep 和 Watcher 错综复杂的关系……不过看起来迂回,简单来说,其实做的就是上面说的互相添加到多对多列表。
你可以在 Dep 的 subs 找到所有订阅同一个 Dep 的 Watcher,也可以在 Watcher 的 deps 找到所有该 Watcher 订阅的所有 Dep。
但是里面还有一个隐藏问题,就是 Dep.target 怎么来呢?先放一放,后会作出解答。先接着看看 setter 函数,其中的关键是 dep.notify()。
Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice() for (var i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
不难理解,就是 Dep 提醒他的订阅者列表(subs)里的所有人更新,所谓订阅者都是 Watcher,subs[i].update() 调用的也就是 Watcher.prototype.update。
那么来看一下 Watcher 的 update 做了什么——
Watcher.prototype.update = function update() { if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } }
在这里我觉得有两个点比较值得展开,所以挖点坑
The above is the detailed content of What is the responsiveness principle of Vue?. For more information, please follow other related articles on the PHP Chinese website!