By: Small Potato Blog park: www.cnblogs.com/HouJiao/ Nuggets: juejin.cn/user/243617…

1. Introduction

As a Vue developer, I have used computed and watch frequently in my projects, but I have never studied them systematically, and I always feel I have a little knowledge about them.

If you’re like me, let’s review and summarize these two points.

This non-source analysis is just a summary of their usage, features, and so on.

2. Computed in Vue

Computed in Vue is also called computed property, and the Vue website gives an example of this.

The template has one message data to display:

<template>
  <div id="app">
    {{message}}
  </div>
</template>
<script>
export default {
  name: 'App'.data() {
    return {
      message: 'Hello'}}}</script>
Copy the code

Suppose there is a need to reverse message and expose it in a template.

The easiest way to do this is directly in the template:

<template>
  <div id="app">
    <p>{{message}}</p>
    <p>{{message.split('').reverse().join('')}}</p>
  </div>
</template>
Copy the code

At this point, Vue officials told us that too many logical operations would make the template heavy and difficult to maintain, and that the transformation could not be used again, and directed us to use computed attribute-computed to achieve this need.

export default {
  name: 'App'.computed: {
    reverseMessage: function(){
      return this.message.split(' ').reverse().join(' '); }},data() {
    return {
      message: 'Hello'}}}Copy the code

In the code above we define a calculation property: reverseMessage, which has a value of a function and returns the result we want.

There is then a reverseMessage that you can use in the template just as you would with message.

<template>
  <div id="app">
    <p>{{message}}</p>
    <p>{{reverseMessage}}</p>
  </div>
</template>
Copy the code

Then some people must say, I can use methods to achieve it. It is true that methods can also meet this requirement, but in this case our computational attributes have a big advantage over methods, and the advantage is that the computational attributes are cached.

If we use methods to fulfill the previous requirements, when the reversal result of message is used in multiple places, the corresponding methods function will be called for multiple times, and the logic inside the function will also need to be executed for multiple times. As long as the message data has not changed, the function that accesses the calculated property multiple times will only be executed once.

<template>
  <div id="app">
    <p>{{message}}</p>
    <p>First visit to reverseMessage:{{reverseMessage}}</p>
    <p>ReverseMessage :{{reverseMessage}}</p>
    <p>(reverseMessage) {{reverseMessage}}</p>
    <p>ReverseMessage :{{reverseMessage}}</p>
  </div>
</template>

<script>
export default {
  name: 'App'.computed: {
    reverseMessage: function(value){
      console.log(" I'm reverseMessage" )
      return this.message.split(' ').reverse().join(' '); }},data() {
    return {
      message: 'Hello'}}}</script>
Copy the code

Run the project and look at the results. You’ll see that the function that evaluates the property reverseMessage is executed only once.

3. Watch in Vue

The Watch in Vue is also called the listening property. It is mainly used to listen for data changes and perform some operations when data changes.

<template>
  <div id="app">
    <p>Counter: {{counter}}</p>
    <el-button type="primary" @click="counter++">
      Click
    </el-button>
  </div>
</template>

<script>
export default {
  name: 'App'.data() {
    return {
      counter: 0}},watch: {
    / * * *@name: counter
     * @description: * Listens for counter data in Vue data * executes the corresponding listening function when counter changes *@param {*} NewValue counter's newValue *@param {*} OldValue counter oldValue *@return {*} None* /
    counter: function(newValue, oldValue){
      if(this.counter == 10) {this.counter = 0; }}}}</script>
Copy the code

We’ve defined a listening property counter, which is listening for the data in Vue Data that defines counter, and the whole logic is to hit the button counter plus 1, and when counter is equal to 10, set counter to 0.

The above code runs as follows:

The Vue website explicitly recommends using the Watch listening property in this way: this is most useful when asynchronous or expensive operations need to be performed when data changes.

4. Choice between computed and Watch

After reading the above two parts, you have grasped the basic uses of computed and watch in Vue. But there’s more to it than that, so let’s do a little more.

Here we restore an example in the official website of Vue. The functions of the example are roughly as follows:

This function can be simply described as: update fullName when firstName and lastName data change, where fullName value is the concatenation of firstName and lastName.

First we use watch to do this: Watch listens for firstName and lastName and updates the value of fullName when these two data changes.

<template>
  <div id="app">
    <p>firstName: <el-input v-model="firstName" placeholder="Please enter firstName"></el-input></p>
    <p>lastName: <el-input v-model="lastName" placeholder="Please enter lastName"></el-input></p>
    <p>fullName: {{fullName}}</p>
  </div>
