computed and watch definition
1.computed is a calculated attribute, similar to a filter, to process the data bound to the view. Examples from the official website:
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div>
var vm = new Vue ({ the: '#example' , data: { message: 'Hello' }, computed: { // Calculate attribute getter reversedMessage: function () { // `this` points to vm instance return this .message.split (''). Reverse (). Join ('' ) } } })
result:
Original message: "Hello"
Computed reversed message: "olleH"
Calculated attributes are cached based on their dependencies. They will only be re-evaluated when the relevant dependencies change.
It is worth noting that "reversedMessage" cannot be defined in the props and data of the component, otherwise an error will be reported.
2.watch is a listening action, used to observe and respond to data changes on the Vue instance. Examples on the official website:
<div id="watch-example"> <p> Ask a yes/no question: <input v-model="question"> </p> <p>{{ answer }}</p> </div>
<!-Because the ecology of AJAX libraries and common tools is already quite rich, Vue core code is not repeated-> <!-Provide these features to keep it streamlined. This also allows you to freely choose the tools you are more familiar with. -> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script> <script> var watchExampleVM = new Vue({ el: '#watch-example', data: { question: '', answer: 'I cannot give you an answer until you ask a question!' }, watch: { // If `question` changes, this function will run question: function (newQuestion, oldQuestion) { this .answer = 'Waiting for you to stop typing ...' this .debouncedGetAnswer () } }, created: function () { // `_.debounce` is a function that limits the operating frequency through Lodash. // In this example, we want to limit the frequency of access yesno.wtf/api // AJAX request will be issued until the user input is completed. To learn more about the // `_.debounce` function (and its close relative` _.throttle`), // please refer to: https://lodash.com/docs#debounce this .debouncedGetAnswer = _.debounce ( this .getAnswer, 500 ) }, methods: { getAnswer: function () { if (this.question.indexOf('?') === -1) { this.answer = 'Questions usually contain a question mark. ;-)' return } this.answer = 'Thinking...' var vm = this axios.get('https://yesno.wtf/api') .then(function (response) { vm.answer = _.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = 'Error! Could not reach the API. ' + error }) } } }) </script>
In this example, using the watch option allows us to perform an asynchronous operation (access an API), limits how often we perform the operation, and sets the intermediate state before we get the final result. These are impossible to calculate attributes.
Similarities and differences between computed and watch usage
The following summarizes the similarities and differences between the two usages:
Same : both computed and watch play the role of monitoring / relying on one data and processing
Similarities and differences : They are all Vue implementations of listeners, but computed is mainly used to process synchronized data, and watch is mainly used to observe the change of a certain value to complete a complex business logic with a large cost . When calculated can be used, it is preferred to use calculated to avoid the embarrassing situation of multiple calls to watch when multiple data affects one of the data.
Advanced usage of watch
1.handler method and immediate attribute
<div id="demo">{{ fullName }}</div>
var vm = new Vue ({ the: '#demo' , data: { firstName: 'Foo', lastName: 'Bar', fullName: 'Foo Bar' }, watch: { firstName: function (val) { console.log ( 'Not executed for the first time ~' ) this .fullName = val + '' + this .lastName } } })
As you can see, the watch will not be executed during initialization. Looking at the example above, only when the value of firstName changes will the monitoring calculation be performed.
But what if you want to execute it the first time it is bound? At this time we have to modify our example:
watch: { firstName: { trades (val) { console.log ( 'First time executed ~' ) this .fullName = val + '' + this .lastName }, // On behalf of the firstName method declared in the watch, immediately execute the handler method immediately: true } }
Open the console and you can see that 'the first time executed ~' is printed.
Have you noticed the handler? We bind a handler method to firstName. The watch method we wrote before actually writes this handler by default. Vue.js will handle this logic, and it will actually be compiled into this handler.
And immediate: true means that if the firstName is declared in wacth, it will immediately execute the handler method inside. If it is false, it will have the same effect as our previous one, and will not be executed when it is bound.
2.deep attribute
There is also a deep attribute in the watch, which indicates whether to enable deep monitoring. The default is false. Let's look at an example:
<div id="app"> <div>obj.a: {{obj.a}}</div> <input type="text" v-model="obj.a"> </div>
var vm = new Vue ({ the: '#app' , data: { obj: { a: 1 } }, watch: { obj: { trades (val) { console.log('obj.a changed') }, immediate: true } } })
By default, in the handler method, it only listens to changes in its reference to the obj property. We only listen to it when we assign a value to obj. For example, we reassign obj in the mounted event hook function:
mounted() { this.obj = { a: '123' } }
This will execute the handler and print out 'obj.a changed'.
But what if we need to monitor the attribute value in obj? At this time, the deep attribute comes in handy. We only need to add deep: true to deeply monitor the value of the attribute in obj.
watch: { obj: { trades (val) { console.log('obj.a changed') }, immediate: true, deep: true } }
The deep attribute means deep traversal, which will traverse down the object layer by layer, and add listeners to each layer.
The embodiment in the source code is defined in src / core / observer / traverse.js:
/* @flow */ import { _Set as Set, isObject } from '../util/index' import type { SimpleSet } from '../util/index' import VNode from '../vdom/vnode' const seenObjects = new Set() /** * Recursively traverse an object to evoke all converted * getters, so that every nested property inside the object * is collected as a "deep" dependency. */ export function traverse (val: any) { _traverse(val, seenObjects) seenObjects.clear() } function _traverse (val: any, seen: SimpleSet) { let i, keys const isA = Array.isArray(val) if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } if (val.__ob__) { const depId = val.__ob__.dep.id if (seen.has(depId)) { return } seen.add(depId) } if (isA) { i = val.length while (i--) _traverse(val[i], seen) } else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[keys[i]], seen) } }
If this.deep == true, that is, there is deep, then trigger the dependence of each deep object and track its changes.
The traverse method recurses each object or array, triggering their getter, so that each member of the object or array is collected by dependency, forming a "deep" dependency relationship.
This function implementation also has a small optimization. During the traversal process, the sub-responsive objects will be recorded to seenObjects through their dep.id to avoid repeated access in the future.
However, using the deep attribute will add a listener to each layer, and the performance overhead may be very large. This way we can optimize in the form of strings:
watch: { 'obj.a': { trades (val) { console.log('obj.a changed') }, immediate: true // deep: true } }
Until the 'obj.a' attribute is encountered, a listener function will be set for this attribute to improve performance.
The essence of computed-computed watch
We know that _init method will be called when new Vue (), this method will initialize the life cycle, initialize events, initialize render, initialize data, computed, methods, wacther and so on.
Today we mainly look at the implementation of the following initialization watch (initWatch), defined in src / core / instance / state.js:
// An object used to pass in a Watcher instance, namely calculated watcher const computedWatcherOptions = {computed: true } function initComputed (vm: Component, computed: Object) { // $ flow-disable-line // Declare a watchers and mount them on the VM instance at the same time const watchers = vm._computedWatchers = Object.create ( null ) // In SSR In the mode, the computed property can only trigger the getter method const isSSR = isServerRendering () // Traverse the passed computed method for (const key in computed) { // Remove each method in the computed object and assign it to userDef 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 it is not SSR server rendering, create a watcher instance if (! IsSSR) { // create internal watcher for the computed property. 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. if (! (key in vm)) { // if the key in computed is not set Go to vm, mount it through defineComputed function defineComputed (vm, key, userDef) } else if (process.env.NODE_ENV! == 'production' ) { // If data and props have the same name as the key in the calculated, warning 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) } } } }
Through the source code, we can find that it first declared an empty object named watchers, and also mounted the empty object on the vm.
Then iterate over the calculated attributes, and assign the method of each attribute to userDef. If userDef is a function, assign it to the getter. Then determine whether it is server-side rendering. If not, create a Watcher instance.
However, it should be noted that in the newly created instance, we pass in the fourth parameter, which is calculatedWatcherOptions. const computedWatcherOptions = {computed: true}, this object is the key to realize the calculated watcher. At this time, the logic in Watcher has changed: