preface

This is a small wave in learning Vue summary of an article, in this article we come to know Vue2 X reactive principle, then we implemented a Vue reactive principle (write the content of the simple) implementation steps and notes write very clear, we are interested in can be patient to watch, you will be able to communicate in the comments, many, also hope you can give a small wave a praise

Vue 2.x Response principle

1. DefineProperty applications

DefineProperty is used in vue2.x response to hijack data, so we must have some understanding of it, so let’s first understand its use method, here we use defineProperty to simulate data in Vue

<body>
    <div id="app"></div>
    <script>
        // Simulate Vue's data
        let data = {
            msg: ' ',}// Simulate the Vue instance
        let vm = {}
        // Hijack the MSG of the VM
        Object.defineProperty(vm, 'msg', {
            // Get the data
            get() {
                return data.msg
            },
            / / set the MSG
            set(newValue) {
                // If the values passed in are equal, do not change them
                if (newValue === data.msg) return
                // Modify the data
                data.msg = newValue
                document.querySelector('#app').textContent = data.msg
            },
        })
        // This will call defineProperty vm. MSG set
        vm.msg = '1234'
    </script>
</body>
Copy the code

You can see that the vm. MSG data above is responsive

2. DefineProperty modify multiple parameters to response

Modify multiple parameters

The above method can only modify one property. In fact, we can’t have only one data in the data. Why don’t we define a method that changes all the data in the data to be responsive when traversing the data

<body>
    <div id="app"></div>
	<script>
        // Simulate Vue's data
        let data = {
            msg: 'ha ha'.age: '18',}// Simulate the Vue instance
        let vm = {}
        // Convert multiple attributes into responses
        function proxyData() {
            [MSG,age] = [MSG,age]
            Object.keys(data).forEach((key) = > {
                // Hijack vm attributes
                Object.defineProperty(vm, key, {
                    / / can be enumerated
                    enumerable: true./ / can be configured
                    configurable: true.// Get the data
                    get() {
                        return data[key]
                    },
                    // Set the property value
                    set(newValue) {
                        // If the values passed in are equal, do not change them
                        if (newValue === data[key]) return
                        // Modify the data
                        data[key] = newValue
                        document.querySelector('#app').textContent = data[key]
                    },
                })
            })
        }
        // Call the method
        proxyData(data)

	</script>
</body>
Copy the code

3.Proxy

Use Proxy in Vue3 to set responsive properties

Let’s take a look at the Proxy’s two parameters

new Proxy(target,handler)

  • target: to useProxyThe wrapped target object (which can be any type of object, including native arrays, functions, or even another proxy)
  • handler: An object that usually has functions as properties, each of which defines a proxy for performing various operationspThe behavior of the

The logic is similar to the vue 2.x implementation, but the implementation method is different

So put the code in

<body>
    <div id="app"></div>
    <script>
            // Simulate Vue data
            let data = {
                msg: ' '.age: ' ',}// Simulate an instance of Vue
            // Proxy is the first
            let vm = new Proxy(data, {
                // get() gets the value
                // Target indicates the object to be proxied
                // Key is the key of the object
                get(target, key) {
                    return target[key]
                },
                / / set the value
                // newValue is the set value
                set(target, key, newValue) {
                    // Check to see if it saves as much performance as the previous value
                    if (target[key] === newValue) return
                    // Set the value
                    target[key] = newValue
                    document.querySelector('#app').textContent = target[key]
                },
            })
    </script>
</body>
Copy the code

Methods that trigger set and get

// The set method is triggered
vm.msg = 'haha'
// The get method is triggered
console.log(vm.msg)
Copy the code

4. Publish and subscribe

The publish subscribe pattern is applied in Vue responsiveness so let’s take a look at it

So first of all there are three roles

To take a real world example, the author (publisher) writes an article and sends it to nugget (signal center), nugget can process the article and push it to the front page, and then the respective biggie (subscriber) can subscribe to the article

An example in Vue is EventBus $on $emit

Let’s simply emulate Vue’s event bus

The previous code was a little too wide to indent 4 units, so let’s change it to 2

