Response principle
When Vue initializes data, it redefines setters and getters for all properties in the data using object.defineProperty. When the page gets the corresponding property, The GET method is triggered and a dependency collection is performed (the watcher of the current component is collected). If the property changes, the dependencies are notified and the corresponding Watcher is triggered to update.
What is dependent collection?
Object. DefineProperty is used to intercept the data property when it is redefined and then render it. So the logical thing to do before the actual rendering is to collect the dependencies. It says that when the dependencies are collected, a watcher is created for each property, and if the property changes, the corresponding watcher is notified to update the view.
Let's look at the source code
Before looking at the source code, first consider a problem, every day we write Vue, know that it has a process from creation to destruction, the official point, that is the Vue life cycle, okey!
[Look through the water]
New Vue instance – initialize data,events – Template compile – Instance mount – Instance update – Instance destroy, we start with new Vue() instance
Source code analysis
1, what does new Vue() do?
code position : src/core/instance/index.js
// Import a series of modules
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '.. /util/index'
function Vue (options) {
// We call the _init method of the Vue constructor and pass in the options parameter
this._init(options)
}
initMixin(Vue) // 1. Initialize each _init method, including initialization options, render, events, beforCreated, etc
stateMixin(Vue) // 2. Use object.defineProperty to create responsive data and initialize $set $delete $watch
eventsMixin(Vue) // 3. Initialize $on $EMIT in vUE
lifecycleMixin(Vue) // 4. Initialize the life cycle
renderMixin(Vue) // 5. Initialize the _render method
export default Vue
Copy the code
Function Vue (options) {… } is the only code that can be used to do this._init(options), then a series of various initialization method calls, and finally export the Vue instance.
So where is this._init(options) defined? In the initMixin module
code position : src/core/instance/init.js
export function initMixin (Vue) {
// Mount the Vue instance prototype directly
Vue.prototype._init = function (options) {
const vm = this
/ / merge options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
// Mount the instance using the $mount method
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
Copy the code
Init.js does a couple of things: merge the options that the user initializes the instance with the options constructor, then call a series of module initialization methods, and finally mount the instance via $mount.
Back to our topic, reactive data principles, which is how the initialization Vue data is rendered and the listener is set up, so let’s focus on the stateMixin module, okay
2. Initialize data
Entering the stateMixin module, we look directly at the initData function, which initializes the data property
// vm: constructor root instance
function initData (vm: Component) {
// Retrieve the data passed in by the user
let data = vm.$options.data
// Template syntax is different from standard syntax
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if(! isPlainObject(data)) { 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]
if(process.env.NODE_ENV ! = ='production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if(! isReserved(key)) { proxy(vm,`_data`, key)
}
}
/* es6: props, methods, functions, functions, functions, functions, functions, functions, functions, functions, functions DefineProperty includes all the functions of Object. DefineProperty, but the important point is to solve the problem of not listening to arrays, objects, etc. * /
// Observe data
observe(data, true /* asRootData */)}Copy the code
Observe (data, true /* asRootData */) observe(data, true /* asRootData */)
3. Create observations
The obsrver method does not do much. The obsrver method does not do much. The obsrver method does not do much.
Code position: SRC/core/observer/index, js 113 rows
export function observe (value: any, asRootData: ? boolean) :Observer | void {
if(! isObject(value) || valueinstanceof VNode) {
Data () {template syntax returns an object return {}} new Vue ({data is also an object data:{}}) */
return
}
let ob: Observer | void
// The listener will not be listened to again
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__b__
} else if( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && ! value._isVue ) {// Focus, focus, focus. Create a listener for an object that is not being listened on
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Copy the code
Follow it: new Observer() creates a listener. Note that these are two things: the Observer method and the Observer class
Code position: SRC/core/observer/index, js line 37
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor(value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__'.this)
/* Array (array); Is by rewriting the array prototype method, including push,shift,unshift,pop,splice, sort, etc. In other words, vue listens for array changes by rewriting the prototype method + recursive traversal to observe the data. We will explain in detail */
if (Array.isArray(value)) { / / array
if (hasProto) {
protoAugment(value, arrayMethods) // Rewrite the array prototype method
} else {
copyAugment(value, arrayMethods, arrayKeys) // Copy the existing methods of the array
}
this.observeArray(value) ObserveArray looks down at the method entities
} else {
this.walk(value) // Redefine the object type}}// walk: if it is an object, walk into this method
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
// defineReactive data. You can see the defineReactive method here and the method entities belowdefineReactive(obj, keys[i]); }}// If it is an array, iterate over the array, go to the observer method to see if it is listening, and continue the recursive callback if it is not listening
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
// Observe each item in the array
observe(items[i])
}
}
}
Copy the code
From the above code, we can see that one of the main things that the Observer class does is to distinguish between arrays and objects, walk through them, and then execute the defineReactive method, Anyway, we’re going to end up with the defineReactive method and then we’re going to look at the key method of defineReactive responsive data binding which is where we often talk about object.defineProperty () being applied.
4. Data hijacking
Code position: SRC/core/observer/index, js 148 rows
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function, shallow? : boolean) {
// Create a dependent collector - concrete Dep class entity 30 seconds down
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if((! getter || setter) &&arguments.length === 2) {
val = obj[key]
}
// If it is an array, observe recursively
letchildOb = ! shallow && observe(val)// Point, point, point
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.Get => dep.depend()
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// Collect dependencies on watcher, that is, create observers
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
// Collect arrays recursively
dependArray(value)
}
}
}
return value
},
// Description property set => dep.notify()
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
/* eslint-enable no-self-compare */
if(process.env.NODE_ENV ! = ='production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if(getter && ! setter)return
if (setter) {
setter.call(obj, newVal)
} else{ val = newVal } childOb = ! shallow && observe(newVal)// Trigger the data corresponding to the dependency update, focus, focus, see below
dep.notify()
}
})
}
Copy the code
4-1 Dep subscriber
When creating an observation, create a Dep collector for that component const Dep = new Dep(), Create its own watcher observer (in the get of the object.defineProperty method) for each data attribute by dep.depend(), then the set method that triggers the data update calls dep.notify() to trigger the view update
Here’s an important point: a component has only one DEP, but there are multiple Wathcers: Go Go Go
The Dep class can see the constructor, add watcher, remove watcher, etc., so let’s look at the Dep collector and update method notify().
Code position: SRC /core/observer/dep.js 13 line
export default class Dep {
statictarget: ? Watcher; id: number; subs:Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)}}// Notify the store of dependency updates
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
// The update method corresponding to the modified attribute in the dependency
subs[i].update()
}
}
}
Copy the code
The wachter observer class defines the wachter observer class. The wachter observer class defines the wachter observer class. The wachter observer class defines the wachter observer class.
5, update the view
The update () method in the SRC/core/observer/wachter js 166 lines, main is to modify the data, and then drive the view, don’t do further analysis, here share here because it is over, you should understand the reactive principle of vue;
Note: The update method involves another asynchronous queue issue, the API nextTick. See a later article.
Welcome to like, a little encouragement, a lot of growth
A link to the
- Front-end visualization platform implementation scheme
- Vue3 10 minutes takes you seconds to vue3
- Vue template compilation principle
- The vue constructor extends
- How does VUEX state management work
- Vue-router source analysis principle
- Vue [how to listen for array changes]
- Handwritten Vue response [object.defineProperty]
- Vue responsive source sharing