The use of the watch

This article focuses on the implementation of Watch (also known as the Observer pattern), so how do you use direct viewing code

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <title>Document</title>
</head>
<body>
  <div id="app">
    <p>{{msg}}</p>
    <p>{{count}}</p>
    <button @click="add">add</button>
  </div>
  <script>
    let app = new Vue({
      el:'#app'.data: {msg:'Hello world'.count: 0
      },
      methods: {
        add(){
          this.count++
        }
      },
      // watch
      watch: {// The function name must be the same as the object name in the data source
        count(newVal,oldVal){
          // If the latest value is greater than 10, modify the MSG property in data
          if(newVal>=10) this.msg = 'Hello Watch'}}})</script>
</body>
</html>
Copy the code

What we need to know:

  1. The function name in watch must be the key value of the object in the data source, so that the object corresponding to the key value in the data source can be monitored.
  2. The function in Watch takes two arguments, the first to the assigned new value and the second to the initial value.
  3. Understand the features of Object.defineProperty

implementation

The first step is to identify the requirements

Requirement: Implement a constructor, which can be put into data source and watch method. The function name in watch corresponds to the key name in data, and the data change of the corresponding object in data can be monitored in the corresponding function (data hijacking).

let vm = new Watcher({
  data: {
    a: 0.n:'hello'
  },
  watch: {
    a(newValue, oldValue) {
      console.log(newValue,oldValue); }}})setTimeout(() = > {
  vm.a = 1
}, 2000) 
// Print 1, 0
Copy the code

Now that we know the requirements, let’s implement them

The second step is to create the constructor

First of all, the Watcher constructor has a parameter that should contain data and watch, so we need to check whether the parameter can get data and watch, and use a function to determine whether data and watch are an object

class Watcher{
  constructor(args) {
    // Check whether the parameter passed meets the condition
    this.$data = this.getBaseType(args.data) === "Object" ? args.data : {}
    this.$watch = this.getBaseType(args.watch) === "Object" ? args.watch : {}
  }
  getBaseType(target) {
    const typeStr = Object.prototype.toString.call(target) // "[Object String]"
    // Return type
    return typeStr.slice(8, -1)}}Copy the code

Step 3: Listen for objects in data

If both data and Watch are passed as objects, start data hijacking so that every object in data is listened on.

class Watcher {
  constructor(args) {
    this.$data = this.getBaseType(args.data) === "Object" ? args.data : {}
    this.$watch = this.getBaseType(args.watch) === "Object" ? args.watch : {}
    // Iterates over every key in data. For in iterates over the key of an object
    Object.keys(args.data).forEach(key= > {
      this.setData(key)
    })
  }
  getBaseType(target) {
    const typeStr = Object.prototype.toString.call(target)
    return typeStr.slice(8, -1)}setData(_key) {
    // Object.defineProperty(this) points the context to the current Object
    // This is equivalent to this.$data in the constructor
    Object.defineProperty(this, _key, {
      get: function () {},Set is triggered when the value of // // _key changes
      set: function (val) {
        // val is the modified value of the object corresponding to key
        console.log(val)
      }
    })
  }
}
Copy the code

Note: DefineProperty put this instead of this.$data as the first parameter. This is a confusing point. Since this in defindProperty is already the same as this.$data in constructor, this.$data in defindProperty is the same as this. Hopefully that clears up any confusion.

The fourth step, implementation

Fix the structure in defineProperty, this is the key and it’s easier to understand by looking at the code

class Watcher {
  constructor(args) {
    this.$data = this.getBaseType(args.data) === "Object" ? args.data : {}
    this.$watch = this.getBaseType(args.watch) === "Object" ? args.watch : {}
    // Iterate over each key in the data
    Object.keys(args.data).forEach(key= > {
      this.setData(key)
    })
  }
  getBaseType(target) {
    const typeStr = Object.prototype.toString.call(target) // "[Object String]"
    // Return type
    return typeStr.slice(8, -1)}setData(_key) {
    // Object.defineProperty(this) points the context to the current Object
    // This is equivalent to this.$data in the constructor
    Object.defineProperty(this, _key, {
      get: function () {
        return this.$data[_key]
      },
      set: function (val) {
        // Set is triggered when the value of _key changes
        // Get the value before the modification
        const oldVal = this.$data[_key]
        this.$data[_key] = val
        // This definition specifies that the format in watch must have the same name and be a function
        this.$watch[_key] && this.getBaseType(this.$watch[_key]) === "Function" && (
          // Call the function in watch and pass the parameter
          this.$watch[_key].call(this, val, oldVal)
        )
      }
    })
  }
}
/ / testing
let vm = new Watcher({
  data: {
    a: 0.n: 'hello'
  },
  watch: {
    a(newValue, oldValue) {
      console.log(newValue, oldValue); }}})setTimeout(() = > {
  vm.a = 1
}, 2000) 
/ / 1 0
Copy the code

In fact, Vue watch is far more complex than this, but what we are learning is an idea, understand the needs and then substitute ideas, which is very helpful for learning the source code.

The above are the four steps to realize Watch, which I hope will be helpful to readers. If there are mistakes, welcome to discuss the comments.