<body>
  <div id="app"></div>
  <script>
    class Vue {
      constructor() {
        // To store events
        Subs = {'myclick': [fn1, fn2, fn3],' inputChange ': [fn1, fn2]} this.subs = {'myclick': [fn1, fn2]}
        this.subs = {}
      }
      // Implement the $on method. Type is the type of the task queue. Fn is the method
      $on(type, fn) {
        // Check whether subs has a method queue of the current type
        if (!this.subs[type]) {
          // Add an array that defaults to null
          this.subs[type] = []
        }
        // Add the method to the type
        this.subs[type].push(fn)
      }
      // Implement the $emit method
      $emit(type) {
        // We must first determine whether the method exists
        if (this.subs[type]) {
          // Get the parameter
          const args = Array.prototype.slice.call(arguments.1)
          // The circular queue calls fn
          this.subs[type].forEach((fn) = >fn(... args)) } } }/ / use
    const eventHub = new Vue()
    Subs ['sum'] subs['sum'] subs['sum'
    eventHub.$on('sum'.function () {
      let count = [...arguments].reduce((x, y) = > x + y)
      console.log(count)
    })
    // Trigger the sum method
    eventHub.$emit('sum'.1.2.4.5.6.7.8.9.10)
  </script>
</body>
Copy the code

5. Observer mode

Difference from publish subscribe

The publisher and the subscriber (observer) in the observer are interdependent and the observer must be required to subscribe to the content change event as opposed to the published subscriber, and the published subscriber is scheduled by the scheduling center. So how does the observer pattern depend on each other

<body>
  <div id="app"></div>
  <script>
    / / target
    class Subject {
      constructor() {
        this.observerLists = []
      }
      // Add an observer
      addObs(obs) {
        // A method to determine whether an observer has or does not have an update subscription
        if (obs && obs.update) {
          // Add to the observer list
          this.observerLists.push(obs)
        }
      }
      // Notify the observer
      notify() {
        this.observerLists.forEach((obs) = > {
          // Each observer will update the event when notified
          obs.update()
        })
      }
      // Clear the observer
      empty() {
        this.subs = []
      }
    }

    class Observer {
      // Define an observer content update event
      update() {
        // Update the logic of the event to be processed
        console.log('Target updated')}}/ / use
    // Create the target
    let sub = new Subject()
    // Create an observer
    let obs1 = new Observer()
    let obs2 = new Observer()
    // Add observers to the list
    sub.addObs(obs1)
    sub.addObs(obs2)
    // If the target is enabled, each observer will trigger its own update event
    sub.notify()
  </script>
</body>
Copy the code

6. Simulate the response principle of Vue

Here to achieve a small simple Vue to achieve the following functions

  • Receive initialized parameters. Here are a few simple examples: EL Data Options
  • Register data with Vue using the private _proxyData method to turn it into a getter setter
  • Use an observer to add attributes in data to yourself as responsive
  • Update the view through the observer pattern by using the observer method to listen for all property changes in the data
  • Use compiler to compile expressions for the difference between instructions and text nodes on element nodes

1.vue.js

This is where you get the EL data

_proxyData registers data attributes to Vue and turns them into getter setters

/* vue.js */

class Vue {
  constructor(options) {
    // Get an object passed in that has no default null object
    this.$options = options || {}
    / / for el
    this.$el =
      typeof options.el === 'string'
        ? document.querySelector(options.el)
        : options.el
    / / get the data
    this.$data = options.data || {}
    // Call _proxyData to process attributes in data
    this._proxyData(this.$data)
  }
  // Register the attributes in data to Vue
  _proxyData(data) {
    Object.keys(data).forEach((key) = > {
      // Perform data hijacking
      // Add each data attribute to Vue to convert it into a getter setter method
      Object.defineProperty(this, key, {
        // Settings can be enumerated
        enumerable: true.// Settings can be configured
        configurable: true.// Get the data
        get() {
          return data[key]
        },
        // Set the data
        set(newValue) {
          // Determine whether the new value and the old value are equal
          if (newValue === data[key]) return
          // Set the new value
          data[key] = newValue
        },
      })
    })
  }
}
Copy the code

2.observer.js

In this case, the attributes in the data are changed to be responsive, and another major feature is the observer pattern, which will be used in detail in section 4.dep.js

/* observer.js */

class Observer {
  constructor(data) {
    // To iterate over data
    this.walk(data)
  }
  // Iterate over data into response
  walk(data) {
    // Check whether data is null and an object
    if(! data ||typeofdata ! = ='object') return
    / / traverse data
    Object.keys(data).forEach((key) = > {
      // Convert to response
      this.defineReactive(data, key, data[key])
    })
  }
  // Convert to response
  // What is different from vue.js
  Var var var var var var var var var var var var var var var var var
  // Convert a property in data to a getter setter
  defineReactive(obj, key, value) {
    // If an object type is called, then walk is returned. If an object type is not called, then walk is returned
    this.walk(value)
    // 保存一下 this
    const self = this
    Object.defineProperty(obj, key, {
      // The Settings are enumerable
      enumerable: true.// The Settings are configurable
      configurable: true./ / get the value
      get() {
        return value
      },
      / / set the value
      set(newValue) {
        // Determine whether the old value and the new value are equal
        if (newValue === value) return
        // Set the new value
        value = newValue
        If newValue is an object, then the properties in the object should also be set to reactive
        self.walk(newValue)
      },
    })
  }
}

Copy the code

Pay attention to order when introducing in HTML

<script src="./js/observer.js"></script>
<script src="./js/vue.js"></script>
Copy the code

Then use the Observer in vue.js

/* vue.js */

class Vue {
  constructor(options){...// Use Obsever to convert data from data into response
    new Observer(this.$data)
  }
  // Register the attributes in data to Vue
  _proxyData(data){... }}Copy the code

See why we’re doing two repetitive operations here? Repeat twice to turn the attributes of data into responses

In obsever. Js we add all the properties of the data to the data itself in a getter setter

In vue.js, all attributes of data are also added to vue, so that future aspect operations can be accessed directly by an instance of vue or by using this in vue

Using the example

<body>
    <div id="app"></div>
    <script src="./js/observer.js"></script>
    <script src="./js/vue.js"></script>
    <script>
      let vm = new Vue({
        el: '#app'.data: {
          msg: '123'.age: 21,}})</script>
  </body>
Copy the code

Thus all data attributes are present in Vue and $data and are responsive

3.compiler.js

Comilper.js implements the text node and element node directives in this file mainly for example. Of course, this simple instruction implements v-text and V-Model

/* compiler.js */

class Compiler {
  // Vm refers to the Vue instance
  constructor(vm) {
    / / get the vm
    this.vm = vm
    / / get the el
    this.el = vm.$el
    // Compile the template
    this.compile(this.el)
  }
  // Compile the template
  compile(el) {
    // Get the child nodes and turn the pseudo-array into a true array if you use forEach traversal
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) = > {
      // Compile according to different node types
      // A text node
      if (this.isTextNode(node)) {
        // Compile the text node
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        // Element node
        this.compileElement(node)
      }
      // Determine if there are children to consider recursion
      if (node.childNodes && node.childNodes.length) {
        // Continue to recursively compile the template
        this.compile(node)
      }
    })
  }
  // Compile the text node (simple implementation)
  compileText(node) {
    // The core idea is to use the regular expression to remove {{}} to find the variable inside
    // Then go to Vue to find the variable and assign it to Node.textContent
    let reg = / \ {\ {(. +?) \} \} /
    // Get the text content of the node
    let val = node.textContent
    // Determine whether {{}}
    if (reg.test(val)) {
      // Retrieve the contents of group 1 ({{}})
      let key = RegExp.$1.trim()
      // Replace and assign to node
      node.textContent = val.replace(reg, this.vm[key])
    }
  }
  // Compile element nodes. Only instructions are processed here
  compileElement(node) {
    // Get all the attributes above the element node to iterate over! [...node.attributes].forEach((attr) = > {
      // Get the attribute name
      let attrName = attr.name
      // Check whether the command starts with v-
      if (this.isDirective(attrName)) {
        // Remove v- easy to operate
        attrName = attrName.substr(2)
        // The value of the command is the MSG in v-text = "MSG"
        // MSG is the key of Vue
        let key = attr.value
        // The instruction operation executes the instruction method
        // There are many vue directives. In order to avoid a large number of ifs, write a uapdate method
        this.update(node, key, attrName)
      }
    })
  }
  // Add the instruction method and execute it
  update(node, key, attrName) {
    // For example, add textUpdater to handle the V-text method
    // We should call the built-in textUpdater method
    // Do you want to add a suffix
    let updateFn = this[attrName + 'Updater']
    // The built-in method can be called if it exists
    updateFn && updateFn(node, key, this.vm[key])
  }
  // Write the corresponding method in advance such as v-text
  // Use the same as Vue
  textUpdater(node, key, value) {
    node.textContent = value
  }
    
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
  }
    
  // Determine whether the element attribute is a vue directive
  isDirective(attr) {
    return attr.startsWith('v-')}// Check whether it is an element node
  isElementNode(node) {
    return node.nodeType === 1
  }
  // Check whether it is a text node
  isTextNode(node) {
    return node.nodeType === 3}}Copy the code

