Update: Thank you for your support. Recently, a blog official website came out to facilitate your system reading. There will be more content and more optimization in the future
—— The following is the text ——
Vue advanced series summary below, welcome to read.
Vue advanced series (1) response formula principle and implementation
Vue advanced series (2) plug-in principle and implementation
Vue advanced series (3) Render function principle and implementation
What is reactive Reactivity
Reactivity represents how a state change dynamically changes the entire system, in a real project application scenario, that is, how data dynamically changes the Dom.
demand
Now I have a requirement, I have two variables a and B, and I want B to always be 10 times as much as A. How do I do that?
Easy Try 1:
let a = 3;
let b = a * 10;
console.log(b); / / 30
Copy the code
At first glance, it looks like it does, but b is fixed, and no matter how you change a, B doesn’t change with it. In other words, B does not keep data synchronization with A. B changes only if you redefine the value of B after a changes.
a = 4;
console.log(a); / / 4
console.log(b); / / 30
b = a * 10;
console.log(b); / / 40
Copy the code
Easy Try 2:
Define the relationship between a and b in a function, then execute the function after changing a, and the value of B will change. The pseudocode is shown below.
onAChanged((a)= > {
b = a * 10;
})
Copy the code
So now the question becomes how to implement the onAChanged function, which automatically executes onAChanged when a changes, see the follow-up.
Combined with the view layer
Now combine a and B with the View page, where A corresponds to the data and B corresponds to the page. The business scenario is simple: change data A and then change page B.
<span class="cell b"></span>
document
.querySelector('.cell.b')
.textContent = state.a * 10
Copy the code
Now establish the relationship between data A and page B, wrapped in a function to create the following relationship.
<span class="cell b"></span>
onStateChanged((a)= > {
document.QuerySelector ('.cell.b ').textContent = state.a *10
})
Copy the code
Again abstracted, it looks like this.
<span class="cell b">
{{ state.a * 10 }}
</span>
onStateChanged(() => {
view = render(state)
})
Copy the code
View = render(state) is a high-level abstraction for all page rendering. The implementation of View = render(state) is not considered here, as it involves a number of technical details such as DOM structure and implementation. What we need here is an implementation of onStateChanged.
implementation
This is done through getter and setter methods in Object.defineProperty. Please refer to the following link for specific usage.
The Object of MDN. DefineProperty
Note that the get and set functions are access descriptors, and the value and writable functions are data descriptors. The descriptor must be one of these two forms, but they cannot coexist, or an exception will occur.
Example 1: Implementationconvert()
function
Requirements are as follows:
- 1. Pass objects
obj
As a parameter - 2, use,
Object.defineProperty
Convert all properties of an object - 3. The converted object retains its original behavior but outputs logs in get or SET operations
Example:
const obj = { foo: 123 }
convert(obj)
obj.foo // Get key "foo": 123
obj.foo = 234 Setting key "foo" to 234
obj.foo // Output getting key "foo": 234
Copy the code
Once you know how to use getters and setters in Object.defineProperty, you can implement onAChanged and onStateChanged by modifying the GET and set functions.
Implementation:
function convert (obj) {
// Iterate over all attributes of the object
// And convert to getter/setters with object.defineProperty ()
Object.keys(obj).forEach(key= > {
// Save the original value
let internalValue = obj[key]
Object.defineProperty(obj, key, {
get () {
console.log(`getting key "${key}": ${internalValue}`)
return internalValue
},
set (newValue) {
console.log(`setting key "${key}" to: ${newValue}`)
internalValue = newValue
}
})
})
}
Copy the code
Example 2: ImplementationDep
class
Requirements are as follows:
- 1. Create one
Dep
Class containing two methods:depend
andnotify
- 2. Create one
autorun
Function, pass in aupdate
Function as argument - 3, in
update
Call from a functiondep.depend()
, explicitly depends onDep
The instance - 4, call
dep.notify()
The triggerupdate
Function rerun
Example:
const dep = new Dep()
autorun((a)= > {
dep.depend()
console.log('updated')})// To register subscribers, print updated
dep.notify()
// Notifies the change
Copy the code
You first need to define the autorun function that takes the update function as an argument. Because the subscriber is registered in the Dep when autorun is called and the update function is re-executed when dep.notify() is called, the UPDATE reference must be held in the Dep, where the variable activeUpdate is used to represent the function that wraps the update.
The implementation code is as follows.
let activeUpdate = null
function autorun (update) {
const wrappedUpdate = (a)= > {
activeUpdate = wrappedUpdate // Reference assignment to activeUpdate
update() // Call update, i.e. call internal DEP.depend
activeUpdate = null // Clear references after successful binding
}
wrappedUpdate() / / call
}
Copy the code
WrappedUpdate is essentially a closure. The activeUpdate variable can be retrieved from the update function, and the activeUpdate variable can be retrieved from the dep.depend() function, so the implementation of deP is simple.
The implementation code is as follows.
class Dep {
/ / initialization
constructor () {
this.subscribers = new Set()}// Subscribe to the list of update functions
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
// Rerun all update functions
notify () {
this.subscribers.forEach(sub= > sub())
}
}
Copy the code
Combining the above two parts is the complete implementation.
Example 3: Implementing responsive systems
Requirements are as follows:
- 1. Combining the above two examples,
convert()
Rename it observerobserve()
- 2,
observe()
Transform the attributes of the object to make them responsive. For each converted attribute, it is assigned oneDep
Instance, which tracks subscriptionsupdate
Function list and in callsetter
Triggers them to run again - 3,
autorun()
receiveupdate
Function as arguments, and inupdate
The function is re-run when the properties to which it subscribed change.
Example:
const state = {
count: 0
}
observe(state)
autorun((a)= > {
console.log(state.count)
})
// Print count is: 0
state.count++
// Print count is: 1
Copy the code
This can be achieved by combining instance 1 and instance 2, with the obj property modified in Observe and the INSTANCE of Dep allocated, subscribers registered in GET, and changes notified in SET. The Autorun function is saved unchanged. The implementation is as follows:
class Dep {
/ / initialization
constructor () {
this.subscribers = new Set()}// Subscribe to the list of update functions
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
// Rerun all update functions
notify () {
this.subscribers.forEach(sub= > sub())
}
}
function observe (obj) {
// Iterate over all attributes of the object
// And convert to getter/setters with object.defineProperty ()
Object.keys(obj).forEach(key= > {
let internalValue = obj[key]
// Each attribute is assigned a Dep instance
const dep = new Dep()
Object.defineProperty(obj, key, {
// Getter is responsible for registering subscribers
get () {
dep.depend()
return internalValue
},
// Setters are responsible for notifying changes
set (newVal) {
constchanged = internalValue ! == newVal internalValue = newVal// Recalculate after triggering
if (changed) {
dep.notify()
}
}
})
})
return obj
}
let activeUpdate = null
function autorun (update) {
// Wrap the update function in the "wrappedUpdate" function,
// The "wrappedUpdate" function registers and unregisters itself
const wrappedUpdate = (a)= > {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
Copy the code
This is even clearer when combined with the flow chart in the Vue documentation.
Job Done!!
This article is based on a paid video by VUE author Iu
communication
My Github link is as follows. Welcome to Star
Github.com/yygmind/blo…
My name is Mu Yiyang, a senior front-end engineer of netease. I will focus on one front-end interview every week. Next, let me take you into the world of advanced front-end, on the way to progress, mutual encouragement!