This is the 14th day of my participation in Gwen Challenge
preface
As anyone who has written Vue code knows, there are two ways to create a Vue instance:
First: instantiate a Vue object
new Vue({
el: '#app'.data: {
message: 'Hello Vue! '}})Copy the code
Second: instantiate child components
import App from './App.vue';
new Vue({
router,
store,
render: h= > h(App)
}).$mount('#app');
Copy the code
Either way, the Vue constructor is called, so what exactly does this new Vue procedure do?
Vue initialization
Vue constructor defined in SRC/core/instance/index, js, it basically is to call a _init method
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)
}
Copy the code
_init methods defined in SRC/core/instance/init. Js
Vue.prototype._init = function (options? :Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
...
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if(process.env.NODE_ENV ! = ='production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
Copy the code
When the Vue instance is a component, the initInternalComponent method is executed. Otherwise, execute mergeOptions
initInternalComponent
The method as a whole is to add attributes to the vm.$options, the instantiation component will be explained later.
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
Copy the code
mergeOptions
MergeOptions merges the options configuration parameters of parent and child, where parent is the options of the constructor and child is the options passed in when instantiated. When child._base is true, the definition is also merged in extends and mixins.
DefaultStrat and Strats contain the specific merge strategy, which is not covered here but will be covered in a chapter later.
export function mergeOptions (
parent: Object,
child: Object, vm? : Component) :Object {
if(process.env.NODE_ENV ! = ='production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if(! child._base) {if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if(! hasOwn(parent, key)) { mergeField(key) } }function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
Copy the code
Several initialization functions are not expanded here, but will be expanded individually later.
The _init method is used to initialize the VM. The life cycle hooks beforeCreate and created are executed during initialization. Initialize the event, data, etc.
$mount(vm.$options.el); $mount(vm.$options.el); If no EL is passed, we will manually call $mount(‘#app’). We can either instantiate the Vue component or Vue object by passing the el argument, or we can call $mount manually
$mount (); $mount (); $mount ();
Vue mounts the instance
Vue mounts the VM through the $mount instance method. The $mount method has multiple definitions, as its implementation depends on the platform and how it is built, but is extended by the mount method defined on the prototype.
Look at the Vue prototype defined on $mount method: SRC/platform/web/runtime/index. Js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
) :Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
Copy the code
The $mount method takes two main parameters: the first is the mounted element (either a string or a DOM object), and the second is the server render parameter. The last is to call the real mountComponent methods: SRC/core/instance/lifecycle. Js
Take a look at the highlights of the mountComponent method:
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
vm.$el = el
if(! vm.$options.render) { vm.$options.render = createEmptyVNode ... } callHook(vm,'beforeMount')
let updateComponent
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
updateComponent = () = >{... }}else {
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')}return vm
}
Copy the code
MountComponet first calls the beforeMount hook. Then we define updateComponent
Next instantiate a render Watcher, which is the core of the mountComponent. The Watcher executes the updateComponent callback function on the new Vue instance. Execute the callback function when monitoring data changes in the VM instance (added later in the watcher implementation)
Vm. $vnode represents the parent virtual node of the Vue instance. If it is null, it indicates that it is currently an instance of the root Vue.
The updateComponent callback is simply defined as vm._update(vm._render(), hydrating), which calls two methods: _render renders the Vue instance as a Vnode, and _update renders the Vnode as a real DOM
conclusion
The Vue project entry file does two main things during the new Vue instance, one is to initialize the VM (various events, parameters, etc.), and one is to mount the Vue instance to ‘#app’