乐趣区

从new-Vue看源码流程

demo

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

简要流程图

源码详细流程

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
}
  1. 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…

退出移动版