Handwritten a simple vue response to take you to understand the principle of response

Posted by introvert on Sun, 02 Jan 2022 19:31:52 +0100

Vue2.x-response principle

1. Application of defineproperty

In vue2 X response uses defineProperty for data hijacking, so we must have a certain understanding of it. Let's first understand its usage. Here, we use defineProperty to simulate data in Vue.

<body>
    <div id="app"></div>
    <script>
        // Simulate Vue data
        let data = {
            msg: '',
        }
        // Simulate Vue instances
        let vm = {}
        // Hijack the msg of vm
        Object.defineProperty(vm, 'msg', {
            // get data
            get() {
                return data.msg
            },
            // Set msg
            set(newValue) {
                // If the values passed in are equal, they do not need to be modified
                if (newValue === data.msg) return
                // Modify data
                data.msg = newValue
                document.querySelector('#app').textContent = data.msg
            },
        })
        // This calls defineproperty VM MSG set
        vm.msg = '1234'
    </script>
</body>

You can see VM MSG data is responsive

2.defineProperty modifies multiple parameters to be responsive

Modify multiple parameters

After looking at the above methods, only one attribute can be modified. In fact, there can't be only one data in our data. Why don't we define a method to traverse the data in data into a response.

<body>
    <div id="app"></div>
	<script>
        // Simulate Vue data
        let data = {
            msg: 'ha-ha',
            age: '18',
        }
        // Simulate Vue instances
        let vm = {}
        // Convert multiple attributes into a response
        function proxyData() {
            // Take out [msg,age] for each item in data
            Object.keys(data).forEach((key) => {
                // Data hijacking of vm properties
                Object.defineProperty(vm, key, {
                    // enumerable 
                    enumerable: true,
                    // Configurable
                    configurable: true,
                    // get data
                    get() {
                        return data[key]
                    },
                    // Set attribute value
                    set(newValue) {
                        // If the values passed in are equal, they do not need to be modified
                        if (newValue === data[key]) return
                        // Modify data
                        data[key] = newValue
                        document.querySelector('#app').textContent = data[key]
                    },
                })
            })
        }
        // Call method
        proxyData(data)

	</script>
</body>

3.Proxy

Use Proxy in Vue3 to set the properties of a response

Let's first understand the two parameters of Proxy

new Proxy(target,handler)

  • Target: the target object to be wrapped by Proxy (it can be any type of object, including a native array, a function, or even another Proxy)
  • Handler: an object that usually takes a function as an attribute. The functions in each attribute define the behavior of the handler when performing various operations

Actually, vue2 The logic of X implementation is similar, but the implementation method is different

Then put the code

<body>
    <div id="app"></div>
    <script>
            // Simulate Vue data
            let data = {
                msg: '',
                age: '',
            }
            // Simulate an instance of Vue
            // Proxy first
            let vm = new Proxy(data, {
                // get() get value
                // target represents the object that needs proxy, which means data
                // Key is the key of the object
                get(target, key) {
                    return target[key]
                },
                // Set value
                // newValue is the set value
                set(target, key, newValue) {
                    // Also, first judge whether it saves performance as the previous value
                    if (target[key] === newValue) return
                    // Set the value
                    target[key] = newValue
                    document.querySelector('#app').textContent = target[key]
                },
            })
    </script>
</body>

Methods to trigger set and get

// set method triggered
vm.msg = 'haha'
// The get method was triggered
console.log(vm.msg)

4. Publish subscribe mode

The Vue response is applied to the publish subscribe mode. Let's learn about it first

First of all, there are three roles

Publisher, subscriber and signal center take a real example. The author (publisher) writes an article and sends it to Nuggets (signal center). Nuggets can process the article and push it to the home page, and then their bosses (subscribers) can subscribe to the article

An example in Vue is EventBus $on $emit

Let's simply imitate Vue's event bus

