This article examines the creation of computed data in Vue2
Take a look at our demo:
<template>
<section>
<div>param1: {{ param1 }}</div>
<div>param2: {{ param2 }}</div>
<div>sum: {{ sum }}</div>
<div>Operation:<button @click="handlehangeRawBtn(1)" name="button">make param1 ++</button>
<button @click="handlehangeRawBtn(2)" name="button">make param2 ++</button>
<button @click="handleConcleComputedData" name="button">Copy the value of param1 to the value of computed minus that is not used in the print template</button>
</div>
<section>
</section>
</section>
</template>
<script>
import child1 from './components/child1.vue'
export default {
name: 'demo'.components: { child1 },
data() {
return {
param1: 1.param2: 2.param3: 3,}},computed: {
sum({ param1, param2 }) {
return param1 + param2
},
minus({ param3, param2 }) {
return param3 - param2
}
},
watch: {
param3(val) {
this.param1 = val
}
},
mounted() {
console.log('App mounted this'.this)},methods: {
handlehangeRawBtn(type) {
this[`param${type}`] + +},handleConcleComputedData() {
this.param1 = this.minus
},
}
}
</script>
Copy the code
InitComputed source code:
const sharedPropertyDefinition = {
enumerable: true.configurable: true.get: noop,
set: noop
}
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if(process.env.NODE_ENV ! = ='production' && getter == null) {
warn(
`Getter is missing for computed property "${key}". `,
vm
)
}
if(! isSSR) {// create internal watcher for the computed property.
// Create an instance of watcher for computed without a key. The second argument to watcher is the function /get function defined for computed
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
/* According to the previous article, the component inherits props and computed data proxies, defines the corresponding getters and setters, Here in instantiation is really creating computed watcher */
if(! (keyin vm)) {
defineComputed(vm, key, userDef)
} else if(process.env.NODE_ENV ! = ='production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
constshouldCache = ! isServerRendering()if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
} else{ sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache ! = =false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop
}
if(process.env.NODE_ENV ! = ='production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`.this)}}Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
Copy the code
Actually, if you look at the source code, computed doesn’t always cache.
The following figure shows how to create a data proxy for props and computed properties when a component inherits (a call to Function VueComponent)
To create a real computed watcher at instantiation time, as shown in the code, let's take the sumComputedWatcher to represent the watcher instance created for sum, the call to the render function, Using vm.sum first triggers a hijacked proxy for the vm.sum.getter bound to the prototype, leading to a call to the corresponding computedGetter (in case you missed the previous article, You can see why you want to call initComputed and initProps on VueComponent, and why you want to create a Sub Vue to inherit Vue. The more I look at it, the more I see it.Copy the code
Don’t put on the prototype of the computed getter, sumComputedWatcher. The get () and sumComputedWatcher. Getter () the confusion.
1| sumComputedWatcher.evaluate()
/* class Watcher { ... , get() { pushTarget(this); let value; const vm = this.vm; try { value = this.getter.call(vm, vm); } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`); } else { throw e; } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value); } popTarget(); this.cleanupDeps(); } return value; }, evaluate() { this.value = this.get(); /* Call sumComputedWatcher's get function, push dep. target into targetStack, then call sumComputedWatcher's getter function, The Watcher class get function is not to be confused with the Watcher hijacking getter function */
this.dirty = false; // The dirty attribute changes to false
}
...,
}
*/
2| sumComputedWatcher.get()
3| pushTarget()
4| sumComputedWatcher.getter.call(vm, vm)// is the same thing as sum(vm), so you can see why, in a computed function, you can take variables directly from the VM
/* sum({param1, param2}) {return param1 + param2} Param1 and Param2 have created their own Observer instances in initData, and this relationship can be clearly seen in the call stack
Copy the code
The call to sum triggers get for param1Dep and param2Dep, respectively.
The process of collecting dependencies then begins
This process completes the construction of the publish-and-subscribe relationship between the sumComputedWatcher and param1Dep and param2Dep, followed by the function call
Call the watcher, the evaluate (); Then call the watcher. After depend (), after the previous section, in our example watcher. Depend () does not go again to collect render watcher and param1, param2, clearly when the render function call collect, Why collect? This makes sense, too, because the variables used in a template don’t have to be all the variables defined when the VM is initialized, because, for computed, I can define them for method calls. For methods that use computed variables, the dependencies are collected the first time the render function is called.
This really means that computed Watcher does dependency collection at call time.Copy the code
Now let’s look at the initialization of watch
function initWatch(vm, watch) {
for (const key in watch) {
const handler = watch[key];
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); }}else{ createWatcher(vm, key, handler); }}}function createWatcher(vm, keyOrFn, handler, options) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(keyOrFn, handler, options); // Call $watch
}
Vue.prototype.$watch = function (expOrFn, cb, options) {
const vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options);
}
options = options || {};
options.user = true;
const watcher = new Watcher(vm, expOrFn, cb, options); // Create a watcher instance
if (options.immediate) {
cb.call(vm, watcher.value);
}
return function unwatchFn() {
watcher.teardown();
};
};
}
Copy the code
The dependency collection for param3Wacter is completed when the component is initialized, analyzing its own dependencies from the watch key. Compared with computed Watcher, computed Watcher has a cache in most cases and only calls updates when the data it really depends on changes, but watch is more about observing its own data and doing some operations.
I don’t want to write here….