preface

As a front end rookie for 4 years, I thought it was time to settle down. After thinking about it, I decided to start with the framework I was using, Vue, so I decided to spend 1-2 months reading the source code in detail, despite having read it a few times before.

The target

Figure out the functions of each module and sort out the main process of Vue initialization.

Preparation before Reading

This reading is based on V2.6.14

1. Github downloads Vue source code

2. The installation package

3. Create the test HTML file

Directory Structure description

. ├ ─ ─ BACKERS. Md ├ ─ ─ LICENSE ├ ─ ─ the README. Md ├ ─ ─ benchmarks / / performance test │ ├ ─ ─ big - table │ ├ ─ ─ dbmon │ ├ ─ ─ the reorder - list │ ├ ─ ─ SSR │ ├ ─ ─ SVG │ └ ─ ─ uptime ├ ─ ─ dist/directory/compiled ├ ─ ─ examples / / 🌰 │ ├ ─ ─ commits │ ├ ─ ─ elastic - the header │ ├ ─ ─ firebase │ ├ ─ ─ Grid │ ├── MarkDown │ ├── Modal │ ├── moving-exercises │ ├─ SVG │ ├─ test.html │ ├─ Todomvc │ ├─ exercises _ _ _ _ _ _ _ _ _ _ _ _ _ _ ├ ─ ─ the flow / / flow statement │ ├ ─ ─ compiler. Js │ ├ ─ ─ component. The js │ ├ ─ ─ global - API. Js │ ├ ─ ─ modules. Js │ ├ ─ ─ options. Js │ ├ ─ ─ SSR. Js │ ├─ ├─ ├─ download.exe │ ├─ download.exe │ ├─ download.exe ├─ UE-Server-renderer │ ├─ Ue-template-Compiler │ ├─ weex-template-Compiler │ ├─ Weex-Vue-Framework ├─ │ ├─ alias.js │ ├─ config.js │ ├─ featuresflags.js │ ├── Build Gen - release - note. Js │ ├ ─ ─ the get - weex - version. Js │ ├ ─ ─ git - hooks │ ├ ─ ─ release - weex. Sh │ ├ ─ ─ the sh │ └ ─ ─ ├─ SRC // Verify-commit-mg.js ├─ SRC // Key to explain the module │ ├ ─ ─ the compiler │ ├ ─ ─ the core │ ├ ─ ─ platforms │ ├ ─ ─ server │ ├ ─ ─ SFC │ └ ─ ─ Shared ├ ─ ─ the test / / test module │ ├ ─ ─ e2e │ ├ ─ ─ Helpers │ ├ ─ ─ SSR │ ├ ─ ─ unit │ └ ─ ─ weex ├ ─ ─ types / / ts statement │ ├ ─ ─ the index, which s │ ├ ─ ─ the options, which s │ ├ ─ ─ the plugin, which s │ ├ ─ ─ The test │ ├ ─ ─ tsconfig. Json │ ├ ─ ─ typings. Json │ ├ ─ ─ umd. Which s │ ├ ─ ─ vnode. Which s │ └ ─ ─ vue. Which s └ ─ ─ yarn. The lockCopy the code

For better debugging, we create a new test.html under test and introduce dist/vue.js

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
    <script src=". /.. /dist/vue.js"></script>
Copy the code

The preparation work is basically finished, now start to look at the main process.

Initialize the

SRC/core/index. Js,

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
Copy the code

Vue imports from./instance/index

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '.. /util/index'

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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

Copy the code

Initialization, mainly this _init method.

It defined in the instance/init, that includes four methods initMixin, initInternalComponent, resolveConstructorOptions, resolveModifiedOptions

initMixin

The main function of this function is to initialize something: what is specified in the comment;

let uid = 0
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options? :Object) {
    const vm: Component = this
    // a uid
    // Each vUE instance generates a uid, which is incremented
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    // Merge options subcomponents and root components merge configuration items differently
    // Optimize merging of options for subcomponents
    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 */
    // Set the proxy to delegate attributes on the VM instance to vm._renderProxy
    if(process.env.NODE_ENV ! = ='production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    $parent, $root, $children, $refs
    initLifecycle(vm)
    // Initialize the event
    initEvents(vm)
    
    // initialize $slots, $scopedSlots, mount $createElement
    $listeners set $attrs, $listeners to responsiveness
    initRender(vm)
    
    / / call beforeCreate
    callHook(vm, 'beforeCreate')
    
    // Initialize inject before data/props is initialized, and at initialization, switch to non-responsive state
    // Inject is non-responsive
    initInjections(vm) // resolve injections before data/props
    
    // Initialize data props, data, method, computed, and so on
    initState(vm)
    
    // initialize provide
    initProvide(vm) // resolve provide after data/props
    
    // Calls the created hook
    callHook(vm, 'created')

    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // Attach to el
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

resolveConstructorOptions

This function merges options

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // If there is a parent class, recurse
  if (Ctor.super) {
    // Recursively merge options, if base class
    const superOptions = resolveConstructorOptions(Ctor.super)
    // If options change, you need to merge them again
    const cachedSuperOptions = Ctor.superOptions
    if(superOptions ! == cachedSuperOptions) {// super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      // If options change, you need to merge them again
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      // Then remerge
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

Copy the code

From the debugger, we can see the merged $options as shown below:

Merges the options passed in at initialization with some properties of the vue constructor

resolveModifiedOptions

The function of this function is to reassign if the option of super changes by traversing it

function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  const latest = Ctor.options
  const sealed = Ctor.sealedOptions
  for (const key in latest) {
    if(latest[key] ! == sealed[key]) {if(! modified) modified = {} modified[key] = latest[key] } }return modified
}

Copy the code

$mount

Debug through breakpoints. We know that $mount method definitions in platforms/web/runtime/index. Js

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)} $mount only does one thing, executes`mountComponent`

Copy the code

mountComponent

UpdateComponent does a few things:

1. Run the beforeMount lifecycle function.

2. Mount vDOM and convert it to DOM.

3. Call _update;

4. Create the component’s Watcher

5. Run the Mounted life cycle function.

export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  
  if(! vm.$options.render) { vm.$options.render = createEmptyVNodeif(process.env.NODE_ENV ! = ='production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0)! = =The '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    updateComponent = () = > {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } 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

conclusion

The main trend of new Vue() actually looks relatively simple.

1. Initialize by calling _init

$parent, $root, $children, $refs

3. Initialize some custom events, including the comparison and update of old and new events

$slots, $scopedSlots, $createElement

5. Call beforeCreate

6. Initialize the Inject before data/props is initialized and switch to the non-responsive state during initialization

Initialize data props, data, Method, computed, and so on

8. Initialize provide

9. Call the created hook function

If there is an EL, mount $mount automatically. If there is no el, call $mount manually

11.$mount Execute mountComponent;

11.1 Run the 'beforeMount' life cycle function; 11.2 Mount vDOM and convert it to DOM. 11.3 Calling the '_update' method; 11.5 Running the 'Mounted' lifecycle FunctionCopy the code

At this point, the initialization to rendering process is almost complete, but we’ll talk about the details later

Next video

Response type principle, as well as realization and simple Vue

The last

Look at it seems not so difficult, humble for attention, for praise, I hope you pay attention to my public number front engineer, we progress together, write bad, spray light ~