<body>
  <div id="app"></div>
  <script>
    class Vue {
      constructor() {
        // Used to store events
        // Stored example this subs = { 'myclick': [fn1, fn2, fn3] ,'inputchange': [fn1, fn2] }
        this.subs = {}
      }
      // Implement the $on method. Type is the type of task queue and fn is the method
      $on(type, fn) {
        // Judge whether there is a method queue of the current type in the sub
        if (!this.subs[type]) {
          // If not, add an empty array by default
          this.subs[type] = []
        }
        // Add method to this type
        this.subs[type].push(fn)
      }
      // Implement the $emit method
      $emit(type) {
        // First, we have to judge whether the method exists
        if (this.subs[type]) {
          // Get parameters
          const args = Array.prototype.slice.call(arguments, 1)
          // Loop queue call fn
          this.subs[type].forEach((fn) => fn(...args))
        }
      }
    }

    // use
    const eventHub = new Vue()
    // Use $on to add a sum type method to sub ['sum ']
    eventHub.$on('sum', function () {
      let count = [...arguments].reduce((x, y) => x + y)
      console.log(count)
    })
    // Trigger sum method
    eventHub.$emit('sum', 1, 2, 4, 5, 6, 7, 8, 9, 10)
  </script>
</body>

5. Observer mode

Differences from publish subscriptions

Different from publishing subscribers, publishers and subscribers (observers) in observers are interdependent. Observers must be required to subscribe to content change events, while publishing subscribers are scheduled by the scheduling center. Let's take a look at how the observer mode is interdependent. Here's a simple example.

<body>
  <div id="app"></div>
  <script>
    // target
    class Subject {
      constructor() {
        this.observerLists = []
      }
      // Add observer
      addObs(obs) {
        // Method for judging whether the observer has and exists updating subscription
        if (obs && obs.update) {
          // Add to observer list
          this.observerLists.push(obs)
        }
      }
      // Notify the observer
      notify() {
        this.observerLists.forEach((obs) => {
          // Each observer updates the event after receiving the notification
          obs.update()
        })
      }
      // Empty observer
      empty() {
        this.subs = []
      }
    }

    class Observer {
      // Define observer content update events
      update() {
        // The logic to handle in the update event
        console.log('Target updated')
      }
    }

    // use
    // Create target
    let sub = new Subject()
    // Create observer
    let obs1 = new Observer()
    let obs2 = new Observer()
    // Add observers to the list
    sub.addObs(obs1)
    sub.addObs(obs2)
    // When the target is turned on, each observer will trigger an update event
    sub.notify()
  </script>
</body>

6. Simulate the response principle of Vue

Here is a small and simple Vue, which mainly realizes the following functions

  • Receive initialization parameters. Here are just a few simple examples el data options
  • By private method_ proxyData registers data with Vue and transfers it to getter setter
  • Use observer to turn the attributes in data into response and add them to itself
  • Use the observer method to listen for all attribute changes of data to update the view through observer mode
  • Use the compiler to compile the difference expression between the instruction and the text node on the element node

1.vue.js

Get el data here

Pass_ proxyData registers the data attribute with Vue and converts it into a getter setter

/* vue.js */

class Vue {
  constructor(options) {
    // The object passed in is not empty by default
    this.$options = options || {}
    // Get el
    this.$el =
      typeof options.el === 'string'
        ? document.querySelector(options.el)
        : options.el
    // Get data
    this.$data = options.data || {}
    // Call_ proxyData handles properties in data
    this._proxyData(this.$data)
  }
  // Register the properties in data with Vue
  _proxyData(data) {
    Object.keys(data).forEach((key) => {
      // Data hijacking
      // Add the attribute of each data to Vue and convert it into getter setter method
      Object.defineProperty(this, key, {
        // Settings can be enumerated
        enumerable: true,
        // Settings can be configured
        configurable: true,
        // get data
        get() {
          return data[key]
        },
        // Set data
        set(newValue) {
          // Judge whether the new value is equal to the old value
          if (newValue === data[key]) return
          // Set new value
          data[key] = newValue
        },
      })
    })
  }
}