</template>

<script>
export default {
  name: 'App'.data() {
    return {
      firstName: ' '.lastName: ' '.fullName: '(null)'}},// Use the watch implementation
  watch: {
    firstName: function(newValue) {
      this.fullName = newValue + ' ' + this.lastName;
    },
    lastName: function(newValue){
      this.fullName = this.firstName + ' '+ newValue; }}}</script>

Copy the code

Then we use computed to do this: define the computed attribute fullName, concatenate the values of firstName and lastName and return them.

<template>
  <div id="app">
    <p>firstName: <el-input v-model="firstName" placeholder="Please enter firstName"></el-input></p>
    <p>lastName: <el-input v-model="lastName" placeholder="Please enter lastName"></el-input></p>
    <p>fullName: {{fullName}}</p>
  </div>
</template>

<script>
export default {
  name: 'App'.data() {
    return {
      firstName: ' '.lastName: ' '}}computed: {
    fullName: function() {
      return this.firstName + ' ' + this.lastName; }}}</script>
Copy the code

We find that both computed and watch can do this, but let’s compare these two different ways:

// Use computed implementation
computed: {
  fullName: function() {
    return this.firstName + ' ' + this.lastName; }},// Use the watch implementation
watch: {
  firstName: function(newValue) {
    this.fullName = newValue + ' ' + this.lastName;
  },
  lastName: function(newValue){
    this.fullName = this.firstName + ' '+ newValue; }}Copy the code

In contrast, it is obvious that computed implementation is simpler and more advanced.

Therefore, in daily project development, we should be careful about the use of computed and Watch:

There is no right or wrong choice and use of these two, but hope to better use, rather than abuse.

5. Calculate the attribute progression

Next, we are going to further study the content of calculating attributes.

5.1 The calculated attribute cannot have the same name as the Vue Data attribute

In The computed property declaration, The computed property cannot have The same name as The one defined in Vue Data. Otherwise, an error occurs: The computed property “XXXXX” is already defined in Data.

For those of you who have read the Vue source code, it should be clear that the Vue initializes as follows: Initialize data in the order initProps-> initMethods -> initData -> initComputed -> initWatch, and define the data to the VM instance via Object.definedProperty. Properties with the same name are overwritten by subsequent properties with the same name.

By printing component instance objects, you can clearly see that props, Methods, Data, and computed are defined on the VM instance.

5.2 Calculate the set function of attributes

In the previous code example, our computed is implemented like this:

computed: {
  reverseMessage: function(){
    return this.message.split(' ').reverse().join(' '); }},Copy the code

This is actually a get method for reverseMessage, so this is the same as:

computed: {
  reverseMessage: {
    // Calculate the property's get method
    get: function(){
      return this.message.split(' ').reverse().join(' '); }}},Copy the code

Alternatively, we can provide a set method for evaluating attributes:

