Before starting, you need to read the source code to have a solid basic skills, but also have patience, can grasp the overall situation, do not buckle details!

First, look at the table of contents

├ ─ ─ build -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- build files related ├ ─ ─ dist -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- build output directory file after ├ ─ ─ examples -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- for example of use of the development of the Vue ├ ─ ─ flow -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- type declaration, Using open source project (Flow) (https://flowtype.org/) ├ ─ ─ package. The json -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the project relies on ├ ─ ─test-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- contains all the test file ├ ─ ─ the SRC -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- this is we should focus on the catalog, Contains the source │ ├ ─ ─ platforms -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- contains platform-dependent code │ │ ├ ─ ─ web -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- contains the package of different building entrance file │ │ | ├──entry-runtime.js ---------------- The entry for the runtime build, output dist/vue.common.js, do not include the compiler to render, So the 'template' option is not supported, and we use vue to export this runtime version by default. Should pay attention to during using │ │ | ├ ─ ─ entry - the runtime - with - compiler. Js - build independent entrance, o dist/vue. Js, It contains a template (template) to render function compiler │ ├ ─ ─ the compiler -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the compiler code storage directory, To compile the template to render function │ │ ├ ─ ─ parser -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to store the template string into its elements of the abstract syntax tree code │ │ ├ ─ ─ codegen -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to deposit from the abstract syntax tree (AST) to generate the render function code │ │ ├ ─ ─ optimizer. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- static analysis tree, Optimization vdom rendering │ ├ ─ ─ the core -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- general storage, platform independent code │ │ ├ ─ ─ the observer -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- response system, Contains data observed at the core of the code │ │ ├ ─ ─ vdom -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains virtual DOM to create (creation) and patching (patching) code │ │ ├ ─ ─ the instance -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- containing Vue constructor design related code │ │ ├ ─ ─ global API -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- contains to Vue constructor mount global method (static method) or attribute code │ │ ├ ─ ─ Components -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains of abstracting the common component of │ ├ ─ ─ server -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains the server rendering (server - side Rendering) related code │ ├ ─ ─ SFC -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains a single file components (. Vue file) parsing logic, Used in vue - the template - the compiler package │ ├ ─ ─ Shared -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- contains the entire code base generic codeCopy the code

What is the constructor of Vue

Use the new operator to call Vue. Vue is a constructor. Now that you know the directory structure, let’s look at the entry file

Open thepackage.json

Let’s see what happens when we run NPM run dev, rollup is also a packaging tool similar to WebPack, according to

TARGET=web-full-dev

To build/config. Js lookup

Open the entry file and find web/entry-runtime-with-compiler.js



Following the search path above, we find the Vue constructor



We define the constructor, introduce the dependency, call the initialization function, and export the Vue

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

export default VueCopy the code

Open these five files and find the corresponding methods. You will see that the purpose of these methods is to mount methods or properties on Vue’s prototype

1. Enter initMixin(Vue) and mount it on Prototype

Vue.prototype._init = function (options) {} 
Copy the code

2. Go to stateMixin(Vue) and mount it on Prototype

Vue.prototype.$data 
Vue.prototype.$props 
Vue.prototype.$set = set 
Vue.prototype.$delete = del 
Vue.prototype.$watch = function() {}Copy the code

3. Go to eventsMixin(Vue) and mount it on Prototype

Vue.prototype.$on 
Vue.prototype.$once 
Vue.prototype.$off 
Vue.prototype.$emit
Copy the code

4. Go to lifecycleMixin(Vue) and mount it on Prototype

Vue.prototype._update 
Vue.prototype.$forceUpdate 
Vue.prototype.$destroy  Copy the code

5. Finally enter renderMixin(Vue) and mount it on Prototype

Vue.prototype.$nextTick 
Vue.prototype._render 
Vue.prototype._o = markOnce 
Vue.prototype._n = toNumber 
Vue.prototype._s = toString 
Vue.prototype._l = renderList 
Vue.prototype._t = renderSlot
Vue.prototype._q = looseEqual 
Vue.prototype._i = looseIndexOf 
Vue.prototype._m = renderStatic 
Vue.prototype._f = resolveFilter 
Vue.prototype._k = checkKeyCodes 
Vue.prototype._b = bindObjectProps Vue.prototype._v = createTextVNode Vue.prototype._e = createEmptyVNode Vue.prototype._u = resolveScopedSlots  Vue.prototype._g =bindObjectListeners
Copy the code

Follow the path above to go to SRC /core/index.js

Introduce dependencies and mount static methods and attributes on Vue

  1. import { initGlobalAPI } from './global-api/index'
    import { isServerRendering } from 'core/util/env'
    
    initGlobalAPI(Vue)
    
    Object.defineProperty(Vue.prototype, '$isServer', {
      get: isServerRendering
    })
    
    Object.defineProperty(Vue.prototype, '$ssrContext', {
      get () {
        /* istanbul ignore next */
        return this.$vnode && this.$vnode.ssrContext
      }
    })
    
    Vue.version = '__VERSION__'
    
    export default VueCopy the code

Go to initGlobalAPI(Vue) and mount the static properties and methods on Vue

Vue.config Vue.util = util
Vue.set = set 
Vue.delete = del 
Vue.nextTick = util.nextTick 
Vue.options = { 
 components: { KeepAlive }, 
 directives: {}, 
 filters: {}, 
 _base: Vue 
} 
Vue.use 
Vue.mixin 
Vue.cid = 0 
Vue.extend 
Vue.component = function(){} 
Vue.directive = function(){} 
Vue.filter = function() {}Copy the code

Then mount

Vue.prototype.$isServer 
Vue.version = '__VERSION__'
Copy the code




Follow the path above to go to Runtime /index.js and install platform-specific tools

Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue. Config. GetTagNamespace = getTagNamespace Vue. Config. IsUnknownElement = isUnknownElement / / installation platform specific instructions and components Vue.options = { components: { KeepAlive, Transition, TransitionGroup }, directives: { model, show }, filters: {}, _base: Vue } Vue.prototype.__patch__ Vue.prototype.$mountCopy the code

Follow the last step of the search path above to web/entry-runtime-with-compiler.js

  1. From the cacheweb-runtime.jsOf the file$mountFunction,const mount = vue.prototype. $mount
  2. Mount it on VuecompileAnd then overlay overlayVue.prototype.$mount

  3. Vue.com compile = compileToFunctions is the templatetemplateCompile to render function.

At this point the entire Vue constructor is restored


Three, through an example to explain the whole process

index.html

<! DOCTYPE html> <html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue.js grid component example</title>
    </head>
  <body>
    <div id="app">
        <ol>
          <li v-for="todo in todos">
            {{ todo.text }}
          </li>
        </ol>
        <Child></Child>
    </div>
  </body>
</html>
Copy the code

grid.js

let Child = {
	data: function() {
	  return {child: 'Hello.'}
	},
	template: '<div>{{child}}</div>'
}
new Vue({
	el: '#app',
	data: {
		todos: [
                    {text: 'learning JavaScript'}, 
                    {text: 'learning Vue'}, 
                    {text: 'The whole Cow project'}]
	},
	components: {'Child': Child}
})Copy the code

Function Vue (options) {this._init(options)}

  • New Vue({// pass the above content}), first goes to vue.prototype._init, the first method that the constructor mounts

Vue.prototype._init = function (options) {
    const vm= this
    vm._uid = uid++
    let startTag, endTag
    vm._isVue = true

    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }

    vm._renderProxy = vm
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) 
    initState(vm)
    initProvide(vm)
    callHook(vm, 'created')
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
Copy the code

The _init() method starts by defining two properties on this object: _uid and _isVue, and then determines if options are defined.

vm.$options= mergeOptions (resolveConstructorOptions (vm) constructor), the options | | {}, vm) mergeOptions mode merge into the options and strategy use Vue. The optionsCopy the code

MergeOptions mode merge into the options and strategy use Vue. Options combined code structure, can be seen through a merger strategy components, directives, filters inherits the global, That’s why globally registered ones can be used anywhere, because every instance inherits from the global ones, so they can be found, right



initLifecycle
initEvents
InitRender, initState
initState
beforeCreate
created,
created
Let’s focus on initState()


initState (vm) {
  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) } }Copy the code

