In the previous two articles, we covered the work of vUE initialization and the overall process of mounting. Below, based on the previous basis, we focus on the vUE responsive principle implementation.
Let’s start with a simple 🌰 :
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<div id='root' @click="handleClick">
{{ a }}
</div>
<script src=".. /vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#root'.data() {
return {
a: "This is the root node."}},methods: {
handleClick() {
this.a = "Instead of refreshing the page, I've changed."; }}})</script>
</body>
</html>
Copy the code
Running results:
After clicking:
Well, it’s easy:
So what’s going on behind all this? Here, we find out.
InitState (new Vue)
When a vue is instantiated, the initState method is called. Its core code is as follows:
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
// ...
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}}Copy the code
From this, we see that when we instantiate vue, we pass in data with only one message, a = “This is the root node”. The initData method is executed.
InitData method core code
function initData (vm: Component) {
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
// omit... Verify keys of data, props, and Methods cannot be duplicated
// omit... Set the data object's access proxy to this.a, which is actually this._data.a, and the value assigned to data is this._data
observe(data, true)}Copy the code
It’s worth noting that the data here is actually vm._data
Observe method core code:
export function observe (value: any, asRootData: ? boolean) :Observer | void {
/ / to omit... If it is a virtual DOM object, it cannot be used by the Observer
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
// ...
) {
ob = new Observer(value)
}
return ob;
}
Copy the code
Observe method: Check whether the object you want to observe is a VNode. If yes, return. Next, determine if the data object we pass in has an __ob__ attribute, in order to prevent repeated observations. Next, go to the instantiation of the Observer class.
The core code of the Observer class is as follows:
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor(value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__'.this)
/ / to omit... Array-related responsive implementations, where arrays and objects are different, will be discussed in more detail in a later article
// If it is not an array, the walk method is executed
this.walk(value)
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
// defineReactive core code:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function, shallow? : boolean) {
const dep = new Dep()
Object.defineProperty(obj, key, {
// ...
get: function reactiveGetter() {
if (Dep.target) {
dep.depend()
}
// ...
return value;
},
set: function reactiveSetter (newVal) {
// ...
dep.notify()
}
})
}
// Dep core code
export default class Dep {
statictarget: ? Watcher; id: number; subs:Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
/ /... The following methods can be ignored for the moment, and we will make up the data get set later
}
Copy the code
We can see that when the Observer class is instantiated:
-
- Call the def method to set the __ob__ property on vm._data, whose value is an Observer class. The vm._data data structure becomes the following:
vm._data = {
a: "This is the root node.".__ob__: {
value: {
a: "This is the heel node."
},
vmCount: 1.// dep is dep class
dep: {
id: 'Here's a DEP ID, and every time the DEP class is instantiated, it adds up by 1'..subs: Array<Watcher> = []
}
}
}
Copy the code
-
- The defineReactive method is called to traverse the vm._data object
-
- The defineReactive method actually does a data hijacking by defining a GET set for each key on vm._data. Note that this is just a definition, not a trigger call. (Remember, remember, remember: this is data hijacking for each key on the object, as we’ll see below.)
Ok, so at this point, we’ve looked at the reactive aspects of initialization.
Next, we move on to the next stage of reactive implementation, mount. Ready to drive
2. The mount
In the previous article, we introduced the overall mount execution process. $mountComponent (); $mount (); $mount ();
The core code of mountComponent is as follows:
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
// omit n lines here...
callHook(vm, 'beforeMount')
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true)
// ...
callHook(vm, 'mounted')
return vm
}
Copy the code
Here, we can see that we call the beforeMount hook and then define the updateComponent method. Then, instantiate the Watch class.
The Watcher core code is as follows:
export default class Watcher {
vm: Component;
// ...
deps: Array<Dep>;
depIds: SimpleSet;
getter: Function;
// ...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object, isRenderWatcher? : boolean) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
// ...
this.value = this.lazy ? undefined : this.get()
}
get() {
pushTarget(this)
// omit try catch... Let's go straight to the core call
value = this.getter.call(vm, vm)
popTarget()
return value
}
addDep(dep: Dep) {
// ...
dep.addSub(this)}update() {
/ /... Omit lazy and sync
queueWatcher(this)}}Copy the code
When Watcher is instantiated, it holds the VM variable, which is the instance of the current component. It is important to point out that our example here has only one div with no nested components. If there are child components, when the child component is instantiated, the VM here is the child component, not the Vue.
Remember: Vue is actually a Vue constructor, and the template component is not actually a Vue constructor, but a VueComponent constructor. I will share the vue source code in a section later.
Ok, so let’s go ahead and mount an instance of Watcher on vm._watch, where lazy doesn’t exist and gets called. So let’s first look at pushTarget, popTarget
Dep class core code:
class Dep {
statictarget: ? Watcher; id: number; subs:Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)}}notify() {
// ...
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep.target = null
const targetStack = []
export function pushTarget (target: ? Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]}Copy the code
Now, some of you have already figured it out: this is actually a publish and subscribe model.
As you can see, the get method of the Watcher class calls pushTarget, which sets the target property of the Dep class to the current Watcher object. The targetStack array pushes the current Watcher class.
Remember: when multiple layers of components are nested, the dep. target value is not cluttered; it always points to the Watcher currently being instantiated.
In later chapters, I will analyze the componentization process of vUE implementation in detail, and will mention deP, Watcher, and Observer again.
Vm._data.__ob__. Dep = vm._data.__ob__. Dep = vm._data.__ob__. Dep = vm._data.__ob__.
Don’t worry, here’s the point:
After watcher is instantiated, the this.getter.call method is called, which is actually the updateComponent method in the mountComponent function. The core of this method: vm._update(vm._render(), hydrating).
Here, we can see that there are two steps:
-
- First execute the render function to obtain the virtual DOM
-
- Perform the update, which is actually the patch process
In the last article, we learned that the virtual DOM is actually an object on which properties are defined to describe DOM nodes. While generating the virtual DOM accesses the data object, that is, the vm._data object, which triggers the get method described earlier (get in the defineReactive function earlier in this article).
In the get method, the following code is executed:
if (Dep.target) {
dep.depend()
}
Copy the code
The DEP class is actually a dependency collector that acts as a bridge between Data Observe and Watcher. Dep.depend () looks like this:
depend () {
if (Dep.target) {
Dep.target.addDep(this)}}Copy the code
Dep. Target refers to the Watcher instantiated by the component. The addDep method in the Watcher class looks like this:
// ...
dep.addSub(this)
Copy the code
The addSub method in deP looks like this:
addSub (sub: Watcher) {
this.subs.push(sub)
}
Copy the code
At this point, the subs array of the DEP class adds the corresponding Watcher, and the VM attribute in the Watcher refers to the component instance that currently uses this data.
At this point, we can see that the data structure of the vm._data object looks like this:
vm._data = {
a: "This is the root node.".__ob__: {
value: {
a: "This is the heel node."
},
vmCount: 1.// dep is dep class
dep: {
id: 'Here's a DEP ID, and every time the DEP class is instantiated, it adds up by 1'..subs: [{/ / Watcher
/ /... Omit the various properties of the Watcher class
update(){... },vm: "Vue component instance object using this data"}]}}}Copy the code
When the data in data changes, that is, when the data in vm._data changes, the corresponding set (set in the defineReactive function earlier in this article) is called, with the following core code:
dep.notify()
// Notify core method in deP
notify() {
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
Copy the code
Subs, in this case, is the Watcher class in the DEP dependent collector array, which holds the instance object VM of the component that depends on the data. Then you can call the patch method of the corresponding component instance to diff the DOM update.
Note that:
-
- According to the vm._data data structure, when the value below vm._data changes, we can get the dep.subs data above the vm._data.__ob__ property, and then loop through Watcher’s update method
-
- The watcher update method is an update queue, and we will focus on the detailed process in the following chapters. Here we can simply understand that every component instance VM that uses this data is saved in watcher, so the patch diff update can be performed on the components that use this data. Instead of refreshing the entire page DOM.
Ok, at this point, the response is implemented ~
3. To summarize
Vue realization of responsive principle steps:
-
- The initState method is called when a vue is instantiated
-
- Set up the data access proxy where this.xx actually accesses this._data.xx
-
- Judge that data must not duplicate the observer
-
- Vm. _data adds an __ob__ attribute whose value is an Observer class
-
- Observer class, containing value, DEP core properties
-
- The Observer class, the DEP attribute, is an instance object of the DEP class
-
- Loop defineReactive for all data under vm._data
-
- DefineReactive method, Object. DefineProperty hijacking get set for all vm._data data
-
- After vue is instantiated, a Watcher is instantiated when it is mounted
-
- When Watcher is instantiated, the target property of the current Dep class is set to point to the currently instantiated Watcher
-
- Execute the updateComponent method
-
- Execute the Render method of the current VM instance to generate the virtual DOM
-
- Get the vm._data data, triggering the GET defined in the defineReactive method
-
- The DEP class collects vm._data data, and vM. _data.__ob__.dep.subs push corresponds to watcher
-
- The collected Watcher has a VM property that points to a component instance that uses vm._data.xx
-
- Update all watcher information in a loop when data changes on vm._data
-
- Enter the UPDATE queue and perform patch to update the DOM
In the next post, I will share how VUE implements componentization. Code word is not easy, a lot of attention, like ~ 😽