“This is the 24th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”
preface
In Vue, computed property is one of the features we use a lot, which is similar to Watch, but very different. Computed is the last and most complex instantiation of the Watcher class. Here we read the source code to see how it works.
Watcher
Computed is responsive data, so computed uses the Watcher class when it’s initialized and when it’s used, so let’s just say Watcher.
Watcher works by setting itself to a globally unique location (windonw.target) and then reading data. Because the data is read, the getter for that data is triggered. The watcher that actually reads the data is then read from the globally unique location in the getter, and the wathcer is collected into the Dep. In this way, Watcher can actively subscribe to any data changes.
The key here is that dependencies are collected when the getter for the data is executed
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() Collect dependencies in the getter
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {... }})Copy the code
For Watcher, this is as short as we can get, otherwise it would be too long.
The computed characteristics
Computed has two characteristics:
- Computed does not run when it is not used
getter
- When you change the responsive data that computed depends on, you do not update the value of computed immediately; you have to wait until computed is used
Principles of the computed
Based on these two characteristics of computed tomography, we analyze it respectively
The first characteristic
During Vue initialization, the initComputed method is called in initState to initialize computed.
// ./src/core/instance/state.js
function initState(vm) { // Initialize all states
vm._watchers = [] // The current instance watcher collection
const opts = vm.$options // Merged attributes.// Initialize other states
if(opts.computed) { // If there are defined calculation attributes
initComputed(vm, opts.computed) // Initialize}... }Copy the code
So what does initComputed do
// ./src/core/instance/state.js
function initComputed(vm, computed) {
const watchers = vm._computedWatchers = Object.create(null) // Create a pure object
for(const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{lazy: true})// Instantiate computedWatcher
if(! (keyinvm)) { defineComputed(vm, key, userDef) } ... }}Copy the code
It checks whether computed is a function type or an object type. If it is a function, the function is the getter; otherwise, use computed. Get the getter to generate a Watcher and set the lazy property to true. This lazy attribute is intended to implement the first feature of computed.
// src/core/observer/watcher.js
class Watcher{
constructor(vm, expOrFn, cb, options) {
this.vm = vm
this._watchers.push(this)
if(options) {
this.lazy = !! options.lazy// Indicates computed
}
this.dirty = this.lazy // Dirty is the flag bit, indicating whether to use computed data
this.getter = expOrFn // Computed callback functions
this.value = this.lazy
? undefined
: this.get(); }}Copy the code
If lazy is true, the get method is not called directly, which is why getters for computed are not run when computed is not used.
Second characteristic
After a computed Watcher is generated, defineComputed continues.
function defineComputed(target, key, userDef) {...Object.defineProperty(target, key, {
enumerable: true.configurable: true.get: createComputedGetter(key),
set: userDef.set || noop
})
}
Copy the code
Execute the defineComputed method to turn computed into responsive data through Object.defineProperty. In createComputedGetter, encapsulate the getter
function createComputedGetter (key) {
return function () { // Returns the getter function
const watcher = this._computedWatchers && this._computedWatchers[key]
// This can also be used to get computed-watcher for key
if (watcher) {
if (watcher.dirty) { // True when watcher is instantiated, which means it needs to be evaluated
watcher.evaluate() // Evaluate the computed property, calling the computed. Get method passed
}
if (Dep.target) { // The current watcher, here is the page render trigger this method, so render-watcher
watcher.depend() // Collect current watcher
}
return watcher.value // Returns the evaluated value or the previously cached value}}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --class Watcher {... evaluate () {this.value = this.get() // Evaluate attributes
this.dirty = false // Indicates that the calculated property has been calculated and no further calculation is required
}
depend () {
let i = this.deps.length // Deps is an array of dePs that calculate the responsive data accessible within the property
while (i--) {
this.deps[i].depend() // let each DEP collect the current render-watcher}}}Copy the code
After using computed data, the getter method is called, and the Watcher corresponding to computed data is collected, so that computed properties change when the responsive data inside changes.
Now that we have covered the principles of computed computed, you may need to have a deeper understanding of vUE data response principles, such as Dep and Watcher, if you want to fully understand them.
Computed caching mechanism
Computed attributes also have a caching mechanism. Only when the responsive data it depends on changes, the cache will be cleared and the results will be recalculated. In fact, the above analysis already exists, that is, through the dirty attribute, the results will be recalculated and the cache will be replaced only when dirty is true. Dirty is set to true only when its responsive data delivery changes, and is set to false again after recalculation.
if (watcher.dirty) { // True when watcher is instantiated, which means it needs to be evaluated
watcher.evaluate() // Evaluate the computed property, calling the computed. Get method passed
}
Copy the code
conclusion
Let’s summarize the above process.
- When Vue is initialized
initComputed
Theta is generated in itlazy
Property of trueWatcher
.Watcher
According to thelazy
Property does not execute the GET method immediately. - Then perform
defineComputed
By means ofObject.defineProperty
Turn computed into responsive data. - In the use of the
computed
After that, the getter method is called,computed
The correspondingWatcher
Will be collected, which is why after the responsive data in it changes,computed
The calculation properties change.