2.observer.js

Here, the attribute in data is changed into a response and added to itself. Another main function is the observer mode in Section 4 Dep.js will be used in detail

/* observer.js */

class Observer {
  constructor(data) {
    // Used to traverse data
    this.walk(data)
  }
  // Convert traversal data to response
  walk(data) {
    // Judge whether data is empty and object
    if (!data || typeof data !== 'object') return
    // Traversal data
    Object.keys(data).forEach((key) => {
      // Turn to responsive
      this.defineReactive(data, key, data[key])
    })
  }
  // Turn to responsive
  // Pay attention to and Vue JS is written differently
  // vue. In JS, the attribute is given to Vue and converted to getter setter
  // Here is to convert the property in data to getter setter
  defineReactive(obj, key, value) {
    // If it is an object type, the call to walk becomes a response. If it is not an object type, it will be return ed directly in the walk
    this.walk(value)
    // Save this
    const self = this
    Object.defineProperty(obj, key, {
      // Set enumerable
      enumerable: true,
      // Settings configurable
      configurable: true,
      // Get value
      get() {
        return value
      },
      // Set value
      set(newValue) {
        // Judge whether the old value and the new value are equal
        if (newValue === value) return
        // Set new value
        value = newValue
        // If newValue is an object, the properties in the object should also be set to responsive
        self.walk(newValue)
      },
    })
  }
}

Pay attention to the order of words introduced in html

<script src="./js/observer.js"></script>
<script src="./js/vue.js"></script>

Then in Vue Using Observer in JS

/* vue.js */

class Vue {
  constructor(options) {
    ...
    // Use the observer to convert the data in the data into a response
    new Observer(this.$data)
  }
  // Register the properties in data with Vue
  _proxyData(data) {
   ...
  }
}

See here, why did you do two repetitive operations? Repetitively convert the attribute of data into response twice

In obsever JS adds all the attributes of data to the data itself, changes the response expression into the getter setter method

In Vue JS also adds all attributes of data to Vue so that future aspect operations can be accessed directly by Vue instances or by this in Vue

Use examples

<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>


In this way, all data attributes exist in Vue and $data and are responsive

3.compiler.js

comilper.js implements the compilation of text node and element node instructions in this file, mainly for example. Of course, this written very simple instruction mainly implements v-text and V-model

/* compiler.js */

class Compiler {
  // vm refers to Vue instances
  constructor(vm) {
    // Get vm
    this.vm = vm
    // Get el
    this.el = vm.$el
    // Compile template
    this.compile(this.el)
  }
  // Compile template
  compile(el) {
    // Get the child node. If you use forEach traversal, turn the pseudo array into a true array
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) => {
      // Compile according to different node types
      // Text type node
      if (this.isTextNode(node)) {
        // Compile text node
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        //Element node
        this.compileElement(node)
      }
      // Determine whether there are child nodes and consider recursion
      if (node.childNodes && node.childNodes.length) {
        // Continue compiling templates recursively
        this.compile(node)
      }
    })
  }
  // Compile text node (simple implementation)
  compileText(node) {
    // The core idea is to find the variables in the regular expression by removing {}}
    // Then go to Vue to find this variable and assign it to node textContent
    let reg = /\{\{(.+?)\}\}/
    // Gets the text content of the node
    let val = node.textContent
    // Judge whether there is {}
    if (reg.test(val)) {
      // Get the content of group 1, that is, remove the front and back spaces
      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 on the element node for traversal
    ![...node.attributes].forEach((attr) => {
      // Get property name
      let attrName = attr.name
      // Determine whether it is an instruction starting with v -
      if (this.isDirective(attrName)) {
        // Remove v- for easy operation
        attrName = attrName.substr(2)
        // The value of the get instruction is msg in v-text = "msg"
        // msg as a key, go to Vue to find this variable
        let key = attr.value
        // Instruction operation execution instruction method
        // There are many vue instructions. In order to avoid a large number of if judgments, a uapdate method is written here
        this.update(node, key, attrName)
      }
    })
  }
  // Add instruction method and execute
  update(node, key, attrName) {
    // For example, adding textUpdater is used to handle the v-text method
    // We should call the built-in textUpdater method
    // It doesn't matter what you add a suffix, but you need to define the corresponding method
    let updateFn = this[attrName + 'Updater']
    // If this built-in method exists, it can be called
    updateFn && updateFn(node, key, this.vm[key])
  }
  // Write the corresponding specified method in advance, such as this v-text
  // It is the same as Vue's
  textUpdater(node, key, value) {
    node.textContent = value
  }
    
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
  }
    
  // Determine whether the attribute of an element is a vue instruction
  isDirective(attr) {
    return attr.startsWith('v-')
  }
  // Determine whether it is an element node
  isElementNode(node) {
    return node.nodeType === 1
  }
  // Determine whether it is a text node
  isTextNode(node) {
    return node.nodeType === 3
  }
}