4.dep.js

Write a Dep class that acts as a publisher in the observer and each responsive property creates a Dep object, a Watcher object that collects the dependent property (which is done when the responsive data is used)

When we update the setter for a responsive property, we call the notify method in Dep to send an update notification

The view is updated by calling the Watcher update. The view is updated by calling the Watcher Update.

In general, the Dep(publisher here) is responsible for collecting dependency add observers (wathcers here) and notifies the observers when the setter data is updated

With so much repetition, you should know at which stage the dependencies are collected and at which stage the observers are notified, so let’s implement it

Write Dep class

/* dep.js */

class Dep {
  constructor() {
    // Store the observer
    this.subs = []
  }
  // Add an observer
  addSub(sub) {
    // Determine whether the observer has and has the update method
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // The notification method
  notify() {
    // Trigger the update method for each observer
    this.subs.forEach((sub) = > {
      sub.update()
    })
  }
}

Copy the code

Use Dep in observer.js

Add dep. target (observer) to GET

Trigger notify in a set

/* observer.js */

class Observer {... }// Iterate over data into response
  walk(data){... }// Convert a property in data to a getter setter
  defineReactive(obj, key, value){...// Create the Dep object
    let dep = new Dep()
    Object.defineProperty(obj, key, {
	  ...
      / / get the value
      get() {
        // Add the observer object dep. target to represent the observer
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      / / set the value
      set(newValue) {
        if (newValue === value) return
        value = newValue
        self.walk(newValue)
        // Trigger notification update view
        dep.notify()
      },
    })
  }
}

Copy the code

5.watcher.js

**watcher **

/* watcher.js */

class Watcher {
  constructor(vm, key, cb) {
    // The vm is a Vue instance
    this.vm = vm
    // Key is an attribute in data
    this.key = key
    // The cb callback function updates the specific method of the view
    this.cb = cb
    // Store the observer in dep.target
    Dep.target = this
    // Compare old data when updating the view
    // The other thing is that the VM [key] emits the get method
    // Add the observer to the dep.subs via dep.addsub (dep.target) in get
    this.oldValue = vm[key]
    // Dep.target does not need to exist because the above operation is already saved
    Dep.target = null
  }
  // The requisite method in the observer is used to update the view
  update() {
    // Get the new value
    let newValue = this.vm[this.key]
    // Compare the old value with the new value
    if (newValue === this.oldValue) return
    // Call the specific update method
    this.cb(newValue)
  }
}

Copy the code

So where do you create a Watcher? Remember the compiler operation on the text node in compiler.js

Add a Watcher here after compiling the text node

There is also the V-text V-Model directive to add a Watcher when compiling an element node

/* compiler.js */

class Compiler {
  // Vm refers to the Vue instance
  constructor(vm) {
    / / get the vm
    this.vm = vm
    / / get the el
    this.el = vm.$el
    // Compile the template
    this.compile(this.el)
  }
  // Compile the template
  compile(el) {
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) = > {
      if (this.isTextNode(node)) {
        // Compile the text node
        this.compileText(node)
      } 
       ...
  }
  // Compile the text node (simple implementation)
  compileText(node) {
    let reg = / \ {\ {(. +) \} \} /
    let val = node.textContent
    if (reg.test(val)) {
      let key = RegExp.$1.trim()
      node.textContent = val.replace(reg, this.vm[key])
      // Create an observer
      new Watcher(this.vm, key, newValue= > {
        node.textContent = newValue
      })
    }
  }
  ...
  // v-text 
  textUpdater(node, key, value) {
    node.textContent = value
     // Create observer 2
    new Watcher(this.vm, key, (newValue) = > {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
    // Create an observer
    new Watcher(this.vm, key, (newValue) = > {
      node.value = newValue
    })
    // Here we implement bidirectional binding to listen for input events to modify attributes in data
    node.addEventListener('input'.() = > {
      this.vm[key] = node.value
    })
  }
}

Copy the code

The set() method is triggered when we change the responsive property, and then the publisher dep.notify method is started, The update method calls the cb(newValue) callback function and passes the newValue to cb(), which is the same method that updates the view

Take the cb method as the third parameter in the example above

new Watcher(this.vm, key, newValue= > {
    node.textContent = newValue
})
Copy the code

There is one more point to achieve two-way binding of the V-Model

Not only do you change the data to trigger an updated view, but you also add input events to node to change properties in the data

To achieve the effect of bidirectional binding

7. Test your own

So far both reactive and bidirectional binding have been implemented so let’s write an example to test it

<body>
  <div id="app">
    {{msg}} <br />
    {{age}} <br />
    <div v-text="msg"></div>
    <input v-model="msg" type="text" />
  </div>
  <script src="./js/dep.js"></script>
  <script src="./js/watcher.js"></script>
  <script src="./js/compiler.js"></script>
  <script src="./js/observer.js"></script>
  <script src="./js/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app'.data: {
        msg: '123'.age: 21,}})</script>
