Hello, here is Link. In the first half of the year, I spent most of my time looking at the source code of Vue. I always think I have a good understanding of responsive code. But some time ago, I saw an interview question, and I had to write a responsive system by hand. We looked it up and decided to sort it out. We wrote a mini responsive system, hand in hand, based on a tutorial posted by Utah. This is how you write when you interview, and if someone tells you that your writing is unprofessional, you say it was taught by Yu Yuxi.😋, see if it is the interviewer major or Yu Yuxi major.

If you know something about responsive forms, and you want to see a course at Utah, you can just look at this mini responsive form. But I’m going to go over some of the things that I don’t think Utah has covered in detail in addition to the original code. If you don’t know much about responsiveness, I recommend you finish my article

What is reactive?

In simple terms, a change in one of our data sets can cause other data sets to change as well. It is intended to save developers from having to manually change other data

let a = 1       / / 1
let b = a * 10  / / 10
Copy the code

Change a

  • Under normal circumstances
a = 2       / / 2
b = a * 10  / / 20 😿
Copy the code
  • responsive
a = 2       / / 2
b = 20      // 20 b will change to the new value according to the value of A, without having to do it ourselves
Copy the code

Close to the Vue

Let’s look at the vue example.

If you’re a vue2 user it’s probably still weird. Is this close to Vue? Intuitively, it is closer to the way VUE3 is used. But this code also explains vue2’s responsive system. As for why VUe2 2/3 is so similar, it is because the principle and idea are the same.

const states = {
    count: 0
}
autorun(() = > {
    console.log("count:" + states.count)
})
// log 0
states.count++
// log 1
Copy the code

All this code does is automatically execute the Autorun function once whenever count changes. This is exactly what vue’s response does. Autorun passes in a function that can evaluate a property, listen on a property, or be a component. Because each of these instances is essentially a function. You can understand it as:

  • If the value changes, update the page

  • Or change the calculated properties

  • Or trigger the listener property


Ok, we now take out the hidden functions in the above code to analyze one by one, and what specific things need to be done to make the above code achieve

  1. The first time the code is executed, the value of the target object should be made responsive (data hijacking)

  2. We want to implement Autorun so that the target function can be executed at the appropriate time (dependent collection)

  3. When we change count, we need to trigger the target function (sending updates)

We are divided into modules to achieve the above functions

The data was hijacked

Do it with Object.defineProperty(). This is a bad story on the web, so I won’t mention it. See the implementation

Step by step, in this case we only consider Object, which is a flat object. If you know something about handling edge situations (arrays, multi-layer objects, etc.), we recommend checking out VUE’s source code for VUE-core-analyse

function isObject(obj) {
    return (
        typeof obj === 'object'&&!Array.isArray(obj) && obj ! = =null&& obj ! = =undefined)}function observe(obj) {
    if(! isObject(obj))return
    defineReactivity(obj)
}
Copy the code

That’s all we need to do to validate the parameters, we will now batch the values inside the object to be reactive via defineReactivity, where activeValue is defined to be available via get after assignment

function defineReactivity(obj) {
    Object.keys(obj).forEach((key) = > {
        let activeValue = obj[key]
        Object.defineProperty(obj, key, {
            get() {
                return activeValue
            },
            set(newValue) {
                activeValue = newValue
            }
        })
    })
}
Copy the code

The publisher

Data hijacking is complete, and now we can control how the data is accessed and set to new values. Now when we need to consider the process of data change, how can we find the corresponding function to execute? Easy. Just find a place to store it. Normally this instance is called a publisher.

Why do you need to make an instance? Because it not only stores the function, but also notifies the function to execute

class Dep {
    constructor() {
        this.subscriber = new Set()}/ / collection
    depend() {
        this.subscriber.add(xxxx)
    }
    // Notification update
    notify() {
        this.subscriber.forEach((sub) = > sub())
    }
}
Copy the code

Let’s not think about how to collect, but when to collect, when to update? Simply collect data when it is accessed and notify updates when it is changed. Let’s revamp the defineReactivity function

function defineReactivity(obj) {
    Object.keys(obj).forEach((key) = > {
        let activeValue = obj[key]
+       const dep = new Dep()
        Object.defineProperty(obj, key, {
            get() {
                // Collect data when it is accessed
+               dep.depend()
                return activeValue
            },
            set(newValue) {
                // Update when data is changed
                activeValue = newValue
+               dep.notify()
            }
        })
    })
}
Copy the code

The subscriber

In Dep, we haven’t done the depend method yet, so we’ll now focus on the Autorun function, whose parameters are the objects we need to send updates to.

In the actual vue code, watcher is actually an instance of a class, but we’ll simplify the code and replace it with a function to make it easier to understand, because in fact, watcher instances also execute a getter function by default, which can be thought of as the target function we pass in.

let activeWatcher = null
function autorun(update) {
    function watcher() {
        activeWatcher = watcher
        update()
        activeWatcher = null
    }
    watcher()
}
Copy the code
  • I’m going to save a global copy hereactiveWatcherIs used toDep instanceIt’s for preservation. We’re modifying itdependmethods
/ / collection
depend(){+if (activeWatcher) {
+       this.subscriber.add(activeWatcher)
+   }
}
Copy the code

By now I’m sure you can understand that saving a global watcher is easy for DEP to collect, but why do you need to save an additional activeWatcher when watcher is executed instead of saving the Watcher function directly? Like a little pants farting 🤔.

In fact, as a mini-response, there’s really no need to do that. But if we look at it as a true responsive system for Vue, you can see why. Since there is definitely more than one watcher instance, we need to ensure that we collect the currently active Watcher when we rely on it for data collection.

All the code

// Publisher module
class Dep {
  constructor() {
      this.subscriber = new Set()}/ / collection
  depend() {
      if (activeWatcher) {
          this.subscriber.add(activeWatcher)
      }
  }
  // Notification update
  notify() {
      this.subscriber.forEach((sub) = > sub())
  }
}
// Data hijacking module
function isObject(obj) {
  return (
      typeof obj === 'object'&&!Array.isArray(obj) && obj ! = =null&& obj ! = =undefined)}function observe(obj) {
  if(! isObject(obj))return
  defineReactivity(obj)
}

function defineReactivity(obj) {
  Object.keys(obj).forEach((key) = > {
      let activeValue = obj[key]
      const dep = new Dep()
      Object.defineProperty(obj, key, {
          get() {
              // Collect data when it is accessed
              dep.depend()
              return activeValue
          },
          set(newValue) {
              // Update when data is changed
              activeValue = newValue
              dep.notify()
          }
      })
  })
}
// Subscriber module
let activeWatcher = null
function autorun(update) {
  function watcher() {
      activeWatcher = watcher
      update()
      activeWatcher = null
  }
  watcher()
}


const states = {
  count: 0
}

observe(states)

autorun(() = > {
  console.log("count:" + states.count)
})

// log 0
states.count++
// log 1

Copy the code

Thank 😘


If you find the content helpful:

  • ❤️ welcome to focus on praise oh! I will do my best to produce high-quality articles

Contact author: Linkcyd 😁 Previous:

  • Webpack2 + Ve2 old project migration vite successfully! It smells good…
  • React Get started with 6 brain maps
  • Interviewer: Come on, hand write a promise
  • Interviewer, I totally understand the difference between computed and Watch
  • Vue Principle: How does a Vue instantiate a component?