Vue.js - 基礎原理
來自: http://segmentfault.com/a/1190000004221918
前言
本文將從官網的例子講起,一步步對Vue.js的實現做講解說明。
請注意下列事項:
-
本文適合于使用了Vue.js一段時間,想進一步深入和對其實現原理有興趣的人。
-
本文基于 1.0.13 版本。
-
本文較長,包含了部分vue.js源代碼,刪除了所有的警告信息(對本文來說沒有任何的作用,而且影響閱讀)和部分注釋。
-
文中對代碼的注釋做了部分翻譯,以供閱讀。
-
作者是不寫冒號主義者,可能會引起部分人的蛋疼。
-
需要有ES6的知識作為基礎。
-
需要理解原型。
-
文是我邊看邊寫的。
-
水平有限,如有錯誤請指出。
例子
下面是官網的例子,可以在上面直接看到執行的情況。接下來我們將會以這段代碼做開頭。
var demo = new Vue({
el: '#demo',
data: {
message: 'Hello Vue.js!'
}
})
<div id="demo">
<p>{{message}}</p>
<input v-model="message">
</div>
首先
我們可以從上面的代碼得到一些信息。從js來看,我們是new了一個Vue實例,提供了一個el和一個data。el是為了和html做映射,data則是本身涵蓋的數據。再看看html,id用于映射,{{message}}是數據的顯示,input的值作為message的model。
這樣html和js根據一個id做出了映射關系,并將data和html做了雙向的關聯,這就是典型的MVVM模式。即Modle、View和ViewModel。
現在
我們需要看看這之中的執行過程到底發生了什么。
對此,我們需要查看源代碼。因為項目的組織是基于ES6的模塊方式組織的,所以尋找和閱讀并不是很困難。讓我們先找到這個入口。
在 vue/src 文件夾里我們可以很容易的找到 index.js 文件,看起來這個就是入口。
import Vue from './instance/vue'
import directives from './directives/public/index'
import elementDirectives from './directives/element/index'
import filters from './filters/index'
import { inBrowser } from './util/index'
Vue.version = '1.0.13'
/**
- Vue and every constructor that extends Vue has an
- associated options object, which can be accessed during
- compilation steps as
this.constructor.options. * - 每一個Vue實例都會有下列options */
Vue.options = { directives, elementDirectives, filters, transitions: {}, components: {}, partials: {}, replace: true }
export default Vue</pre>
好吧,似乎 vue/src/instance/vue.js 才是真正的本體。
// 構造函數
function Vue (options) {
this._init(options)
}
// install internals
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
miscMixin(Vue)
// install APIs
globalAPI(Vue)
dataAPI(Vue)
domAPI(Vue)
eventsAPI(Vue)
lifecycleAPI(Vue)
export default Vue</pre>
我把import部分和部分注釋刪掉了,影響閱讀。額,這個Vue構造函數里似乎只是執行了一個_init函數用來處理options,所以我們要接著找。
這個我們簡單的全局搜一下就可以了,然后定位到 vue/src/instance/internal/init.js 。一看這個代碼,我們就知道重點來了。
這個文件export了一個函數,看看 vue/src/instance/vue.js 我們就能知道其實就是initMixin這個函數。所以init就是在這個函數里被賦值的,我們直接看代碼,這樣會比較直觀。
// mergeOptions這個函數,看名字是用來做options合并的
import { mergeOptions } from '../../util/index'
// uid?我們先不探討
let uid = 0
// 被作為initMixin調用
export default function (Vue) {
// 這就是我們要找的東西 GJ
Vue.prototype._init = function (options) {
// 檢查一下options是不是為空
options = options || {}
// 各種options,這里是各個默認值
this.$el = null
this.$parent = options.parent
this.$root = this.$parent
? this.$parent.$root
: this
this.$children = []
this.$refs = {} // child vm references
this.$els = {} // element references
this._watchers = [] // all watchers as an array
this._directives = [] // all directives
// 哦,是Vue的實例個數
this._uid = uid++
// a flag to avoid this being observed
this._isVue = true
// events bookkeeping
this._events = {} // registered callbacks
this._eventsCount = {} // for $broadcast optimization
// fragment instance properties
this._isFragment = false
this._fragment = // @type {DocumentFragment}
this._fragmentStart = // @type {Text|Comment}
this._fragmentEnd = null // @type {Text|Comment}
// lifecycle state
this._isCompiled =
this._isDestroyed =
this._isReady =
this._isAttached =
this._isBeingDestroyed = false
this._unlinkFn = null
// context:
// if this is a transcluded component, context
// will be the common parent vm of this instance
// and its host.
this._context = options._context || this.$parent
// scope:
// if this is inside an inline v-for, the scope
// will be the intermediate scope created for this
// repeat fragment. this is used for linking props
// and container directives.
this._scope = options._scope
// fragment:
// if this instance is compiled inside a Fragment, it
// needs to reigster itself as a child of that fragment
// for attach/detach to work properly.
this._frag = options._frag
if (this._frag) {
this._frag.children.push(this)
}
// push self into parent / transclusion host
if (this.$parent) {
this.$parent.$children.push(this)
}
// merge options.
options = this.$options = mergeOptions(
this.constructor.options,
options,
this
)
// set ref
this._updateRef()
// initialize data as empty object.
// it will be filled up in _initScope().
this._data = {}
// call init hook
this._callHook('init')
// initialize data observation and scope inheritance.
this._initState()
// setup event system and option events.
this._initEvents()
// call created hook
this._callHook('created')
// if `el` option is passed, start compilation.
if (options.el) {
this.$mount(options.el)
}
}
}</pre>
PS
-
我希望SF支持ES6代碼渲染。
-
請糾錯。
</ul> </div>