InitData to see Vue’s data response system. Since only data is passed, initData(VM) is executed.

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
      proxy(vm, `_data`, key)
  }
  observe(data, true /* asRootData */)
}
Copy the code

proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}Copy the code

Retrieve the key in data and loop through proxy, or directly through this. Properties access values in data, proxy data on instance objects, So we can access the data via this.todos. Todos, the official data response system observes (data, True /* asRootData */), the data through the object.defineProperty to get,set processing, so that the data response

class Observer {
  constructor(value) {
    this.value = value 
    this.dep = new Dep() 
    this.vmCount = 0 
    def(value, '__ob__', this) 
    if (Array.isArray(value)) {
      const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  walk(obj: Object) {
    const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }
  observeArray(items) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
} 
Copy the code

In the Observer class, we use the walk method to loop through the defineReactive method on the attributes of the data. The defineReactive method is simply to convert the attributes of the data into accessor attributes and recursively observe the data. Otherwise, only the immediate subattributes of data can be observed. This completes our first step in getting notifications from get and set when we modify or retrieve the value of the data property.

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  const dep = new Dep()
  letchildOb = ! shallow && observe(val) Object.defineProperty(obj, key, { enumerable:true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return} val = newVal childOb = ! shallow && observe(newVal) dep.notify() } }) }Copy the code

  1. Let childOb =! Shallow && Observe (val), make a recursive call, get set all data subsets, and respond
  2. In the Observe class, if the property is an array, it will be modified

    if (Array.isArray(value)) {
          const augment = hasProto
            ? protoAugment
            : copyAugment
          augment(value, arrayMethods, arrayKeys)
          this.observeArray(value)
        } else {
          this.walk(value)
        }exportconst arrayMethods = Object.create(arrayProto) ; ['push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
    ]
    .forEach(function (method) {
      const original = arrayProto[method]
      def(arrayMethods, method, functionmutator (... args) { const result = original.apply(this, args) const ob = this.__ob__let inserted
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
      })
    })
    Copy the code

