Vue is currently one third of the country’s front-end Web end, but also as one of my main technology stack, in daily use, also curious about why, in addition to the recent emergence of a large number of vUE source code reading articles, I take this opportunity to draw some nutrition from everyone’s article and discussion, At the same time, some of the ideas of reading source code are summarized, produce some articles, as the output of their own thinking, my level is limited, welcome to leave a message to discuss ~
Target Vue version: 2.5.17-beta.0
Vue source note: github.com/SHERlocked9…
Note: the syntax of the source code in the article uses Flow, and the source code is truncated as required (in order not to be confused @_@), if you want to see the full version of the github address above, this is a series of articles, the article address is at the bottom of ~
If you are interested, you can join the wechat group at the end of the article and discuss with us
1. Responsive systems
Vue. Js is an MVVM framework that doesn’t care about view changes, but updates the view with data. This makes state management very simple. Stealing a picture from the official website
Each component instance has a corresponding Watcher instance object, which records properties as dependencies during component rendering and then, when setters for dependencies are called, informs Watcher to recalculate, causing its associated components to be updated.
Here are three important concepts Observe, Dep, Watcher, located in the SRC/core/observer/index, js, SRC/core/observer/Dep., js, SRC/core/observer/Watcher. Js
Observe
Class adds attributes to reactive objectsgetter/setter
Used for dependency collection and distribution of updatesDep
Class to collect the dependencies of the current reactive objectWatcher
Class is observer, instance is divided into three types: render watcher, compute property Watcher, listener watcher
2. Code implementation
2.1 initState
Response to a type of entrance is located in the SRC/core/instance/init. Js initState:
// src/core/instance/state.js
export function initState(vm: Component) {
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) // Initialize props
if (opts.methods) initMethods(vm, opts.methods) // Initialize methods
if (opts.data) initData(vm) // Initialize data
if (opts.computed) initComputed(vm, opts.computed) // Initialize computed
if (opts.watch) initWatch(vm, opts.watch) // Initialize watch}}Copy the code
It regularly defines several methods to initialize props, methods, data, computed, and wathcer. Here’s a look at the initData method to see a leopard
// src/core/instance/state.js
function initData(vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
observe(data, true /* asRootData */) // Perform reactive processing on data
}
Copy the code
An observe method attempts to create an Observer instance __ob__, and returns a new Observer instance if it succeeds. Returns the existing Observer instance if it already exists
2.2 the Observer/defineReactive
// src/core/observer/index.js
export function observe (value: any, asRootData: ? boolean) :Observer | void {
let ob: Observer | void
ob = new Observer(value)
return ob
}
Copy the code
This method uses data as an argument to instantiate an instance of an Observer object. Observer is a Class used for dependency collection and notify. The Observer constructor uses defineReactive to instantiate the object’s key reactivity. Add getter/setter recursively to the property of the object. When data is evaluated, the getter is triggered and the dependency is collected. When the value is modified, the getter is triggered first and then the setter is triggered and updates are issued
// src/core/observer/index.js
export class Observer {
value: any;
dep: Dep;
constructor (value: any) {
value: any;
this.dep = new Dep()
def(value, '__ob__'.this) // the def method is guaranteed not to be enumerable
this.walk(value)
}
// Iterate over each property of the object and convert them to getters/setters
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) { // Reactalize all traversable objects
defineReactive(obj, keys[i])
}
}
}
export function defineReactive (obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean) {
const dep = new Dep() // Define a DEP object in the closure of each reactive key value
// If a getter/setter was preset for the object, it will be cached and executed in the newly defined getter/setter
const getter = property && property.get
const setter = property && property.set
letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val Execute if the original object has a getter method
if (Dep.target) { // If there is a watcher reading the current value
dep.depend() // Then do the dependency collection, dep.addSub
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val / / the getter
if(newVal === value || (newVal ! == newVal && value ! == value)) {// If the value is the same as the original value, no matter
return
}
if (setter) { setter.call(obj, newVal) } Execute if the original object has setter methods
else { val = newVal }
dep.notify() // If there is a change, notify the update, call watcher.update()}})}Copy the code
In the getter, dependency collection is performed only when there is a value in the dep. target. PushTarget pushes the current value of the Watcher into the dep. target, and pushes the original Watcher into the targetStack. When the current watcher value is finished, the stack is unloaded and the original watcher value is assigned to dep. target. CleanupDeps finally clears the missing watcher in the new newDeps to prevent unwanted watcher triggers from the view
If there is no change to the old value, return. If there is change, deP will notify all Watcher instances in subs that depend on this data to update. QueueWatcher () is asynchronously pushed from the update to the dispatcher observer queue, FlushSchedulerQueue () at nextTick flushSchedulerQueue() removes the watcher from the queue to execute watcher.run and the related hook function
2.3 Dep
Dep is a dependency collection container, or dependency collector, that keeps track of which Watchers depend on their changes, or which watchers subscribe to their changes; Here is a quote from a netizen:
Liuhongyi0101: To put it simply, it is reference counting, who borrowed my money, I will write that person down, and later I will notify them that I have no money when my money is less
And the little book that keeps the borrower down is the subs in the Dep instance here
// src/core/observer/dep.js
let uid = 0 // The id of the Dep instance to facilitate de-duplication
export default class Dep {
statictarget: ? Watcher// Who is currently collecting dependencies
id: number
subs: Array<Watcher> // Set of observers
constructor() {
this.id = uid++ // The id of the Dep instance to facilitate de-duplication
this.subs = [] // Stores the Watcher that needs to be notified in the collector
}
addSub(sub: Watcher) { ... } /* Add an observer object */
removeSub(sub: Watcher) { ... } /* Removes an observer object */
depend() { ... } /* Dependency collection, add yourself to observer dependencies when dep. target is present */
notify() { ... } /* Notify all subscribers */
}
const targetStack = [] / / watcher stack
export function pushTarget(_target: ? Watcher) {... }/* Set the Watcher observer instance to dep.target to rely on the collection. This instance is also stored in the target stack */
export function popTarget() {... }/* Remove the observer instance from the target stack and set it to dep.target */
Copy the code
The dependency collected by subs in the Dep instance is watcher, which is an instance of Watcher and will be used to notify updates in the future
2.4 Watcher
// src/core/observer/watcher.js
/* A parsed expression that performs a dependency collection on the observer and triggers a callback function when the expression data changes. It is used in the $watch API as well as the instruction */
export default class Watcher {
constructor( vm: Component, expOrFn: string | Function, cb: Function, options? :? Object, isRenderWatcher? : Boolean // whether to render watcher flag bit) {this.getter = expOrFn // execute in the get method
if (this.computed) { // Whether it is a calculated attribute
this.value = undefined
this.dep = new Dep() // The calculated property was not evaluated during creation
} else { // Attributes are not evaluated immediately
this.value = this.get()
}
}
/* Get the getter value and re-collect the dependency */
get() {
pushTarget(this) // Set dep. target = this
let value
value = this.getter.call(vm, vm)
popTarget() // Remove the observer instance from the target stack and set it to dep.target
this.cleanupDeps()
return value
}
addDep(dep: Dep) { ... } /* Add a dependency to the Deps collection */
cleanupDeps() { ... } /* Remove useless watcher dependencies that are not in newDeps */
update() { ... } /* Scheduler interface to call back when a dependency changes */
run() { ... } /* Dispatcher work interface, dispatcher callback */
getAndInvoke(cb: Function) {... } evaluate() { ... }/* Collect all dePS dependencies for this watcher */
depend() { ... } /* Collect all dePS dependencies for this watcher, only calculated attributes use */
teardown() { ... } /* Remove itself from all dependent collection subscriptions */
}
Copy the code
UpdateComponent = () => {vm._update(vm._render(), hydrating)}, This method starts with vm._render() generating the render VNode tree, completing data access on the current Vue instance VM, triggering getters for the corresponding responsive objects, and then vm._update() to patch
Note that the get method finally executes getAndInvoke. This method first iterates through the deps stored in watcher and removes the subscription that is no longer in newDep. DepIds = newDepIds; Deps = newDeps, emptying newDepIds and newDeps. Remove old subscriptions that are no longer needed after each new subscription is added, so that watcher is not notified to update in some cases, such as when data on a template dependent that v-if no longer needs changes
2.5 summary
The whole collection process is about like this, you can compare with the above process
Watcher can be used in the following scenarios:
render watcher
Render Watcher, the watcher for rendering viewscomputed watcher
Calculated properties Watcher, because calculated properties are both dependent on and dependent on, and therefore hold oneDep
The instancewatch watcher
The listener watcher
If it is dependent on another observer, such as Data, data properties, calculation properties, props, it generates an instance of the Dep in the closure and collects it when the getter is called. And is dependent on the watcher of deposit into their subs this. Subs. Push (sub), in order to change the notice notify in dep. Subs array depends on his own, which has changed, please timely update ~
Any object that depends on another reactive object will generate an observer watcher to count which reactive objects the watcher depends on. The current watcher will be set to the global DEP.target before the watcher is evaluated. And update when the responsive objects on which they depend change
This article is a series of articles that will be updated later to make progress together
- Vue source code to read – file structure and operation mechanism
- Vue source code read – dependency collection principle
- Vue source code read – batch asynchronous update and nextTick principle
Online posts are mostly different in depth, even some inconsistent, the following article is a summary of the learning process, if you find mistakes, welcome to comment out ~
Reference:
- Vue2.1.7 source code learning
- Vue. Js technology revealed
- Analyze the internal operation mechanism of vue.js
- Vue. Js document
- [large dry goods] hand in hand with you over vUE part of the source code
- MDN – Object.defineProperty()
- Vue. Js source code learning a – data option State learning
PS: Welcome to pay attention to my official account [front-end afternoon tea], come on together
In addition, you can join the “front-end afternoon tea Exchange Group” wechat group, long press to identify the following TWO-DIMENSIONAL code to add my friend, remarks add group, I pull you into the group ~