preface
This is the third article in a 10-part series on Vue, in which we look at one of Vue’s core functions, the responsive principle.
How to understand reactive
When a state changes, the transactions associated with that state also change. From the front end, when the data state changes, the associated DOM also changes. Data models are just plain old JavaScript objects. And when you modify them, the view is updated.
Throw a question
Let’s start by looking at the way we usually write in Vue:
<div id="app" @click="changeNum">
{{ num }}
</div>
var app = new Vue({
el: '#app',
data: {
num: 1
},
methods: {
changeNum() {
this.num = 2
}
}
})
Copy the code
This is quite common, but you’ve thought about why the back view is updated when this.num = 2 is called. Through this article I try to make this point clear.
If we don’t use Vue, how do we implement it?
My first thought was to do something like this:
let data = {
num: 1
};
Object.defineProperty(data, 'num', {set: function( newVal ){
document.getElementById('app').value = newVal; }}); input.addEventListener('input'.function(){
data.num = 2;
});
Copy the code
This allows you to roughly click on the element and automatically update the view.
Here we need to manipulate the accessor property of the Object via Object.defineProperty. When listening for data changes, manipulate the relevant DOM.
A common pattern used here is the publish/subscribe model.
I’ve drawn a rough flowchart to illustrate the observer and publish/subscribe patterns. As follows:
A careful student will notice that the difference between my sketchy process and using Vue is that I need to manipulate DOM re-rendering myself.
If we use Vue, this step is handled by the code inside Vue. This is why we don’t need to manually manipulate the DOM when using Vue.
I mentioned object.defineProperty in the last article and won’t repeat it here.
How is Vue responsive
We know that objects can manipulate their accessor properties through Object.defineProperty, that is, objects have getter and setter methods. This is the cornerstone of a responsive approach.
Let’s start with an intuitive flow chart:
InitData method
When a Vue is initialized, its _init() method calls the initState(VM) method. The initState method initializes properties such as props, methods, data, computed, and wathcer.
Here we do a more detailed analysis of the process of data initialization.
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if(! isPlainObject(data)) { ...... } // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while(i--) { const key = keys[i] ...... // Omit some compatible code, but does not affect understandingif (props && hasOwn(props, key)) {
......
} else if(! isReserved(key)) { proxy(vm, `_data`, key) } } // observe data observe(data,true /* asRootData */)
}
Copy the code
InitData The main process of initializing data also does two things:
- through
proxy
Take each of these valuesvm._data.[key]
The agent tovm.[key]
On; - call
observe
Method to observe the change of the whole data, data also into response (observable), can be passedvm._data.[key]
Access the corresponding property in the definition data return function.
Data hijacking —Observe
Use this method to make all properties under data reactive (observable).
// Add getters and setters to the properties of the object for dependency collection and publication of updatesexportclass Observer { value: any; dep: Dep; vmCount: number; constructor (value: Any) {this.value = value // instantiate the Dep object this.dep = new Dep() this.vmCount = 0 // add its own instance to the __ob__ attribute of the data object value def(value,'__ob__', this) // value is a different call to an arrayif (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} elseConst keys = object.keys (obj) {const keys = object.keys (obj)for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Copy the code
The def function encapsulates object.defineProperty, so if you console.log(data), you’ll notice an extra __ob__ attribute.
The defineReactive method iterates through all attributes
// Define a concrete implementation of a responsive objectexport functiondefineReactive ( obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean ) { const dep = new Dep() ..... // Omit some compatible code, but does not affect understandingletchildOb = ! shallow && observe(val) Object.defineProperty(obj, key, { enumerable:true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if(dep.target) {// Do dependency collection dep.depend()if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: functionreactiveSetter (newVal) { const value = getter ? getter.call(obj) : val ..... // Omit some compatible code, but does not affect understandingif (setter) {
setter.call(obj, newVal)
} else{val = newVal} // listen for new values childOb =! Shallow && observe(newVal) // Notify all subscribers by internally calling watcher's update method dep.notify()}})}Copy the code
The defineReactive method first initializes the instance of the Dep object, and then recursively calls the Observe method on the child object to make all child attributes responsive. And call deP related methods in getter and setter methods of Object.defineProperty.
That is:
getter
All the method does is rely on collectiondep.depend()
setter
All the method does is publish updatesdep.notify()
We find that there is a significant relationship between these Dep objects. Now let’s look at the Dep object. The Dep
Dep for the dispatch center role
Earlier we mentioned the publish/subscribe model, where there is a dispatch center in front of publishers and subscribers. Here, Dep plays the role of dispatching center, and its main functions are as follows:
- Collect subscriber Watcher and add to observer list subs
- Receive events from the publisher
- Notify the subscriber of the target update and let the subscriber perform its own update method
The detailed code is as follows:
// Dep constructorexportdefault class Dep { static target: ? Watcher; id: number; subs: Array<Watcher>;constructor() {this.id = uID + this.subs = []} // Add Watcher addSub (sub: Watcher) {this.subs.push(sub)} // Remove Watcher removeSub (sub: Watcher) {remove(this.subs, sub)} // Do dependency collectiondepend () {
if(dep.target) {dep.target.adddep (this)}} // Notify all subscribers that watcher's update method is called internallynotify () {
const subs = this.subs.slice()
for (leti = 0, l = subs.length; i < l; I ++) {subs[I].update()}}} // Dep.target is the globally unique observer because only one observer is being processed at any time. Dep.target = null // Observer queue to be processed const targetStack = []export functionpushTarget (_target: ? Watcher) {if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
Copy the code
Dep can be understood as a management of Watcher. Dep and Watcher are closely related. So we have to take a look at Watcher’s implementation.
Subscriber — Watcher
There are a number of prototype methods defined in Watcher, so I’ll just skim over the update and GET methods here.
// Some compatibility code has been omitted for ease of understandinggetPushTarget (this) const vm = this.vmletValue = this.getter.call(vm, vm) // deeptrueProcessing logic ofif(this.deep) {traverse(value)} popTarget() {this.deep) {traverse(value)} popTarget() {this.deep) {traverse(value)}return value
}
update () {
if (this.computed) {
...
} else if(this.sync) {// mark to synchronize this.run()}elseNextTick queueWatcher(this)}}Copy the code
That’s about it for Vue’s reactive process. Interested can look at the source code.
Finally, we review it through this flow chart:
Vue related article output plan
Recently, some friends always ask me questions about Vue, so I will output 10 articles about Vue, hoping to be of some help to you. I will keep it updated every 7 to 10 days.
- Vuex injected into the Vue lifecycle (done)
- 【 Front-end dictionary 】 Learning Vue source code necessary knowledge reserves (done)
- 【 Front-end Dictionary 】 Brief analysis of Vue responsive principle (completed)
- [Front-end dictionary] Patch between old and new VNodes
- How to develop functional components and upload NPM
- Optimize your Vue project in these areas
- 【 Front-end Dictionary 】 From vue-router design to front-end routing development
- How to use Webpack correctly in a project
- Vue server render
- How to choose between Axios and Fetch
I suggest you pay attention to my public number, the first time you can receive the latest article.
If you want to join a group, you can also add a smart robot that automatically pulls you into the group: