At present, about 40% of the demand is in the Vue2 technology stack, the so-called know what is more to know why, in order to better use Vue, faster troubleshooting problems, recently learned some knowledge related to the source code, although the online summary of Vue a lot of a lot of their own, but not much of their own one, Welcome to discuss learning together, find problems welcome to point out.
What does a responsive system do
Back to the simplest code:
data = {
text: 'hello, world'
}
const updateComponent = () = > {
console.log('received', data.text);
}
updateComponent()
data.text = 'hello, liang'
// Run the result
// Receive hello, world
Copy the code
What a reactive system does: a function that depends on data is re-executed when the dependent data changes.
We expect the updateComponent function to be executed again when data.text is modified above.
To implement a responsive system, we need to do two things:
-
Know which functions depend on the data in data
-
Call functions that depend on data when the data in data changes
To implement point 1, we need to save the current function when the function is executed, and then save the function to the current data when the data is read.
The second point is to execute the saved function once when modifying the data.
To do something extra when reading and modifying data, we can override the get and set functions of Object attributes via Object.defineProperty().
Responsive data
Let’s write a function that overrides the get and set functions for attributes.
/** * Define a reactive property on an Object. */
export function defineReactive(obj, key, val) {
const property = Object.getOwnPropertyDescriptor(obj, key);
// Read user - defined get and set
const getter = property && property.get;
const setter = property && property.set;
// Val does not pass in for manual assignment
if((! getter || setter) &&arguments.length === 2) {
val = obj[key];
}
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// 1. The current function needs to be saved
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// 2. The current data-dependent function will be executed
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /}}); }Copy the code
To make it easier to call, we wrap the operations of steps 1 and 2 into a Dep class.
export default class Dep {
static target; // The function currently being executed
subs; // The dependent function
constructor() {
this.subs = []; // Save all functions that need to be executed
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
// Go here when trigger get
if (Dep.target) {
// Delegate to dep. target to call addSub
Dep.target.addDep(this); }}notify() {
for (let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update();
}
}
}
Dep.target = null; // Static variable, globally unique
Copy the code
We save the currently executing function to the target variable of the Dep class.
Save the currently executing function
To save the current function, we also need to write a Watcher class, pass in the function that needs to be executed, save it in the getter property of the Watcher class, and hand it over to the Watcher class to execute.
So in the Dep class, instead of holding the current function in subs, the Watcher object holds the current function.
import Dep from "./dep";
export default class Watcher {
constructor(Fn) {
this.getter = Fn;
this.get();
}
/** * Evaluate the getter, and re-collect dependencies. */
get() {
Dep.target = this; // Save the Watcher that wraps the currently executing function
let value;
try {
// Invoke the currently passed function to trigger the get of the object property
value = this.getter.call();
} catch (e) {
throw e;
}
return value;
}
/** * Add a dependency to this directive. */
addDep(dep) {
// Get will go here and collect the current dependency
// The Watcher of the currently executing function is saved to subs in the DEP
dep.addSub(this);
}
/** * Subscriber interface. * Will be called when a dependency changes. */
// Set is triggered when the object property value is modified
update() {
this.run();
}
/** * Scheduler job interface. * Will be called by the scheduler. */
run() {
this.get(); }}Copy the code
The function is wrapped by Watcher and stored in dep. target. Then the function passed in is called.
If the value of an object property is changed in the future, the set of the object property is triggered, and the previously collected Watcher object is called, through the Uptate method of the Watcher object, to call the function that was originally executed.
Responsive data
Going back to the defineReactive function we left behind, let’s complete it in the same way we did above.
import Dep from "./dep";
/** * Define a reactive property on an Object. */
export function defineReactive(obj, key, val) {
const property = Object.getOwnPropertyDescriptor(obj, key);
// Read user - defined get and set
const getter = property && property.get;
const setter = property && property.set;
// Val does not pass in for manual assignment
if((! getter || setter) &&arguments.length === 2) {
val = obj[key];
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
const dep = new Dep(); // Hold a Dep object that holds all Watcher dependencies on this variable
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// 1. The current function needs to be saved
if (Dep.target) {
dep.depend();
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
return value;
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// 2. The current data-dependent function will be executed
dep.notify();
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /}}); }Copy the code
The Observer object
Let’s write an Observer method that makes all properties of the object responsive.
export class Observer {
constructor(value) {
this.walk(value);
}
/** * iterates through all the attributes of the object, calling defineReactive to intercept the get and set methods of the attributes of the object */
walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); }}}Copy the code
We provide an observe method to create an Observer.
export function observe(value) {
let ob = new Observer(value);
return ob;
}
Copy the code
test
Apply the above method to the example at the beginning of this article and do it:
import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
text: "hello, world"};// Make the data responsive
observe(data);
const updateComponent = () = > {
console.log("Receive", data.text);
};
// The current function is executed by Watcher
new Watcher(updateComponent);
data.text = "hello, liang";
Copy the code
At this point, the output will be twice ~
Received hello, world received hello, liangCopy the code
It means our responsive system worked.
The total
Firstly, the whole process of responsive system is understood as follows:
Each property has a subs array, and the Watcher will hold the currently executing function. When the property is read, get will be triggered, and the current Watcher will be saved in the Subs array. When the property value is modified, the saved function will be executed through the Watcher object in the subs array.
Of course, there are still billions of details to be worked out, which will continue in the next article.