文章概述

此文章主要目的是从一个最简单的demo开始,从new Vue开始,跟踪Vue源码中的代码行进流程。
对主要的初始化流程有更清晰的理解。为后续的深入理解打好基础,避免迷茫。

搭配

可以搭配这篇文章一起食用 vuejs全局运行机制

demo

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

简要流程图

image

源码详细流程

src/core/index.js

这里主要是导出真正的Vue函数,初始化全局API

import Vue from './instance/index'

initGlobalAPI(Vue)

export default Vue

src/core/instance/index.js

实例化的时候调用this._init方法


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

src/core/instance/init.js

这一块主要是初始化一系列的属性和方法。然后调用$mount方法挂载el元素。

Vue.prototype._init = function (options?: Object) {
     ...
     vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        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) {
      // 挂载el属性
      vm.$mount(vm.$options.el)
    }
}

src/platforms/web/entry-runtime-with-compiler.js

两个点,调用compileToFunctions函数生成render函数备用, 调用mount函数开始执行真正的挂载流程。

// 这个mount指向下面这个文件的Vue.prototype.$mount
const mount = Vue.prototype.$mount

// 在这里主要是使用compileToFunctions来编译模板,生成render函数,以供后用。
// 主体的mount函数还是使用原来的$mount方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
    el = el && query(el)
    ...
    if (!options.render) {
        ...
      template = getOuterHTML(el)
        // 调用compileToFunctions函数,得到render, staticRenderFns
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      // 更新options属性
      options.render = render
      options.staticRenderFns = staticRenderFns
        
    }
    
    // 调用原本的mount方法
    return mount.call(this, el, hydrating)
}

// 对外导出的 Vue(这个vue指向下面这个文件)
export default Vue

src/platforms/web/runtime/index.js

一个点,执行mountComponent开始挂载组件

import { mountComponent } from 'core/instance/lifecycle'

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
export default Vue

src/core/instance/lifecycle.js

然后会执行到mountComponent函数。在实例化Watcher时,会执行vm._render(), vm._update()方法,来看这两个重要方法

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
    ...
    let updateComponent
    ...
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
    
    new Watcher(vm, updateComponent, noop, {
        before () {
          if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, 'beforeUpdate')
          }
        }
     }, true /* isRenderWatcher */)
}

src/core/instance/render.js

这里的$options.render就是第一步调用compileToFunctions的模板编译后的render函数。 所以这一步理解为返回模板对应的vnode

Vue.prototype._render = function (): VNode {
    ...
    const { render, _parentVnode } = vm.$options
    ...
    vnode = render.call(vm._renderProxy, vm.$createElement)
    ...
    return vnode
}

src/core/instance/lifecycle.js

_update方法(这一步的作用是把VNode渲染成真实的DOM)。这一步主要调用 vm.__patch__方法

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    ...
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    ...
}

src/platforms/web/runtime/index.js


// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

src/platforms/web/runtime/patch.js


export const patch: Function = createPatchFunction({ nodeOps, modules })

src/core/vdom/patch.js

这个函数超级复杂。作用是依赖vnode递归创建了一个完整的DOM树并插到Body上。

export function createPatchFunction (backend) {
    ...
    
    return function patch (oldVnode, vnode, hydrating, removeOnly)
    
    }
}

简要流程:

template => vnode tree => DOM tree => 将DOM tree插到body下

核心的两个方法

_render负责将template转为vnode tree, _update负责将vnode tree转为DOM tree, 并且插到body下

参考链接

https://ustbhuangyi.github.io...


littlelightss
406 声望14 粉丝