Initialize instance properties
- Vue mounts properties to the instance via the initLifecycle function
export function initLifecycle(vm) {
const options - vm.$options;
// Find the first non-abstract parent class
let parent = options.parent;
if(parent && ! options.abstract) {while(parent.$options.abstract && parent.$parent) {// If the parent is an abstract class, assign the value to vm.$parent until the first non-abstract parent is encountered
parent = parent.$parent;
}
parent.#children.push(vm);
}
vm.$parent = parent;
vm.$root = parent? parent.$root: vm; Represents the root Vue instance of the current component tree
vm.children = []; // represents the immediate child component that contains the current instance
vm.$refs = {};
vm._watcher = null;
vm._isDestroyed = false;
vm._isBeingDestroyed = false;
}
Copy the code
Initialization event
- Vue performs initialization events through the initEvents function, as shown in the above flowchart
export function initEvents(vm) {
vm._events = Object.create(null);
// Initializes the event attached by the parent component
const listrens = vm.$options.parentListeners;
if(listrens) {
updateComponentListenners(vm,listeners)
}
}
/ / updateComponentListenners function source code
let target;
function add(event,fn,once) {
if(once) {
target.$once(event,fn);
}else{ target.$on(event,fn); }}function remove(event,fn) {
target.$off(event,fn);
}
export function updateComponentListenners(vm,listeners,oldListeners) {
target = vm;
updateListenners(vm,listeners|| {}, add, remove,fn);// If a Key exists in the Listenners object but not in the oldListeners, it indicates that the listeners need to add a key
}
/ / updateListenners function
function isUndef(v){
return v === undefined || v === null
}
export function updateListenners(on,oldOn,add,remove,vm) {
let name ,cur,old, event;
for (name in on) {
cur = on[name];
old = oldOn[name];
event = normalizeEvent(name);
if(isUndef(cur)) { // Determine whether the value corresponding to the event name is undefined or null, if so, trigger a warning on the consoleprocess.env.NODE_ENV ! = ='production' && warn(`Invalid handler for event '${event.name}':got` + String(cur),vm);
} else if (isUndef(old)) { // Determine whether the event name exists in oldOn. If not, call add to register the event
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur);
}
add(event.name, cur, event.once, event.capture, event.passive)
} else if(cur ! == old) {// If both exist, but they are not the same, replace the event callback with the callback in onold.fns = cur; on[name] = old; }}for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
Copy the code
Initialization state
- The initialization status includes props, Methods, data, conputed, and Watch
graph TD
initState --> initProps
initState --> initMethods
initState --> initData
initState --> initComputed
initState --> initWatch
/ / initState source code
initState function initState(vm) {
vm._watchrs = [];
const opts = vm.$options;
if(opts.props) initProps(vm.opts.props); // if vm.$options has props, call props if it does
if (opts.methods) initMethods(vm,opts.methods);// If the methods attribute is present, the call initializes methods
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);// If it does not exist. Use the observe function directly to observe the empty object
}
if(opts.computed) initComputed(vm,opts.computed);
if(opts.wathch && opts.watch !== nativeWatch) {
initWathch(vm,opts.wathch);
}
}
Copy the code
1. Initialize props
- The parent component provides the data, and the child component selects what it needs through the props field
Normalizing props source code:
function normalizeProps(options,vm) {
const props = options.props;
if(! props)return; // If there is no props attribute, the user is not using props to receive any data
const res = {};
let i,val,name;
if(Array.isArray(porps)) { // If this is an Array, normalize props of type Array to object through each item of the while Array
i = props.length;
while(i--) {
val = props[i];
if(typeof val === 'string') {
name = camelzie(val);
res[name]= {type: null};
}else if(process.env.NODE_ENV ! = ='production') { // Print warnings in non-production environments
warn('poprs must be strings when using array syntax'); }}}else if(isPlanObjcet(props)) { // The types of the normalized props can be basic functions or arrays,
for(cosnt key inprops) { val = props[key]; name = camelize(key); res[name] = isPlanObject(val)? val: {type:val}
}
} else if(process.env.NODE_ENV ! = ='production') {
warn(`Invalid value for option "props":expected an Array or an Object`.`+ but got ${toRawType(props)}. `,vm);
}
options.props =res; // Returns the normalized result
}
Copy the code
2. Initialize methods
- Iterate over the Methods object in the options, and mount each attribute in turn to the VM
function initMethods (vm,methods) {
const props - vm.$options.props;
for (const key in methods) {
if(process.env.NODE_ENV ! = ='production') {
if (methods[key] == null) { // In a non-production environment, verify that the method is valid and issue a warning on the console
warn(
`Method "${key}" has an undefined value in the compontent definition.` + `Did you reference the function correctly? `,vm); }}if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has alreay been defined as a props.`,vm);
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance methods .` +
`Avoid defining component methods that start with_or $.`,vm);
}
}
vm[key] = methosd[key] == null ? noop :bind(methods[key],vm)// Mount the method to the VM [key]
}
Copy the code
4. Initialize computed
- The result of the computed genus we know is cached and recomputed only if the return value of the dependent reactive attribute changes
- Look at the relationship below
- Compute properties computed source code
const computedWatcherOptions = {lazy, true};
function initComputed(vm,computed) {
const watcher = vm._computedWatcher = Object.create(null); // Note that the created object has no prototype. It does not have a _proto_ attribute
// Calculate attributes in SSR, just a normal getter method
const isSSR = isServerRending();
for (const key in computed) {
const userDef = computed[key];
const getter = typeof userDef === 'function' ? userDef : userDef.get;
if(process.env.NODE_ENV ! = ='production' && getter == null) {
warn(`Getter is missing for computed property "${key}"`);
}
// In non-SSR, create an internal observer for the calculated attributes
if(! isSSR) { watcher[key] =new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
if(! (keyin vm)) {
defineComputed(vm, key, userDef);
} else if(process.env.NODE_ENV ! = ='production') {
if(key in vm.$data) {
warn(`The .... "${key} in data `, vm);
} else if(vm.$options.props && key in vm.$options.props) {
warn(`The .... "${key} in prop `, vm); }}}}// initComputed initializes computed properties, vm is vue instance context (this)
Copy the code
The initial watch
// initWatch receives 2 arguments, one VM, one watch object,
function initWatch(vm,watch) {
for(const key in watch) {
const handler = watch[key];
if(Array.isArray(handler)) {
for(let i =0; i<handler.length;i++) {
createWatcher(vm,key,hnadler);
}
} else{ createWatcher(vm,key,handler); }}}// The createWatcher function is responsible for handling other types of handlers and calling the vm.$watch watcher to observe the expression
function createWatcher(vm,expOrFn,handler,optons) {
if(isPlainObject(handler)) { // If an object is included, set a special included object, so options is set to handler; And handler object handler method
options = handler;
handler = handler.handler;
}
if(type handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn,handler,optons);
}
// vm:vue execution context
// expOrFn: Representation or audit attribute functions
// Handler watch object value
// options: Options object to pass to vm.$watch
Copy the code
Hook function
- The same as a callback function. When the system executes somewhere, it checks for a hook and executes a callback if it does
The VUE lifecycle can be divided into eight phases:
-
Before beforeCreate instance creation:
The first hook fired after new Vue() cannot be accessed in the current phase for data, methods, computed, and data and methods on watch.
-
Created instance created:
Occurs after the instance has been created. The current phase has completed data observation, that is, data can be used and changed. You can do some initial data fetching, you can’t interact with the Dom at this stage, you can access the Dom via vm.$nextTick if you want to.
-
BeforeMount Before mounting:
Before this, the Template template has been imported into the rendering function for compilation. At this stage, the virtual Dom has been created and is about to start rendering. Data can also be changed at this point without triggering the updated
-
Mounted: In this stage, the Dom is mounted and data is bidirectional bound. You can access the Dom node and use the $refs attribute to perform operations on the Dom. You can also send a request to the background and get the data back
-
BeforeUpdate Before the update:
Before the update, which is triggered before the responsive data is updated and the virtual DOM is re-rendered, you can make changes to the data at this stage without re-rendering
-
Updated:
The component Dom has been updated at this stage. It is important to avoid changing the data during this period, as this may result in an infinite loop of updates
-
BeforeDestory before destruction: The current phase is when the instance is fully available and we can clean up after, such as clearing timers.
-
Destoryed destruction completed:
At this point, all that’s left is the DOM shell. Components have been disassembled, data bindings removed, listeners removed, and subinstances destroyed
What happens when new Vue() is called
function vue(options) {
if(process.env.NODE_ENV ! = ='production' && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')}this.init(options)
}
exprot default Vue
If vue is not called with new in a non-production environment, this throws an error warning at the console: Vue is a constructor and should be called with the new keyword
// 2. The initialization process of the declaration cycle is implemented in this._init
Copy the code
The inner workings of the _init method
Vue.prototype._init = function (options) {
vm.$options = mergeOptons(
resloveConstructorOptions(vm.construtor),
options||{},
vm
);
}
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm,'beforeCreate');
initInjections(vm);// Initialize inject before data/props
initState(vm);
initProvide(vm);// Initialize provide after data/props
callHook(vm,'created');
// If the user passes the EL option when instantiating vue, the template compilation phase and mount phase are automatically enabled
// If the user is passing the el option. This does not go to the next lifecycle process
// If you need to execute vm.$mount to manually start the compilation and mounting phases of the template
if(vm.$options.el){
vm.$mount(vm,$options.el)
}
Copy the code
Hook function source code
1. BeforeCreate and created
// src/core/instance/init
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options? :Object) {
const vm: Component = this.// The merge options section has been omitted
initLifecycle(vm)
$parent, $root, $children, and other lifecycle attributes are added to the VM object
initEvents(vm) // Initializes the event-related attributes
initRender(vm) // THE VM adds attributes and methods related to the virtual DOM, slot, etc
callHook(vm, 'beforeCreate') // Call the beforeCreate hook
// the initinjection (VM) and initProvide(VM) injections values defined in the parent component _provided into the child component, and these properties are not observed
initInjections(vm)
initState(vm) // Initialize data such as props, methods, data, watch, and computed
initProvide(vm)
callHook(vm, 'created') // Calls the created hook}}// src/core/instance/state
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)
if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }// 1. The beforeCreate hook is called before initState, and initState is used to initialize properties such as props, methods, data, computed, and watch.
//2. The beforeCreate hook has no access to data on props, methods, data, computed, or watch. In Created
Copy the code
2. BeforeMounted and mounted
// The mountComponent core is to instantiate a render Watcher
// The updateComponent method is called in its callback
// Two core methods vm._render(generate virtual Dom) and vm._update(map to real Dom)
// src/core/instance/lifecycle
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
vm.$el = el
if(! vm.$options.render) { vm.$options.render = createEmptyVNode ... } callHook(vm,'beforeMount') // Call beforeMount hook
let updateComponent
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
updateComponent = () = > {
// A function that maps the virtual Dom to the real Dom.
Vm._render () is called before vm._update.const vnode = vm._render()
...
vm._update(vnode, hydrating)
}
} else {
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
// Check whether mouted is completed and is not destroyed
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted') // Call mounted hook
}
return vm
}
BeforeMount hook function is executed before vm._render() is used to render VNode, and mouted hook is executed after vm._update() is used to patch VNode to the real Dom. Why did not get Dom properly until mounted stage
Copy the code
BeforeUpdate and updated
// src/core/instance/lifecycle
new Watcher(vm, updateComponent, noop, {
before () {
// Check whether mouted is completed and is not destroyed
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate') // Invoke the beforeUpdate hook}}},true /* isRenderWatcher */)
// src/core/observer/scheduler
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if(vm._watcher === watcher && vm._isMounted && ! vm._isDestroyed) {// Only if the current watcher is vm._watcher (i.e. the current render watcher)
// The updated hook function is executed if the component is mounted and is not destroyed.
callHook(vm, 'updated') // Calls the updated hook}}}// src/instance/observer/watcher.js
export default class Watcher {...constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object.// In its constructor, isRenderWatcher,
// Then assign the current instance of watcher to vm._watcherisRenderWatcher? : boolean) {
// Push the current wathcer instance to vm._watchers,
// vm._watcher is used specifically to listen for changes in vm data and then re-render,
// So it's a rendering dependent watcher, so in the callUpdatedHooks function,
// The updated hook function is executed only after the vm._watcher callback completes
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)... }Copy the code
# beforeDestroy and destroyed
// src/core/instance/lifecycle.js
// During $destroy, it executes vm.__patch__(vm._vnode, null).
// Trigger the destruct hook function of its child component, such a recursive call,
// The destroy hook is executed in the same order as the mounted procedure.
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy') // Call the beforeDestroy hook
vm._isBeingDestroyed = true
// Some destruction work
const parent = vm.$parent
if(parent && ! parent._isBeingDestroyed && ! vm.$options.abstract) { remove(parent.$children, vm) }/ / remove the watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
...
vm._isDestroyed = true
// Invoke the destroy hook on the current Rendered tree
// If a subcomponent is found, it is destroyed first
vm.__patch__(vm._vnode, null)
callHook(vm, 'destroyed') // Call the Destroyed hook
// Close all instance listeners.
vm.$off()
// Remove the __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// Release circular references
if (vm.$vnode) {
vm.$vnode.parent = null}}}Copy the code