從new Vue()看源碼流程

文章概述

此文章主要目的是從一個最簡單的demo開始,從new Vue開始,跟蹤Vue源碼中的代碼行進流程。
對主要的初始化流程有更清晰的理解。爲後續的深刻理解打好基礎,避免迷茫。html

搭配

能夠搭配這篇文章一塊兒食用 vuejs全局運行機制vue

demo

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

簡要流程圖

image

源碼詳細流程

src/core/index.js

這裏主要是導出真正的Vue函數,初始化全局APInode

import Vue from './instance/index'

initGlobalAPI(Vue)

export default Vue

src/core/instance/index.js

實例化的時候調用this._init方法git

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元素。github

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函數開始執行真正的掛載流程。web

// 這個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開始掛載組件segmentfault

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()方法,來看這兩個重要方法app

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函數。 因此這一步理解爲返回模板對應的vnodedom

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__方法ide

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...

相關文章
相關標籤/搜索