Override the above methods on the array, so that when you do these operations on the array,


When initData(VM) completes, the system is complete. CallHook (VM, ‘created’) triggers the creation, and then goes back to _init(), to vm.$mount(vm.$options.el)

Vue.prototype._init = function (options) {
    const vm= this
    vm._uid = uid++
    let startTag, endTag
    vm._isVue = true

    if (options && options._isComponent) {
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }

    vm._renderProxy = vm
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) 
    initState(vm)
    initProvide(vm)
    callHook(vm, 'created')
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }Copy the code

Enter $mount will first retrieve mount el node, and then determine whether you introduced to render method first, did not find in the template,

In this case, they don’t

GetOuterHTML (EL) serves as the current template

Vue.prototype.$mount = function (
  el,
  hydrating
){
  el = el && query(el)
  const options = this.$options
  if(! options.render) {let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === The '#') {
          template = idToTemplate(template)
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if(process.env.NODE_ENV ! = ='production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile end')
        measure(`${this._name} compile`, 'compile'.'compile end')}}}return mount.call(this, el, hydrating)
}
Copy the code

With the template compileToFunctions, the template is compiled into an AST syntax tree, statically optimized, and finally processed into the render function. In this case, the render function uses with(this) to advance the scope of this. {{todo.text}} So we can use the attribute directly in the template without adding this! _l = renderList; render = renderList; render = renderList; render = renderList

function() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_c('ol', _l((this.todos), function(todo) {
            return _c('li', [_v("\n " + _s(todo.text) + "\n ")])
        })), _v(""), _c('child')], 1)}}Copy the code

  • After generating the render function and then entering the mountComponent,
  • The first call is

    BeforeMount function,

  • _watcher = new Watcher(vm, updateComponent, noop)
  • Finally, callHook(VM, ‘mounted’), and execute Mounted, so that the MOUNTED is already mounted to the DOM before mounted