4.dep.js

Write a Dep class, which is equivalent to the publisher in the observer. Each responsive attribute will create such a Dep object, which is responsible for collecting the Watcher object of the dependent attribute (an operation done when using responsive data).

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

Then call the update in Watcher to update the view (notify the observer to call the observer's update to update the view when the data changes).

Generally speaking, DEP (here refers to publisher) is responsible for collecting dependencies, adding observers (here refers to Watcher), and then notifying observers when setter data is updated.

Let's tell you which of the following stages is the implementation stage

Write the Dep class first

/* dep.js */

class Dep {
  constructor() {
    // Storage observer
    this.subs = []
  }
  // Add observer
  addSub(sub) {
    // Determine whether the observer exists and owns the update method
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // Notification method
  notify() {
    // Trigger update method for each observer
    this.subs.forEach((sub) => {
      sub.update()
    })
  }
}

At observer Using Dep in JS

Add Dep.target (observer) in get

Trigger notify in set

/* observer.js */

class Observer {
  ...
  }
  // Convert traversal data to response
  walk(data) {
   ...
  }
  // Here is to convert the property in data to getter setter
  defineReactive(obj, key, value) {
	...
    // Create Dep object
    let dep = new Dep()
    Object.defineProperty(obj, key, {
	  ...
      // Get value
      get() {
        // Add an observer object here. Dep.target represents the observer
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      // Set value
      set(newValue) {
        if (newValue === value) return
        value = newValue
        self.walk(newValue)
        // Trigger notification update view
        dep.notify()
      },
    })
  }
}

5.watcher.js

The function of watcher is to update the data after update is updated.

/* watcher.js */

class Watcher {
  constructor(vm, key, cb) {
    // vm is a Vue instance
    this.vm = vm
    // key is an attribute in data
    this.key = key
    // How to update the view with cb callback function
    this.cb = cb
    // Store the observer's in Dep.target
    Dep.target = this
    // Compare the old data when updating the view
    // Another point is that vm[key] triggers the get method at this time
    // Previously, the observer was added to dep.subs through dep.addSub(Dep.target) in get
    this.oldValue = vm[key]
    // Dep.target doesn't need to exist because the above operations have been saved
    Dep.target = null
  }
  // The necessary methods in the observer are used to update the view
  update() {
    // Get new value
    let newValue = this.vm[this.key]
    // Compare old and new values
    if (newValue === this.oldValue) return
    // Call the specific update method
    this.cb(newValue)
  }
}

So where to create Watcher? Remember in compiler Do you compile text nodes in JS

After compiling the text node, add a Watcher here

In addition, the v-text v-model instruction adds a Watcher when compiling element nodes

/* compiler.js */

class Compiler {
  // vm refers to Vue instances
  constructor(vm) {
    // Get vm
    this.vm = vm
    // Get el
    this.el = vm.$el
    // Compile template
    this.compile(this.el)
  }
  // Compile template
  compile(el) {
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) => {
      if (this.isTextNode(node)) {
        // Compile text node
        this.compileText(node)
      } 
       ...
  }
  // Compile 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 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 observer
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // Here, the bidirectional binding is implemented to listen to input events and modify the properties in data
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }
}

When we change the responsive attribute, we trigger the set() method, and then the publisher dep.notify method starts. We get all the observer watcher instances to execute the update method, call the callback function cb(newValue) method, and pass the new value to cb(). When cb method is a specific view updating method to update the view

For example, the third parameter cb method in the above example.

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

Another point is to realize the two-way binding of v-model

Not only should the view be updated by modifying the data, but also the input event should be added to the node to change the properties in the data

To achieve the effect of two-way binding

7. Test what you write

So far, both responsive and bidirectional binding have been basically implemented. Then write an example to test

<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>

OK basically realizes the responsive principle through observer mode.

8. Five document codes

Here directly paste 5 files and codes. Some places above have been omitted. The following is a complete one for everyone to read

vue.js

/* vue.js */

class Vue {
  constructor(options) {
    // The object passed in is not empty by default
    this.$options = options || {}
    // Get el
    this.$el =
      typeof options.el === 'string'
        ? document.querySelector(options.el)
        : options.el
    // Get data
    this.$data = options.data || {}
    // Call_ proxyData handles properties in data
    this._proxyData(this.$data)
    // Use the observer to convert the data in the data into a response
    new Observer(this.$data)
    // Compile template
    new Compiler(this)
  }
  // Register the properties in data with Vue
  _proxyData(data) {
    Object.keys(data).forEach((key) => {
      // Data hijacking
      // Add the attribute of each data to Vue and convert it into getter setter method
      Object.defineProperty(this, key, {
        // Settings can be enumerated
        enumerable: true,
        // Settings can be configured
        configurable: true,
        // get data
        get() {
          return data[key]
        },
        // Set data
        set(newValue) {
          // Judge whether the new value is equal to the old value
          if (newValue === data[key]) return
          // Set new value
          data[key] = newValue
        },
      })
    })
  }
}

observer.js

/* observer.js */

class Observer {
  constructor(data) {
    // Used to traverse data
    this.walk(data)
  }
  // Convert traversal data to response
  walk(data) {
    // Judge whether data is empty and object
    if (!data || typeof data !== 'object') return
    // Traversal data
    Object.keys(data).forEach((key) => {
      // Turn to responsive
      this.defineReactive(data, key, data[key])
    })
  }
  // Turn to responsive
  // Pay attention to and Vue JS is written differently
  // vue. In JS, the attribute is given to Vue and converted to getter setter
  // Here is to convert the property in data to getter setter
  defineReactive(obj, key, value) {
    // If it is an object type, the call to walk becomes a response. If it is not an object type, it will be return ed directly in the walk
    this.walk(value)
    // Save this
    const self = this
    // Create Dep object
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      // Set enumerable
      enumerable: true,
      // Settings configurable
      configurable: true,

      // Get value
      get() {
        // Add an observer object here. Dep.target represents the observer
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      // Set value
      set(newValue) {
        // Judge whether the old value and the new value are equal
        if (newValue === value) return
        // Set new value
        value = newValue
        // If newValue is an object, the properties in the object should also be set to responsive
        self.walk(newValue)
        // Trigger notification update view
        dep.notify()
      },
    })
  }
}

