大前端進階-讀懂vuejs源碼1

此文章適合準備第一次閱讀vuejs源碼的童鞋,因爲vuejs的源碼很是多,並且分佈在各個文件夾中,所以想要讀懂源碼,須要理清整個框架的脈絡。此文章就是從編譯入口出發,找到源碼中的關鍵點。html

準備工做

打包源碼

瀏覽器調試比單純的閱讀源碼更有效率,那麼如何爲vuejs添加sourceMap?vue

  1. fork vue源碼倉庫到本身的github倉庫中,這樣,能夠隨便添加註釋和修改。
  2. 下載項目,打開package.json文件,找到文件中:

image.png

  1. vuejs使用rollup打包,在dev命令最後添加--sourcemap
  2. 執行npm run dev進行打包,生成帶sourcemap的vue文件。
    image.png
  3. 找到examples文件夾,隨便找一個用例,將vue文件的引用地址改成新打包生成的文件。

image.png

  1. 用瀏覽器打開html文件,打開控制檯,就能夠看到源碼。

瞭解打包文件

在命令行中執行npm run build,會打包全部版本的vue文件,打包結果以下:
image.pngnode

其中:webpack

  • common 表示符合commonjs規範的文件。
  • ems 表示符合ES Module規範的文件。
  • dev 表示文件內容未壓縮,是可讀的。
  • prod 表示文件內容是壓縮過的。
  • runtime

表示運行時版本,不包含模版編譯功能。也就是在聲明組件的時候沒法編譯template模版,只能使用render函數。 vue-cli構建的項目中,因爲打包的時候會將template模版編譯成render函數,因此其打包後引用的vue版本爲運行時版本。git

在vue-cli建立的項目中執行vue inspect > out.js。能夠將全部的webpack配置輸出到out.js文件中,查看其中的resolve配置能夠看到其打包的vue版本:github

resolve: {
    alias: {
      vue$: 'vue/dist/vue.runtime.esm.js'
    }
}
  • 未加common,es

表示是umd規範文件,此文件能夠支持commonjs,ES Module,AMD,或者直接經過window.Vue方式引用。web

  • 未加rentime

表示完整版本,包含運行時和編譯器。總體代碼比運行時版本多。vue-cli

入口文件

vuejs項目的打包入口文件,能夠看成是源碼閱讀的入口文件。npm

打包的時候執行的是以下命令:json

rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap

其中scripts/config.js爲rollup配置文件所在路徑,rollup配置文件要求導出一個對象,其中input屬性指定打包入口文件。

scripts/config.js

在該文件的最後:

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

因爲執行的打包命令中包含--environment TARGET:web-full-dev,因此此時process.env.TARGET值爲web-full-dev,也就是經過
genConfig獲取配置並導出。

在getConfig方法中,經過const opts = builds[name]獲取內置的配置,其中name爲web-full-dev。經過opts.entry指定input屬性值,其最終值爲platforms/web/entry-runtime-with-compiler.js

platforms文件加中存放的是和平臺相關的代碼,其中web文件夾是和web相關的代碼,weex文件夾是和weex相關的代碼。

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

此文件的功能並不複雜,只是修改了Vue原型上的$mount方法和添加靜態方法compile

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
   // 具體邏輯
}
// 靜態方法compile
Vue.compile = compileToFunctions

在原有$mount方法的基礎上添加判斷,當vue組件沒有定義render時,判斷是否傳入了templete,若是傳了,就將其編譯成render。具體邏輯可精簡爲:

const options = this.$options
// 若是沒有傳入render
if (!options.render) {
    // 獲取template
    let template = options.template

    // .... 此處包含template的各類狀況判斷

    if (template) {
        // 編譯template爲render函數
        const { render, staticRenderFns } = compileToFunctions(template, {
            outputSourceRange: process.env.NODE_ENV !== 'production',
            shouldDecodeNewlines,
            shouldDecodeNewlinesForHref,
            delimiters: options.delimiters,
            comments: options.comments
        }, this)
        // 在options上面添加render函數
        options.render = render
        // 靜態render函數,用於優化Dom渲染過程
        options.staticRenderFns = staticRenderFns
    }
}
// 調用原有的mount方法
return mount.call(this, el, hydrating)
此處 compileToFunctions是模版編譯的入口,等到後面編譯部分再繼續。

platforms/web/runtime/index.js

entry-runtime-with-compiler.js文件中的Vue類引入自platforms/web/runtime/index.js這個文件,此文件爲Vue類添加了web平臺特有功能,如Dom操做。

此文件能夠分爲三大塊:

  • 擴展config
// 添加web平臺特有的一些輔助方法
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag // 判斷是不是保留tag,如input
Vue.config.isReservedAttr = isReservedAttr // 是不是保留屬性
Vue.config.getTagNamespace = getTagNamespace // 獲取元素的命名空間
Vue.config.isUnknownElement = isUnknownElement

此處添加的方法大部分是Vue內部使用,平時工做上幾乎用不到,因此再也不詳解。

  • 添加全局內置組件和指令
// 添加web平臺相關的全局內置組件和指令
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

此處添加的組件有: transitiontransition-group
此處添加的指令有: v-modelv-show

  • 添加原型方法

添加了兩個關鍵方法:Dom掛載和Dom更新。

// 添加虛擬Dom更新操做
Vue.prototype.__patch__ = inBrowser ? patch : noop