</body>
Copy the code

OK basically implements the responsive principle through the observer pattern

8. Five file codes

Here directly put the 5 files of the code posted above some places omitted, the following is the complete convenient for everyone to read

vue.js

/* vue.js */

class Vue {
  constructor(options) {
    // Get an object passed in that has no default null object
    this.$options = options || {}
    / / for el
    this.$el =
      typeof options.el === 'string'
        ? document.querySelector(options.el)
        : options.el
    / / get the data
    this.$data = options.data || {}
    // Call _proxyData to process attributes in data
    this._proxyData(this.$data)
    // Use Obsever to convert data from data into response
    new Observer(this.$data)
    // Compile the template
    new Compiler(this)}// Register the attributes in data to Vue
  _proxyData(data) {
    Object.keys(data).forEach((key) = > {
      // Perform data hijacking
      // Add each data attribute to Vue to convert it into a getter setter method
      Object.defineProperty(this, key, {
        // Settings can be enumerated
        enumerable: true.// Settings can be configured
        configurable: true.// Get the data
        get() {
          return data[key]
        },
        // Set the data
        set(newValue) {
          // Determine whether the new value and the old value are equal
          if (newValue === data[key]) return
          // Set the new value
          data[key] = newValue
        },
      })
    })
  }
}

Copy the code