compiler.js

/* compiler.js */

class Compiler {
  // vm refers to Vue instances
  constructor(vm) {
    // Get vm
    this.vm = vm
    // Get el
    this.el = vm.$el
    // Compile template
    this.compile(this.el)
  }
  // Compile template
  compile(el) {
    // Get the child node. If you use forEach traversal, turn the pseudo array into a true array
    let childNodes = [...el.childNodes]
    childNodes.forEach((node) => {
      // Compile according to different node types
      // Text type node
      if (this.isTextNode(node)) {
        // Compile text node
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        //Element node
        this.compileElement(node)
      }
      // Determine whether there are child nodes and consider recursion
      if (node.childNodes && node.childNodes.length) {
        // Continue compiling templates recursively
        this.compile(node)
      }
    })
  }
  // Compile text node (simple implementation)
  compileText(node) {
    // The core idea is to find the variables in the regular expression by removing {}}
    // Then go to Vue to find this variable and assign it to node textContent
    let reg = /\{\{(.+?)\}\}/
    // Gets the text content of the node
    let val = node.textContent
    // Judge whether there is {}
    if (reg.test(val)) {
      // Get the content of group 1, that is, remove the front and back spaces
      let key = RegExp.$1.trim()
      // Replace and assign to node
      node.textContent = val.replace(reg, this.vm[key])
      // Create observer
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // Compile element nodes. Only instructions are processed here
  compileElement(node) {
    // Get all the attributes on the element node for traversal
    ![...node.attributes].forEach((attr) => {
      // Get property name
      let attrName = attr.name
      // Determine whether it is an instruction starting with v -
      if (this.isDirective(attrName)) {
        // Remove v- for easy operation
        attrName = attrName.substr(2)
        // The value of the get instruction is msg in v-text = "msg"
        // msg as a key, go to Vue to find this variable
        let key = attr.value
        // Instruction operation execution instruction method
        // There are many vue instructions. In order to avoid a large number of if judgments, a uapdate method is written here
        this.update(node, key, attrName)
      }
    })
  }
  // Add instruction method and execute
  update(node, key, attrName) {
    // For example, adding textUpdater is used to handle the v-text method
    // We should call the built-in textUpdater method
    // It doesn't matter what you add a suffix, but you need to define the corresponding method
    let updateFn = this[attrName + 'Updater']
    // If this built-in method exists, it can be called
    updateFn && updateFn.call(this, node, key, this.vm[key])
  }
  // Write the corresponding specified method in advance, such as this v-text
  // It is the same as Vue's
  textUpdater(node, key, value) {
    node.textContent = value
    // Create observer
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater(node, key, value) {
    node.value = value
    // Create observer
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // Two way binding is implemented here
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

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

dep.js

/* dep.js */

class Dep {
  constructor() {
    // Storage observer
    this.subs = []
  }
  // Add observer
  addSub(sub) {
    // Determine whether the observer exists and owns the update method
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // Notification method
  notify() {
    // Trigger update method for each observer
    this.subs.forEach((sub) => {
      sub.update()
    })
  }
}

watcher.js

/* watcher.js */

class Watcher {
  constructor(vm, key, cb) {
    // vm is a Vue instance
    this.vm = vm
    // key is an attribute in data
    this.key = key
    // How to update the view with cb callback function
    this.cb = cb
    // Store the observer's in Dep.target
    Dep.target = this
    // Compare the old data when updating the view
    // Another point is that vm[key] triggers the get method at this time
    // Previously, the observer was added to dep.subs through dep.addSub(Dep.target) in get
    this.oldValue = vm[key]
    // Dep.target doesn't need to exist because the above operations have been saved
    Dep.target = null
  }
  // The necessary methods in the observer are used to update the view
  update() {
    // Get new value
    let newValue = this.vm[this.key]
    // Compare old and new values
    if (newValue === this.oldValue) return
    // Call the specific update method
    this.cb(newValue)
  }
}

QQ:505417246
Wechat: 18331092918
WeChat official account: Code program life
Personal blog: http://rayblog.ltd

Topics: Javascript Front-end Vue Interview source code