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
-
The first time the code is executed, the value of the target object should be made responsive (data hijacking)
-
We want to implement Autorun so that the target function can be executed at the appropriate time (dependent collection)
-
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 here
activeWatcher
Is used toDep instance
It’s for preservation. We’re modifying itdepend
methods
/ / 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?