“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

preface

Autumn attracted attack, the author is studying Vue source code recently, through the interpretation of the source code, combined with the interview questions often tested, in-depth understanding of Vue source principle, and begin to achieve some methods of Vue. This series of articles is the summary and output of this learning plan, and will be updated continuously…

Learning goals

  • Figure out how watch works
  • Implement a diy Watcher

Knowledge reserves

Before understanding the implementation principle, if you are a beginner, you can first understand the responsive principle:

Pass a normal JavaScript Object to the Vue instance as the data option. Vue will walk through all the properties of the Object and use object.defineProperty to turn them into getters/setters. Object.defineproperty is a non-shim feature in ES5, which is why Vue does not support IE8 and earlier browsers.

These getters/setters are invisible to the user, but internally they allow Vue to track dependencies and notify changes when the property is accessed and modified.

One for each component instancewatcherInstance, which records the “touched” properties as dependencies during component rendering. The watcher is then notified when the setter for the dependency fires, causing its associated component to be rerendered.After a preliminary familiarity with the principles of responsiveness, let’s begin the implementation of Watcher

Watcher open made

Step1: let’s start with the code and see what we want to achieve — requirements analysis

Before we start, let’s take a look at what we want to achieve:

let vm = new Watcher({
    data: {
      a: 0.b: 'hello'
    },
    watch: {
      a(newValue, oldValue) {
        console.log(newValue, oldValue); }}})Copy the code

As shown in the above code, in this wrapper, we want to create an instance of Watcher, modify the properties in the Data object of Watcher, the watch in Watcher will detect the changes, and the function of the corresponding property name will trigger execution. This is the simplest understanding of the responsive principle.

Step2: nonsense do not say, direct hands

Once you know what you need, get right to it:

  • 1. Let’s set up the shelf and create a Watcher class
  • 2. For example, if the code in requirement analysis wants to detect data changes, it must have its own data source and object to put method declaration, so it needs to put a data object and a Watch object in the constructor
  • 3. According to the response principle mentioned above, we need to iterate over the passed data Object, use the object.defineProperty method to data hijack all the properties of the Object, and turn all the properties into getters/setters. And the realization of tracking.
Class Watcher{constructor(opts){// constructor = this.getBaseType(); this.$data = this.getBaseType(opts.data) === 'Object' ? opts.data : {} this.$watch = this.getBaseType(opts.watch) === 'Object' ? Opts.watch: {} for(let key in opts.data){// in.. This.setdata (key)}}} this.setdata (key)}}Copy the code

Let’s create a getBaseType method and a setData method:

  • The getBaseType method is used for type determination
getBaseType(target){callObjectThe toString method on the prototype can be used to determine the type, and then call willthisPoint to target to determine the typeconst typeStr = Object.prototype.toString.call(target)
         return typeStr.slice(8, -1)}Copy the code

Note: about the Object. The prototype. ToString. Call (obj) method to judge data types, in order to avoid the logical confusion, here only briefly, if don’t understand we can see this article: blog.csdn.net/hanyanshuo/…

  • SetData method
setData(_key){
    // Use object.defineProperty to bind each item of a data Object, tracking changes through getters/setters
    Object.defineProperty(this,_key,{
        get:function(){
            return this.$data[_key]
        },
        set:function(val){
            const oldVal = this.$data[_key]
            if(oldVal === val) return val
            this.$data[_key] = val
            this.$watch[_key] && this.getBaseType(this.$watch[_key]) === 'Function' && (
            this.$watch[_key].call(this , val , oldVal)
            )
        }
    })
}
Copy the code

The logic in this section is simply the syntax sugar for object.defineProperty, and the core of the implementation depends on this method. (Further understanding of this method is recommended.)

Object.defineproperty takes three arguments:

1. The object whose properties are to be defined 2. The property to be defined or modified 3Copy the code

In the above code, the first argument to an object.defineProperty should, by definition, receive this.$data, whereas here it receives this. Who does this point to?

This refers to this.$data because the first argument to the method refers to the current object in context. In the code above, this refers to Watcher, and the current object is this.$data. So this is equivalent to binding the this.$data object.

When the setData method is called for each property in the data, any property that is read will be read through the get callback and the value of the corresponding property in the data will be read. If any property is modified, the set callback is triggered. The action in the set callback is as follows:

  1. First save the oldValue in the data object
  2. Optimization: If the newValue passed to the callback is equal to oldValue, nothing is done
  3. Save the newValue passed into the callback
  4. To determine: if watch has a property of the same name as the property to be changed -> If the property of the same name is a method -> pass oldValue, newValue into the method and call (here a call is used to point the method to watch).

The complete code

class Watcher {
    constructor(opts) {
      this.$data = this.getBaseType(opts.data) === 'Object' ? opts.data : {}
      this.$watch = this.getBaseType(opts.watch) === 'Object' ? opts.watch : {}
      for(let key in opts.data) { // Retrieve each key in the data object
         // Object.keys(opts.data).forEach()
          this.setData(key)
      }
    }
    getBaseType(target) {
      const typeStr = Object.prototype.toString.call(target) // "[Object string]"
      return typeStr.slice(8, -1)}setData(_key) {
      // this.$data = this
      Object.defineProperty(this, _key, { // Object.defineProperty(this) points context to the current Object
        get: function() {
          return this.$data[_key]
        },
        set: function(val) {
          const oldVal = this.$data[_key]
          if (oldVal === val) return val
          this.$data[_key] = val
          this.$watch[_key] && this.getBaseType(this.$watch[_key]) === 'Function' && (
          this.$watch[_key].call(this, val, oldVal)
          )
        }
      }) 
    }
  }
  
Copy the code

conclusion

In this article, the implementation of Watcher is mainly the following two steps:

  1. Initialize a Data object and a Watch object. GetBaseType is used to type the arguments passed in
  2. Through the for… In calls setData for each attribute key of data (i.e., data binding via object.defineProperty)

Difficult points: Operations in object.defineProperty may not be friendly to beginners. Here’s a summary:

Data is bound

  • Read property values: Returns property values in a Data object through the GET callback
  • To change the value of a property, the set callback is triggered. The logic in the set is summarized as follows:
  1. If newValue and oldVaue are exactly equal, nothing is done
  2. Save oldValue, newValue
  3. If there is a method in the watch object with the same name as the property name, pass the two arguments of the second step -> point this to the Watcher class -> call the method

If you’re not familiar with some of the methods in this article, warm links are as follows:

  • Object. The prototype. ToString. Call (obj) type judgment: blog.csdn.net/hanyanshuo/…
  • Object.defineproperty data hijacking: developer.mozilla.org/zh-CN/docs/…
  • Deep Understanding of VUE Responsive Principles: Deep Responsive Principles — vue.js (vuejs.org)

conclusion

Out of the original intention of taking the article as the output of daily learning and technology sharing, I try to output what I have learned in daily life in a short length and share it with my friends who are also about to face the interrogation of the interviewer. This article is only the beginning of Vue source series, the follow-up will continue to update the series, will also output some must ask the summary of the interview questions, welcome to continue to pay attention to, and the author common progress!!

This is the end of this article, I hope I have shared some of the confusion to help you, if there are unclear or the article is wrong you are welcome to discuss and point out in the comments section…