preface
Hello everyone, my name is Good. This chapter is suitable for those who have read some of the source code but do not understand it thoroughly.
- If you don’t know the JS built-in API, you can go to
MDN
Looking updeveloper.mozilla.org/zh-CN/
The source code analysis is divided into the following sections, which will be updated later
- Vue initialization process – Data binding
- Vue template compilation principle
- Vue relies on the collection principle
- Vue global hook functions and lifecycle hooks
- Vue Diff core process principles
Vue responsive principle
Initialization process analysis: Initialize instance > Initialize Status > Bind data
So this is our template
let vm = new Vue({
el: '#app'.data() { // Data is a function in the root component
return {
name: 'vue'.obj: {
name: 'obj'
},
ary: ['one', {
message: 'inAry'}}}});Copy the code
Initialize instance
Without further ado, let’s get right to the code
import { initMixin } from './init'
// When new Vue, the _init method in the instance is executed and the configuration is initialized
function Vue (options) {
// if (process.env.NODE_ENV ! == 'production' &&
/ /! (this instanceof Vue) // error if this is not an instanceof Vue
// ) {
// warn('Vue is a constructor and should be called with the `new` keyword')
// }
this._init(options); // Pass the configuration object
}
initMixin(Vue); // Pass Vue before new Vue
Copy the code
If there is no file module partition, it will be difficult to manage in the case of excessive code
Go to the _init method
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
const vm = this
// omit validation source code...
// if (options && options._isComponent) {we initialize the page not as a component so go else
// initInternalComponent(vm, options)
// } else {
// We will talk about merging in more detail in a later section, but first we need to mount options on the instance
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// }
// initLifecycle(vm) comment section, ignored for the moment, more on in a later chapter
// initEvents(vm)
// initRender(vm)
// callHook(vm, 'beforeCreate') // Lifecycle hook
// initInjections(vm) // resolve injections before data/props
initState(vm) // Initialization state
// initProvide(vm) // resolve provide after data/props
// callHook(VM, 'created') // Lifecycle hooks
// Page mount logic
$mount('#app') $mount('#app')
// The $mount method is still used
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
Copy the code
_init mainly does initialization of state and mount pages, but there are other initialization processes as well. We’ll talk about them all later
Initialization state
Enter the initState method
export function initState (vm) {
vm._watchers = []
const opts = vm.$options
// Since we only have data in the template, we will omit the other source code
if (opts.data) {
initData(vm) // Pass the VM in to bind the attributes in data
} else {
observe(vm._data = {}, true /* asRootData */)}}Copy the code
Enter the initData method
const sharedPropertyDefinition = { // Object.defineProperty's third argument
enumerable: true.configurable: true.get: () = > {},
set: () = >{}};/* Proxy function proxy */
export function proxy (target, sourceKey, key) {
// vm, '_data', key for clarity, note that '_data' is a string and other variables
// Reassign get/set to sharedPropertyDefinition
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]; ['_data'][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val; // equivalent to vm['_data'][key] = val
}
// Call defineProperty to proxy, and get/set is called when we call vm[key]
Object.defineProperty(target, key, sharedPropertyDefinition)
}
/* initData method */
function initData (vm) {
let data = vm.$options.data; // give vm.$options.data to data
// Assign the result to data and vm._data
data = vm._data = typeof data === 'function' // If it is a function, execute it and return it
? getData(data, vm) // Our data is a function, so execute first before returning the result
: data || {}
// omit validation source code...
const keys = Object.keys(data); // Get the key from data
let i = keys.length
while (i--) {
const key = keys[i]; // Get the current I in keys
// omit validation source code...
// We delegate data[key] to vm only if it does not begin with _ and $
if(! isReserved(key)) { proxy(vm,`_data`, key); // The attributes of data. XXX can be accessed directly from vm. XXX}}// The core method of data binding
observe(data, true /* asRootData */)}Copy the code
Data binding
- The heart of data binding
Enter the Observer (Core method)
export function observe (value, asRootData) {
if(! isObject(value) || valueinstanceof VNode) { // If not an object type or a VNode instance
return
}
let ob;
// If you already have an __ob__ song attribute and the attribute value is an instance of an Observer
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__; // Assign the value directly to ob
} else if (
shouldObserve && // Whether to observe, the default is true! isServerRendering() &&// Not server rendering
(Array.isArray(value) || isPlainObject(value)) && // Vlaue is an array or object
Object.isExtensible(value) && // Vlaue is an object to which new attributes can be added! value._isVue// value is not Vue
) {
ob = new Observer(value); // Create an observer to observe value
}
if (asRootData && ob) { // Initialize bound data asRootData is true
ob.vmCount++
}
return ob
}
Copy the code
Enter the Observer method
import { arrayMethods } from './array'
export const hasProto = '__proto__' in {}; // Whether __proto__ is available
function protoAugment (target, src) { // Point the target prototype chain to SRC
target.__proto__ = src
}
export class Observer {
constructor (value) {
this.value = value
// this.dep = new Dep(); Dependency collection, which will be covered below but ignored for now
// this.vmCount = 0
def(value, '__ob__'.this); Value ['__ob__'] = this
if (Array.isArray(value)) { // Handle the array differently
if (hasProto) { __proto__ / / available
protoAugment(value, arrayMethods) // Point the prototype chain of Value to arrayMethods
}
this.observeArray(value); // The listener array contains the value of the object type
} else { // The object gets the key listener directly
this.walk(value)
}
}
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]); // Bind each value of the object
}
}
observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
// Recursively inspect each item in the array, and return if it is not an objectobserve(items[i]); }}}export function defineReactive (obj, key, val) {
// const dep = new Dep(); Objects depend on collection, which will be covered later
// getOwnPropertyDescriptor gets a descriptor of itself
const property = Object.getOwnPropertyDescriptor(obj, key);
// Object properties cannot be changed or deleted
if (property && property.configurable === false) {
return
}
const getter = property && property.get;
const setter = property && property.set;
if((! getter || setter) &&arguments.length === 2) {
/* * There are several cases: * 1. We define getters externally for properties, so vue stores our getters and has setters * 2. Without a getter, vue will assign obj[key] to val, and it has a setter * 3. Getter /setter is not available, so it will assign val * 4. Getter has but setter does not, so it will not go there */
val = obj[key];
}
// recursively listen on val
letchildOb = ! shallow && observe(val);Object.defineProperty(obj, key, { // Bind data
enumerable: true.configurable: true.get: function reactiveGetter () {
// As long as we pass the getter function, the return value of the getter is what we pass
// But without the getter, it will make val = obj[key]
const value = getter ? getter.call(obj) : val
// if (dep.target) {// Collect the dependency, ignore it for now
// dep.depend()
// if (childOb) {
// childOb.dep.depend()
// if (Array.isArray(value)) {
// dependArray(value)
/ /}
/ /}
// }
// Return the content
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// Check parameters
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
if(process.env.NODE_ENV ! = ='production' && customSetter) {
customSetter()
}
if(getter && ! setter)return
if (setter) { // Notify the setter we defined to execute and pass newVal
setter.call(obj, newVal)
} else{ val = newVal } childOb = ! shallow && observe(newVal);// let newVal listen again
// dep.notify() updates the view}})}Copy the code
ArrayMethods Array variation methods
- The one above that deals with arrays
protoAugment(value, arrayMethods)
ArrayMethods comes from here - The mutation method is an array prototype method that can change the original array
const arrayProto = Array.prototype; // Get the array prototype
export const arrayMethods = Object.create(arrayProto); // Create an empty object and the prototype points to arrayProto
const methodsToPatch = [ // None of these methods can change the original array
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]; // Get the method on the array prototype
ArrayMethods [method] = function mutator (... args) {... }
def(arrayMethods, method, function mutator (. args) { // args is an array whose parameters are the parameters of array methods
// This is whoever calls the mutator method
const result = original.apply(this, args); // Call the method of the source array, but apply changes this to the array that is currently calling it
const ob = this.__ob__; // Get the __ob__ attribute that was assigned earlier in creating an Observer. You can go back to the last code block if you forget
let inserted;
switch (method) {
case 'push':
case 'unshift':
// Assign the new value of the array to the inserted position
inserted = args;
break
case 'splice':
// Cut above the second value added to the array and assign a value to the inserted inserted
inserted = args.slice(2); // For example: [0, 0, 4]. Slice (2)
break
}
// If there is a value inserted, listen for the new value in the array
if (inserted) ob.observeArray(inserted);
// ob.dep.notify() relies on views to update data, as discussed in a later chapter
return result
});
});
Copy the code
conclusion
- Vue can pass directly
vm.xxx
Access to thedata
Properties, all of themproxy
willvm._data
To the VM. - Vue hijacks the data object in the configuration object and then fetches the key listening binding for data. if
data[key]
Is also the value of an object data type, recursivelyobserve(data[key])
If it is an array, it does not loop through the index directly. Instead, it redirects the array prototype chain, adding 7 variantspush/pop/unshift/shift/splice/reverse/sort
Let the observer instanceobserveArray
Inner loop array each item, each item will be calledobserve
Recursive listening
At the end
Thank you for seeing to the end! If there is any incorrect place welcome in the comment area or private letter I correct, thank you!