_watcher = new Watcher(vm, updateComponent, noop)

function mountComponent (
  vm,
  el,
  hydrating
): Component {
  vm.$el = el
  if(! vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount')

  let updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }

  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')}return vm
}
Copy the code

Look at the Watcher code

class Watcher {
  constructor (
    vm,
    expOrFn,
    cb,
    options
  ) {
    this.vm = vm
    vm._watchers.push(this)

    if(options) { this.deep = !! options.deep this.user = !! options.user this.lazy = !! options.lazy this.sync = !! options.sync }else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // forlazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV ! = ='production'
      ? expOrFn.toString()
      : ' '
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    value = this.getter.call(vm, vm)
    return value
  }
  addDep (dep: Dep) {
    const id = dep.id
    if(! this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep)if(! this.depIds.has(id)) { dep.addSub(this) } } }evaluate () {
    this.value = this.get()
    this.dirty = false
  }
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
}
Copy the code

Execute constructor because this.lazy=false; this.value = this.lazy ? undefined : this.get();

Execute the get method pushTarget(this) and add the static property this(the current new Watcher() instance) to the dep.target.

 function pushTarget (_target) { 

 if (Dep.target) targetStack.push(Dep.target) 

 Dep.target = _target

 }

get () {
    pushTarget(this)
    let value
    const vm = this.vm
    value = this.getter.call(vm, vm)
    return value
  }
Copy the code

Then execute this.getter.call(vm, VM)Copy the code
UpdateComponent = () => {vm._update(vm._render(), hydrating)}Copy the code
  1. Start by calling vm._render()

  Vue.prototype._render = function (){
    const vm = this
    const {
      render,
      staticRenderFns,
      _parentVnode
    } = vm.$options

    let vnode = render.call(vm._renderProxy, vm.$createElement)
    return vnode
  }
Copy the code

Start to execute the previously compiled render function, in the execution of the function, by obtaining todos attribute, trigger the corresponding

function() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_c('ol', _l((this.todos), function(todo) {
            return _c('li', [_v("\n " + _s(todo.text) + "\n ")])
        })), _v(""), _c('child')], 1)}}Copy the code

Dep. Target already has static properties in the Watcher instance

So the corresponding DEP instance collects the corresponding Watcher instance

Copy the code

After executing, return to the vnode,




updateComponent = () => { vm._update(vm._render(), } where vm._render() executes the render function and returns vnode as an argument. Next, we execute vm._update. This is the first rendering, so we execute vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false,
        vm.$options._parentElm,
        vm.$options._refElm
      )Copy the code

Vue.prototype._update = function (vnode, hydrating) {
    const vm: Component = this
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode
    if(! prevVnode) { vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false,
        vm.$options._parentElm,
        vm.$options._refElm
      )
      vm.$options._parentElm = vm.$options._refElm = null
    } else {
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el}}Copy the code

vm.__patch__( vm.$el, vnode, hydrating, false, vm.$options._parentElm, $options._refElm) according to the vNode tree, create the corresponding element, inserted into the parent node, through the vnode recursion loop to create all child nodes inserted into the parent node Where a Child element is a component, such as a Child in this case, an instance of the corresponding VueComponent is created, executing the same process as new Vue()

If there is no prevVnode, this is the first render. Create the real DOM directly. If you already have a prevVnode indicating that it is not the first render, then the patch algorithm is used to perform the necessary DOM operations. This is Vue’s logic for updating the DOM. It’s just that we didn’t implement the virtual DOM internally.

When the value of the attribute is changed, the set method of the corresponding attribute will be triggered. Because GET was triggered during the previous execution of render, and the corresponding Watcher was collected, the set will be triggered when the value is changed, informing the previously collected Watcher instance to execute, and recalculating the render method for patch operation

Finally steal a picture:




I have been writing for a long time, but I really can’t go on writing any more. When I have good language, I will sort it out again.