深入Vue.js從原始碼開始(二)
從入口開始
我們之前提到過 Vue.js 構建過程,在 web 應用下,我們來分析 Runtime + Compiler 構建出來的 Vue.js,它的入口是 src/platforms/web/entry-runtime-with-compiler.js:
摘選entry-runtime-with-compiler.js
import config from 'core/config' import { warn, cached } from 'core/util/index' import { mark, measure } from 'core/util/perf' //主角在這裡 import Vue from './runtime/index' import { query } from './util/index' import { compileToFunctions } from './compiler/index' import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'
Vue 的入口
在這個入口 JS 的上方我們可以找到 Vue 的來源:import Vue from './runtime/index',我們先來看一下這塊兒的實現,它定義在 src/platforms/web/runtime/index.js 中:
import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
這裡關鍵的程式碼是 import Vue from 'core/index'是真正初始化 Vue 的地方[/src/core/index.js]
import Vue from './instance/index' import { initGlobalAPI } from './global-api/index'
這裡有 2 處關鍵的程式碼,import Vue from './instance/index'(./指的是當前目錄) 和 initGlobalAPI(Vue),初始化全域性 Vue API,我們先來看第一部分,在 src/core/instance/index.js 中:
Vue 的定義
import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' 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) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
1
在這裡,我們終於看到了 Vue 的廬山真面目,它實際上就是一個用 Function 實現的類,我們只能通過 new Vue 去例項化它
為何 Vue 不用 ES6 的 Class 去實現呢?我們往後看這裡有很多 xxxMixin 的函式呼叫,並把 Vue 當引數傳入,它們的功能都是給 Vue 的 prototype 上擴充套件一些方法(這裡具體的細節會在之後的文章介紹,這裡不展開),Vue 按功能把這些擴充套件分散到多個模組中去實現,而不是在一個模組裡實現所有,這種方式是用 Class 難以實現的。這麼做的好處是非常方便程式碼的維護和管理,這種程式設計技巧也非常值得我們去學習。
initGlobalAPI
Vue.js 在整個初始化過程中,除了給它的原型 prototype 上擴充套件方法,還會給 Vue 這個物件本身擴充套件全域性的靜態方法,它的定義在 src/core/global-api/index.js 中:
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
這裡就是在 Vue 上擴充套件的一些全域性方法的定義,Vue 官網中關於全域性 API 都可以在這裡找到,這裡不會介紹細節,會在之後的章節我們具體介紹到某個 API 的時候會詳細介紹。有一點要注意的是,Vue.util 暴露的方法最好不要依賴,因為它可能經常會發生變化,是不穩定的。
資料驅動
Vue.js 一個核心思想是資料驅動。所謂資料驅動,是指檢視是由資料驅動生成的,我們對檢視的修改,不會直接操作 DOM,而是通過修改資料。它相比我們傳統的前端開發,如使用 jQuery 等前端庫直接修改 DOM,大大簡化了程式碼量。特別是當互動複雜的時候,只關心資料的修改會讓程式碼的邏輯變的非常清晰,因為 DOM 變成了資料的對映,我們所有的邏輯都是對資料的修改,而不用碰觸 DOM,這樣的程式碼非常利於維護。
在 Vue.js 中我們可以採用簡潔的模板語法來宣告式的將資料渲染為 DOM:
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
最終它會在頁面上渲染出 Hello Vue。接下來,我們會從原始碼角度來分析 Vue 是如何實現的,分析過程會以主線程式碼為主,重要的分支邏輯會放在之後單獨分析。資料驅動還有一部分是資料更新驅動檢視變化,這一塊內容我們也會在之後的章節分析,這一章我們的目標是弄清楚模板和資料如何渲染成最終的 DOM。