computed: {
  reverseMessage: {
    // Calculate the property's get method
    get: function(){
      return this.message.split(' ').reverse().join(' ');
    },
    set: function(newValue){
       // Set method logic}}},Copy the code

The set method is triggered only if we actively change the value of the evaluated property.

The set method for calculating attributes has not been encountered in the actual project development, but after some thinking, the following example is made:

This example is a conversion between minutes and hours, which can be done nicely with the set method that evaluates attributes:

<template>
  <div id="app">
    <p>minutes<el-input v-model="minute" placeholder="Please enter the content"></el-input></p>
    <p>hours<el-input v-model="hours" placeholder="Please enter the content"></el-input></p>
  </div>
</template>

<script>
export default {
  name: 'App'.data() {
    return {
      minute: 60,}},computed: {
    hours: {get: function() {
        return this.minute / 60;
      },
      set: function(newValue) {
        this.minute = newValue * 60; }}}}</script>
Copy the code

5.3 Calculate the cache of attributes

We concluded earlier that computed attributes are cached and demonstrated an example. So how is the caching of computed attributes implemented?

Caching of calculated attributes requires reading the Vue source code implementation, so let’s take a look at the source code.

I believe that we see the source code of the word will be a bit terrified, but do not worry too much, when the article is written here to consider the content and focus of this article, so it will not be detailed to interpret the source code of computing attributes, focus on learning the cache of computing attributes, and point to the end.

So if you haven’t read Vue’s responsivity principle carefully, it is recommended to ignore the content of this section, and it will be easier to understand the content of this section after you have some understanding of the source code of responsivity. (I have written a related article before, I hope to give you a reference: 1W word long article + many pictures, with you to understand the vue2. X bidirectional data binding source code implementation)

The entry source code for calculating attributes is as follows:

/ * * Vue version: v2.6.12 * code position: / Vue/SRC/core/instance/state. The js * /
export function initState (vm: Component) {
  / /... Omit...
  const opts = vm.$options
  / /... Omit...
  if (opts.computed) initComputed(vm, opts.computed)
  / /... Omit...
}
Copy the code

Then let’s look at initComputed:

/ * * Vue version: v2.6.12 * code position: / Vue/SRC/core/instance/state. The js * @ params: the vm Vue instance object * @ params: computed the calculation of all properties * /
function initComputed (vm: Component, computed: Object) {
 
  /* * object.create (null) : Create an empty Object * const Watchers is the Watcher instance used to hold all calculated attributes */
  const watchers = vm._computedWatchers = Object.create(null)
 
  // Iterate over the calculated properties
  for (const key in computed) {
    const userDef = computed[key]
    / * * for computing the get method * calculation of attribute can be a function, default is provided by the get method can also be objects, respectively the get and set methods * / declaration
    const getter = typeof userDef === 'function' ? userDef : userDef.get
   
    /* * Create watcher * @params: vm vue instance object * @params: getmethod for calculating attributes * @params: /vue/ SRC /shared/util.js export function noop (a? : any, b? : any, c? : any) {} * @params: ComputedWatcherOptions * computedWatcherOptions is an object defined in line 167 of this file * const computedWatcherOptions = {lazy: true} */
    watchers[key] = new Watcher(
      vm,
      getter || noop,
      noop,
      computedWatcherOptions
    )
    // Function call
    defineComputed(vm, key, userDef)
  }
}
Copy the code

In initComputed, you basically iterate over the computed properties, and you do two things in the process:

  • First: create for computed propertieswatcher, i.e.,new Watcher
  • Second: calldefineComputedmethods

So first let’s look at what New Watcher does.

To make it easier for you to see what New Watcher does, I’ve simplified the source code for Watcher, keeping some of the more important code.

At the same time, important parts of the code are added to the notes, some of the notes may be a bit repetitive or wordy, but mainly want to repeat in this way so that we can mull over and understand the content of the source code, convenient for subsequent understanding ~

/* * Vue version: v2.6.12 Vue/SRC/core/observer/watcher. Js * in order to see clearly the role of the watcher * source to simplify, so the following is a simplified version of the watcher class * adjusted * / part of the code sequence at the same time
export default class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object.) {
    // VM is the component instance
    this.vm = vm  
    // expOrFn passes parameters in new Watcher for get methods that evaluate attributes
    // Assign the get method of the evaluated property to the getter property of watcher
    this.getter = expOrFn
    // cb = noop: export function noop (a? : any, b? : any, c? : any) {}
    this.cb = cb  
    // Option passes {lazy: true} in new Watcher
    / /!!!!! The options.lazy operator strongly converts options to Boolean
    // The value of this.lazy is true after assignment
    this.lazy = !! options.lazy// The value of this.dirty is true after assignment
    this.dirty = this.lazy 
    
    /* * The value of this.value is undefined */ in new Watcher because this.lazy is true
    this.value = this.lazy ? undefined : this.get()
  }
  get () { 
    const vm = this.vm
    /* * In the constructor, the get method of the evaluated property is assigned to the getter property of the watcher * so this line of code calls the get method of the evaluated property to get the value of the evaluated property */
    value = this.getter.call(vm, vm)
    return value
  }
  evaluate () {
    /* * Call watcher's get method * So evaluate gets the value of the evaluated property, assigns it to watcher.value * and sets watcher.dirty to false. This dirty is the key to caching 
    this.value = this.get()
    this.dirty = false}}Copy the code

After looking at this simplified version of Watcher, we should have a pretty clear idea of the implementation of the Watcher class.

That brings us to the important point about caching, which is the second thing that iterating over calculated attributes does: call defineComputed:

/ * * Vue version: v2.6.12 * code position: / Vue/SRC/core/instance/state. The js * @ params: target Vue instance object * @ params: key computation attribute name * @ params: UserDef computes the function or object */ defined by the attribute
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {  

  / /... Omit the code logic for sharedPropertyDefinition for the moment......
  
  /* * Const sharedPropertyDefinition = {* Enumerable: Const sharedPropertyDefinition = {* Enumerable: DefineProperty, which is different from any other information system, passes the corresponding parameters to make the calculated property observable */
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code

At its core, the defineComputed method is a single line of code that makes the calculated property observable using Object.defineProperty.

So our next focus is on the third argument passed when we call object.defineProperty: sharedPropertyDefinition.

SharedPropertyDefinition is an object defined in the current file. The default values are as follows:

const sharedPropertyDefinition = {
  enumerable: true.configurable: true.get: noop,
  set: noop
}
Copy the code

In the defineComputed source code posted above, I commented that I omitted a piece of code logic for sharedPropertyDefinition, so the omitted source code is not shown. Its main role is to sharedPropertyDefinition. Get and sharedPropertyDefinition. Set rewrite, rewrite sharedPropertyDefinition after value is:

const sharedPropertyDefinition = {
  enumerable: true.configurable: true.get: function(){
      // Get the watcher instance corresponding to the calculated attribute
      const watcher = this._computedWatchers && this._computedWatchers[key]
      if (watcher) {
        if (watcher.dirty) {
          watcher.evaluate()
        }
        if (Dep.target) {
          watcher.depend()
        }
        return watcher.value
      }
    }
  },
  // set = noop
  // But we need to know that the real value of set is the set function we provide to evaluate attributes
  // Don't get me wrong
  set: noop,  
}
Copy the code

The sharedPropertyDefinition. The logic of the get function is already very clear, at the same time the logic is the key to calculate attribute cache implementation logic: in sharedPropertyDefinition. Get function, to obtain the corresponding watcher example to calculate the attribute; If the value is false, return watcher.value. Otherwise, call watcher.evaluate() to retrieve the value of the evaluated property.

Source code analysis on the calculation of attribute cache here, I believe that we have a certain understanding of the calculation of attribute cache implementation. But just to understand these is not enough, we should go to read through the full source code implementation of computational properties, to have a more transparent understanding of the computational properties.

6. Advanced listening properties

6.1 handler

Previously we implemented the listening property like this:

watch: {
  counter: function(newValue, oldValue){
    if(this.counter == 10) {this.counter = 0; }}}Copy the code

This is equivalent to providing a handler function for counter:

watch: {
  counter: {
    handler: function(newValue, oldValue){
      if(this.counter == 10) {this.counter = 0; }}}}Copy the code

6.2 the immediate

Normally, the functions provided by the listening property are not executed immediately, but only when the corresponding Vue data changes.

If we want the listener function to execute immediately, we can give the listener an immediate option and set its value to true.

watch: {
  counter: {
    handler: function(newValue, oldValue){
      if(this.counter == 10) {this.counter = 0; }},immediate: true}}Copy the code

6.3 deep

If we are listening on vue Data of an object type, the listener is not fired by default when properties within the object change.

<template>
  <div id="app">
    <p><el-input v-model="person.name" placeholder="Please enter your name"></el-input></p>
    <p><el-input v-model="person.age" placeholder="Please enter your age"></el-input></p>
  </div>
</template>
<script>
export default {
  name: 'App'.data() {
    return {
      person: {
        name: 'jack'.age: 20}}},watch: {
    person: function(newValue){
      console.log(newValue.name + ' '+ newValue.age); }}}</script>
Copy the code

Listen on the data of the object type. The listener does not fire:

We can listen for changes to properties inside the object by providing deep: true for the listener property:

watch: {
  person: {
    handler: function(newValue){
      console.log(newValue.name + ' ' + newValue.age);
    },
    deep: true}}Copy the code

However, if you look closely at the above example, you can see that this method is used to listen for data of type Object. If any property inside the Object data changes, the listener function is triggered. If we want to listen for a property in the Object separately, we can use the following method:

watch: {
  'person.name': function(newValue, oldValue){
      / / logic}}Copy the code

7. To summarize

Here is the end of this article, the content is very simple and easy to understand, in this will be the above content to do a summary:

Endless learning, in addition to the basic content, the implementation principle of many features is also what we should pay attention to, but between the original intention of the output of this article, so there is no complete analysis of the implementation of the principle, there is a chance to summarize ~

8. Recent articles

Remember a real Webpack optimization experience

JavaScript execution context is not as difficult as you might think

A preliminary study on Page – skeleton-webpack-Plugin

Vue with Django-rest-FrameWord

Vue with Django-rest-FrameWord

9. Write at the end

If this article has helped you, please follow ❤️ + like ❤️ and encourage the author

Article public number first, focus on unknown treasure program yuan for the first time to get the latest article

Ink ❤ ️ ~