// 添加掛載方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  // 渲染虛擬Dom
  return mountComponent(this, el, hydrating)
}
mountComponent是Vnodes渲染的入口方法,後續會詳細降到Vnodes渲染過程。

core/index.js

platforms/web/runtime/index.js中的Vue類引入自core/index.js文件。

core文件夾包含了vue核心代碼,其與平臺沒有任何關係。

此文件只包含一個關鍵代碼:

// 爲Vue類添加全局靜態方法如Vue.extend, Vue.component等。
initGlobalAPI(Vue)
// ... 剩餘的是和ssr服務端渲染相關的全局屬性,此處省略。

initGlobalAPI

定義在core/global-api/index.js中:

export function initGlobalAPI(Vue: GlobalAPI) {
    // 定義config
    Object.defineProperty(Vue, 'config', configDef)

    // 幫助函數,不要直接使用,vuejs不保證會正確執行
    Vue.util = {
        warn,
        extend,
        mergeOptions,
        defineReactive
    }

    // 設置全局的set,delete和nextTick
    Vue.set = set
    Vue.delete = del
    Vue.nextTick = nextTick

    // 添加observable方法
    Vue.observable = <T>(obj: T): T => {
        observe(obj)
      return obj
    }
    // 建立全局的options對象
    Vue.options = Object.create(null)
    // 初始化options中的components,directives,filters三個屬性。
    ASSET_TYPES.forEach(type => {
            Vue.options[type + 's'] = Object.create(null)
        })
    // Vue.use
    initUse(Vue)
    // Vue.mixin
    initMixin(Vue)
    // Vue.extend
    initExtend(Vue)
    // Vue.component, Vue.directive, Vue.filter
    initAssetRegisters(Vue)
  }

core/instance/index.js

core/index.js中的Vue類引入自core/instance/index.js文件,此文件定義了Vue的構造函數和實例方法。

定義構造函數

function Vue (options) {
  // 確保Vue不會被看成函數調用
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 執行_init方法,此方法在initMixin中定義
  this._init(options)
}

聲明實例屬性方法

// 添加實例方法屬性
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
此處體現了vuejs針對代碼邏輯文件的劃分,將不一樣的功能劃分到不一樣的文件中。
  • initMixin

定義在core/instance/init.js文件中。

export function initMixin(Vue: Class<Component>) {
    Vue.prototype._init = function (options?: Object) {
        // ... 省略
    }
}

該文件主要爲Vue實例添加_init方法,當建立Vue實例的時候,此方法會被當即調用。

下一部分Vue初始化過程的入口就是此方法。

  • stateMixin

定義在core/instance/state.js中:

export function stateMixin(Vue: Class<Component>) {
    // 定義$data屬性
    Object.defineProperty(Vue.prototype, '$data', dataDef)
    // 定義$props屬性
    Object.defineProperty(Vue.prototype, '$props', propsDef)

    // 定義get,set方法
    Vue.prototype.$set = set
    Vue.prototype.$delete = del

    // 定義watch方法
    Vue.prototype.$watch = function (
        expOrFn: string | Function,
        cb: any,
        options?: Object
    ): Function {
        // ... 省略,在響應式源碼部分詳解
    }
}
  • eventsMixin

定義在core/instance/events.js中:

export function eventsMixin(Vue: Class<Component>) {
    // 定義$on
    Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
        // ... 省略
    }
    // 定義$once
    Vue.prototype.$once = function (event: string, fn: Function): Component {
        // ... 省略
    }
    // 定義$off
    Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
        // ... 省略
    }
    // 定義$emit
    Vue.prototype.$emit = function (event: string): Component {
        // ... 省略
    }
}

這裏定義的事件註冊方法邏輯很近似,都是將註冊的方法存儲在Vue實例的_events屬性中。

_events屬性是在_init方法執行的過程當中初始化的。

  • lifecycleMixin

定義在core/instance/lifecycle.js中:

export function lifecycleMixin(Vue: Class<Component>) {
    // 定義_update方法
    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        // 調用__patch__執行更新渲染
        if (!prevVnode) {
            // initial render
            vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
        } else {
            // updates
            vm.$el = vm.__patch__(prevVnode, vnode)
        }

    }

    // 定義強制更新方法
    Vue.prototype.$forceUpdate = function () {

    }

    // 定義銷燬方法
    Vue.prototype.$destroy = function () {

    }
}

此文件中定義的_update方法是響應式過程當中的關鍵一環,做爲觀察者Watcher的回調函數,當vm的數據放生變化的時候,會被調用。

  • renderMixin

定義在core/instance/render.js中:

export function renderMixin(Vue: Class<Component>) {
    // 定義nextTick方法
    Vue.prototype.$nextTick = function (fn: Function) {
        // 。。。省略
    }
    Vue.prototype._render = function (): VNode {
        // 內部調用options中的render方法,生成虛擬Dom
    }
}

_render方法將配合_update,_update更新時對比的是虛擬Dom,而_render方法就是用於生成虛擬Dom。

總結

至此,vuejs整個項目的web平臺文件關係理順,以下:

core/instance/index.js: 聲明Vue構造函數和實例方法屬性。
core/index.js:爲Vue添加靜態方法。
platforms/web/runtime/index.js:針對web平臺,添加Dom渲染和加載方法。
platforms/web/entry-runtime-with-compiler.js: 擴展Vue模版編譯能力,若是是不帶編譯的運行時版本就無需針對template進行處理。

相關文章
相關標籤/搜索