obsever.js

/* observer.js */

class Observer {
  constructor(data) {
    // To iterate over data
    this.walk(data)
  }
  // Iterate over data into response
  walk(data) {
    // Check whether data is null and an object
    if(! data ||typeofdata ! = ='object') return
    / / traverse data
    Object.keys(data).forEach((key) = > {
      // Convert to response
      this.defineReactive(data, key, data[key])
    })
  }
  // Convert to response
  // What is different from vue.js
  Var var var var var var var var var var var var var var var var var
  // Convert a property in data to a getter setter
  defineReactive(obj, key, value) {
    // If an object type is called, then walk is returned. If an object type is not called, then walk is returned
    this.walk(value)
    // 保存一下 this
    const self = this
    // Create the Dep object
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      // The Settings are enumerable
      enumerable: true.// The Settings are configurable
      configurable: true./ / get the value
      get() {
        // Add the observer object dep. target to represent the observer
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      / / set the value
      set(newValue) {
        // Determine whether the old value and the new value are equal
        if (newValue === value) return
        // Set the new value
        value = newValue
        If newValue is an object, then the properties in the object should also be set to reactive
        self.walk(newValue)
        // Trigger notification update view
        dep.notify()
      },
    })
  }
}

Copy the code

compiler.js

/* compiler.js */

