Writing in the front

The first member added to the Vue constructor is initMixin(Vue), which creates the _init method. The gateway to Vue instances is, so to speak, the _init method, so let’s start with this method and take a step-by-step look at how the VM is generated.

/* 
      

{{msg}}

*/
// vue.extend manually mounts components const ExtendUse = Vue.extend({ props: ['msg'].template: `<h2>ExtendUse said: {{msg}}</h2>`,})const ChildExtend = new ExtendUse({ propsData: { msg: 'hello Extend' } }).$mount('#extendUse') Vue.component automatically mounts components const ChildComponent = Vue.extend({ props: ['msg'].template: `<h2>child's father said: {{msg}}</h2>`, }) Vue.component('child-component', ChildComponent) / / the Vue instance const vm = new Vue({ el: '#app'.data: { msg: 'hello Vue',}})Copy the code

As usual, I’ll abbreviate the _init source code and divide it into steps:

Vue.prototype._init = function (options) {
  // 步骤 - 1
  const vm = this
  vm._uid = uid++
  vm._isVue = true
  // 步骤 - 2
  if (options && options._isComponent) {
    initInternalComponent(vm, options)
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )
  }
  // 步骤 - 3
  if(process.env.NODE_ENV ! = ='production') {
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
  vm._self = vm
  // 步骤 - 4
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm)
  initState(vm)
  initProvide(vm)
  callHook(vm, 'created')
  // 步骤 - 5
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}
Copy the code

Step 1

In this code, the first thing we know is who does the VM point to? Function Vue(options) {this._init(options)} function Vue(options) {this._init(options)} function Vue(options) {this._init(options)} So the const vm = this step is actually assigning the instance itself to the VM. More specific? Const app = new Vue(options) where app is the value of the VM. Next, the _uid and _isVue identifiers are added to the instance to record that the current instance is the (_uid + 1) Vue instance object.

Step 2

This code is a conditional determination of whether it is a component. What kind of existence is a component? Does vue. extend manually mount count as a component? I’ll just wrap it up and say that only components that exist in vm.options.components are considered components and can enter if judgments. This means that either Vue.component(id, definition) defines an object or new Vue({components: {id: Definition}}), otherwise does not have the _isComponent property. Thus, even vue.extend, which is also called in vue.component.extend, does not count as a component when manually mounted and does not have the _isComponent attribute. So what does this code mean? In short, we merge the member variable options in the Vue constructor with the options we passed in and mount it on the $options property of the instance.

initInternalComponent

I don’t know if you’ve ever wondered, why does a component go into _init, where does it go into initialization? If you haven’t forgotten Vue.component and vuue. Extend, Vue.component calls vuue. Extend once, storing the generated instance into vm.options.components. At vuue. Extend we defined a VueComponent constructor, which inherits from the Vue constructor and therefore also has _init. The VueComponent constructor calls this._init(options) again, so after layer upon layer, you should be able to see why components can come in. So, what’s going on in this method? When you enter the function body, you will find that “options” has so many properties. I lose, see have not seen, a face meng force gradually become N face meng force. So we first click not table, because it involves the template compilation part, too far back to the trouble.

resolveConstructorOptions

New (Vue. Extend (extendOptions))(options)/new Vue(options) merge options. To clarify, the following VM refers to an instance of the Vue constructor, and componentVM refers to an instance of the constructor returned by vue.extend. This method is essentially to get the options on the Vue constructor. If the constructor passed in does not have a super attribute, then the current instance is VM and returns vm.constructor.options. If the constructor passed in has a super attribute, then the current instance is componentVM, and the current vm.constructive. options is vuecomponent.options. If VueComponent superOptions and VueComponent. Super. The options will be unequal VueComponent. SuperOptions updates, Get the latest VueComponent. Options and return, otherwise return VueComponent. Options. However, superOptions and super.options are both Vue constructors. When are they not equal? You can debug the following code yourself:

const Score = Vue.extend({
  template: '<p>{{firstName}} {{lastName}}: {{score}}</p>'
})
Vue.mixin({
  data() {
    return {
      firstName: 'Walter'.lastName: 'White'
    }
  }
})
Score.mixin({
  data: function () {
    return {
      score: '99'}}})new Score().$mount('#app')
Copy the code

mergeOptions

First, we need to understand what this method does.