class Compiler {
  // Vm refers to the Vue instance
  constructor(vm) {
    / / get the vm
    this.vm = vm
    / / get the el
    this.el = vm.$el
    // Compile the template
    this.compile(this.el)
  }
  // Compile the template
  compile(el) {
    // Get the child nodes and turn the pseudo-array into a true array if you use forEach traversal
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) = > {
      // Compile according to different node types
      // A text node
      if (this.isTextNode(node)) {
        // Compile the text node
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        // Element node
        this.compileElement(node)
      }
      // Determine if there are children to consider recursion
      if (node.childNodes && node.childNodes.length) {
        // Continue to recursively compile the template
        this.compile(node)
      }
    })
  }
  // Compile the text node (simple implementation)
  compileText(node) {
    // The core idea is to use the regular expression to remove {{}} to find the variable inside
    // Then go to Vue to find the variable and assign it to Node.textContent
    let reg = / \ {\ {(. +?) \} \} /
    // Get the text content of the node
    let val = node.textContent
    // Determine whether {{}}
    if (reg.test(val)) {
      // Retrieve the contents of group 1 ({{}})
      let key = RegExp.$1.trim()
      // Replace and assign to node
      node.textContent = val.replace(reg, this.vm[key])
      // Create an observer
      new Watcher(this.vm, key, (newValue) = > {
        node.textContent = newValue
      })
    }
  }
  // Compile element nodes. Only instructions are processed here
  compileElement(node) {
    // Get all the attributes above the element node to iterate over! [...node.attributes].forEach((attr) = > {
      // Get the attribute name
      let attrName = attr.name
      // Check whether the command starts with v-
      if (this.isDirective(attrName)) {
        // Remove v- easy to operate
        attrName = attrName.substr(2)
        // The value of the command is the MSG in v-text = "MSG"
        // MSG is the key of Vue
        let key = attr.value
        // The instruction operation executes the instruction method
        // There are many vue directives. In order to avoid a large number of ifs, write a uapdate method
        this.update(node, key, attrName)
      }
    })
  }
  // Add the instruction method and execute it
  update(node, key, attrName) {
    // For example, add textUpdater to handle the V-text method
    // We should call the built-in textUpdater method
    // Do you want to add a suffix
    let updateFn = this[attrName + 'Updater']
    // The built-in method can be called if it exists
    updateFn && updateFn.call(this, node, key, this.vm[key])
  }
  // Write the corresponding method in advance such as v-text
  // Use the same as Vue
  textUpdater(node, key, value) {
    node.textContent = value
    // Create an observer
    new Watcher(this.vm, key, (newValue) = > {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
    // Create an observer
    new Watcher(this.vm, key, (newValue) = > {
      node.value = newValue
    })
    // Bidirectional binding is implemented here
    node.addEventListener('input'.() = > {
      this.vm[key] = node.value
    })
  }

  // Determine whether the element attribute is a vue directive
  isDirective(attr) {
    return attr.startsWith('v-')}// Check whether it is an element node
  isElementNode(node) {
    return node.nodeType === 1
  }
  // Check whether it is a text node
  isTextNode(node) {
    return node.nodeType === 3}}Copy the code

dep.js

/* dep.js */

class Dep {
  constructor() {
    // Store the observer
    this.subs = []
  }
  // Add an observer
  addSub(sub) {
    // Determine whether the observer has and has the update method
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // The notification method
  notify() {
    // Trigger the update method for each observer
    this.subs.forEach((sub) = > {
      sub.update()
    })
  }
}

Copy the code

watcher.js

/* watcher.js */

class Watcher {
  constructor(vm, key, cb) {
    // The vm is a Vue instance
    this.vm = vm
    // Key is an attribute in data
    this.key = key
    // The cb callback function updates the specific method of the view
    this.cb = cb
    // Store the observer in dep.target
    Dep.target = this
    // Compare old data when updating the view
    // The other thing is that the VM [key] emits the get method
    // Add the observer to the dep.subs via dep.addsub (dep.target) in get
    this.oldValue = vm[key]
    // Dep.target does not need to exist because the above operation is already saved
    Dep.target = null
  }
  // The requisite method in the observer is used to update the view
  update() {
    // Get the new value
    let newValue = this.vm[this.key]
    // Compare the old value with the new value
    if (newValue === this.oldValue) return
    // Call the specific update method
    this.cb(newValue)
  }
}

Copy the code