function mergeOptions (parent, child, vm) {
  if(process.env.NODE_ENV ! = ='production') {
    checkComponents(child)
  }
  if (typeof child === 'function') {
    child = child.options
  }
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, 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

checkComponents

Judge (vm | componentVM). Options.com ponents: {id: definition} id is equal to the slot/component or other native HTML tags, if it is an error.

normalizeProps

When (vm | componentVM) of the incoming props in the options to activate the method, the purpose is to format the props in the property. If options.props is an array, unify the values of options.props as {item: {type: null}}. If options.props is an object, unify the value of options.props as {key: {type: value}}. Convert a key to nameSpace when it is named after a dash (name-space)

// If options.props is an array
new Vue({
  props: ['msg'.'aeo-rus']})/* props: { msg: { type: null }, aeoRus: { type: null } } */
// If options.props is an object
new Vue({
  props: {
    msg: {
      type: String.default: 'hello Vue'
    },
    'aeo-rus': {
      default: 'aeorus'}}})/* props: { msg: { type: String, default: 'hello Vue' }, aeoRus: { default: 'aeorus' } } */
Copy the code

normalizeInject

When (vm | componentVM) of the incoming inject the options of activated when the method, the purpose is to format the inject the properties. If options.inject is an array, unify the value of options.inject as {item: {from: item}}. If options.inject is an object, unify the value of options.inject as {key: {from: value}} (when value is also an object, unify the value as {from: key, value.k: The value v}).

new Vue({
  inject: ['onload'.'reload']})/* inject: { onload: { from: 'onload' }, reload: { from: 'reload' } } */
new Vue({
  inject: {
    onload: {
      from: 'onlaunch'.default: 'onload'
    },
    reload: 'reload'}})/* inject: { onload: { from: 'onlaunch', default: 'onload' }, reload: { from: 'reload' } } */
Copy the code

normalizeDirectives

When (vm | componentVM) of the incoming directives in the options to activate the method, the purpose is to format the directives of the instructions. Align the options.directives to {key: {bind: value, update: value}} (value may be a method).

new Vue({
  directives: {
    focus: {
      inserted(el) {
        el.focus()
      }
    }
  }
})
/* directives: { focus: { inserted: el => { el.focus() } } } */
new Vue({
  directives: {
    focus: el= > {
      el.focus()
    }
  }
})
/* directives: { focus: { bind: el => { el.focus() }, update: el => { el.focus() } } } */
Copy the code

extends / mixins

When (vm | componentVM) of the incoming extends in the options/mixins enter the condition judgment, recursive mergeOptions method, (essentially extends/mixins objects options) and (vm | componentVM) to merge.

mergeField

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)
}
Copy the code

This method is extremely complex and essentially combines properties on the constructor with properties on the options. Depending on the key, return the properties on options or create a new object with the values of the properties on Options modeled after the values of the properties on the constructor.

// for example, when key is components
options = {
  components: {
    ChildComponent,
    __proto__: {
      components: {
        KeepAlive,
        Transition,
        TransitionGroup,
      },
    },
  },
}
Copy the code

Step 3

We can see that there is a new attribute in this code, _renderProxy, which is literally called the render proxy object. In fact, it does participate in the rendering process, pointing to it with the internal pointer of the Render method when the Render method is called to get the VNode. But that raises two questions: 1. Why have a render proxy object? 2. Why do development and production environments use different rendering proxy objects? In fact, it is not complicated, we use the following code to solve the problem:

Vnode = render. Call (vm._renderProxy, vm.$createElement) */
render(h) {
  h('div'.'hello vue')}Copy the code

$createElement = vm.$createElement = vm.$createElement = vm. Therefore, we need to call the internal pointer to the current instance, so that we can use this to get the other properties of the options on the instance. So why do development and production environments use different rendering proxy objects? We can find that in the development environment, we actually call the initProxy method to create the rendering Proxy object, which determines whether there is a Proxy object, through the Proxy object to intercept XXX in this. XXX is an illegal attribute, which is conducive to the careless operation during development. This is not necessary in production because we don’t keep errors online.

Step 4

This is again a series of calls, much like the ones in core/global-api/index.js, but don’t ignore the input arguments. In core/global-api/index.js, Vue is passed in, while in this case VM is passed in. This means that while core/global-api/index.js is adding members to the Vue constructor, here it is adding attributes to the VM.

initLifecycle

Initialize a set of properties to bind parent/parent /parent /root to the VM to which the component belongs if the current instance is a component.

vm = {
  __proto__: {
    ...Vue,
  },
  _uid,
  _isVue,
  $options,
  _renderProxy,
  _self,
  /* new add start */
  $parent,
  $root,
  $children: [].$refs: {},
  _watcher: null._inactive: null._directInactive: false._isMounted: false._isDestroyed: false._isBeingDestroyed: false./* new add end */
}
Copy the code

initEvents

Initializes the event center and rebinds the parent component’s event listeners if the current instance is a component.

vm = {
  __proto__: {
    ...Vue,
  },
  _uid,
  _isVue,
  $options,
  _renderProxy,
  _self,
  $parent,
  $root,
  $children: [].$refs: {},
  _watcher: null._inactive: null._directInactive: false._isMounted: false._isDestroyed: false._isBeingDestroyed: false./* new add start */
  _events: {},
  _hasHookEvent: {},
  /* new add end */
}
Copy the code

initRender

Initialize the properties and methods that store and generate the virtual DOM. If the current instance is not a component, add $attrs / $Listeners to the instance as an empty object. If the current instance is a component, attrs on the parent component’s virtual DOM is added responsively to its own instance’s $attrs attribute. Add responsiveness to the parent component’s event listeners and place them on the $Listeners property of its own instance.

vm = {
  __proto__: {
    ...Vue,
  },
  _uid,
  _isVue,
  $options,
  _renderProxy,
  _self,
  $parent,
  $root,
  $children: [].$refs: {},
  _watcher: null._inactive: null._directInactive: false._isMounted: false._isDestroyed: false._isBeingDestroyed: false._events: {},
  _hasHookEvent: {},
  /* new add start */
  $vnode: null._vnode: null,
  $slots,
  $scopedSlots,
  _c() {},
  $createElement() {},
  $attrs,
  $listeners,
  /* new add end */
}
Copy the code

beforeCreate

Call the beforeCreate life cycle.

initInjections

Due to the step 2 – > resolveConstructorOptions – > mergeOptions – > normalizeInject this process in the instance of the $options mount the inject this property, This method does not need to add, but simply adds responsivity to the inject object.

vm = {
  __proto__: {
    ...Vue,
  },
  _uid,
  _isVue,
  $options: {
    /* update start */
    inject: {}
    /* update end */
  },
  _renderProxy,
  _self,
  $parent,
  $root,
  $children: [].$refs: {},
  _watcher: null._inactive: null._directInactive: false._isMounted: false._isDestroyed: false._isBeingDestroyed: false._events: {},
  _hasHookEvent: {},
  $vnode: null._vnode: null,
  $slots,
  $scopedSlots,
  _c() {},
  $createElement() {},
  $attrs,
  $listeners,
}
Copy the code

initState

This step is certainly familiar, as long as you have seen the Vue source code parsing, responsive principle, such as video should all understand, here is the network transmission of Vue constructor content. Initialize props/methods/data/computed/watch.

vm = {
  __proto__: {
    ...Vue,
  },
  _uid,
  _isVue,
  $options: {
    inject: {}
  },
  _renderProxy,
  _self,
  $parent,
  $root,
  $children: [].$refs: {},
  _watcher: null._inactive: null._directInactive: false._isMounted: false._isDestroyed: false._isBeingDestroyed: false._events: {},
  _hasHookEvent: {},
  $vnode: null._vnode: null,
  $slots,
  $scopedSlots,
  _c() {},
  $createElement() {},
  $attrs,
  $listeners,
  /* new add start */
  _watchers,
  _props, // if options has props. options.methods,// If options has methods. options.data,// If there is data on options
  _data: {
    ...options.data
  }, // If there is data on options. options.computed,// If options has computed
  /* new add end */
}
Copy the code

initProvide

vm = {
  __proto__: {
    ...Vue,
  },
  _uid,
  _isVue,
  $options: {
    inject: {}
  },
  _renderProxy,
  _self,
  $parent,
  $root,
  $children: [].$refs: {},
  _watcher: null._inactive: null._directInactive: false._isMounted: false._isDestroyed: false._isBeingDestroyed: false._events: {},
  _hasHookEvent: {},
  $vnode: null._vnode: null,
  $slots,
  $scopedSlots,
  _c() {},
  $createElement() {},
  $attrs,
  $listeners,
  _watchers: [].// If options has watch, there will be an instance of Watcher
  _props, // if options has props. options.methods,// If options has methods. options.data,// If there is data on options
  _data: {
    ...options.data
  }, // If there is data on options. options.computed,// If options has computed
  /* new add start */
  _provided, // If there is provide on options
  /* new add end */
}
Copy the code

created

Invoke the Created lifecycle.

Step 5

So far, we’ve only done initialization, mostly mounting the so-and-so property, and if we’ve really done anything business-related, it’s probably the initState part, which adds responsiveness to the data, and incidentally does dependency collection when we have calculation properties and listeners. But haven’t we seen any DOM manipulation yet? This.$refs is not available in the beforeCreate and Created lifecycles. The DOM is not generated yet. With that, _init is done, and we’re ready to move on to the next process ———— template compilation -> ゲ specifies the user name.