vue在引入后會進行一些初始化操作,主要是在全局對象和vue原形鏈上掛載函數(shù),這些函數(shù)在后續(xù)的實例化過程中用到時再進行討論。
一個例子
如下是vue一個最簡單的例子,基于此例展開對vue整體架構(gòu)的分析
<div id="app">{{a}}</div>
<script>
var model = new Vue({
el: '#app',
data: {
a: 1
}
});
model.a = 10;
</script>
1. 初始化
vue本身是個函數(shù),執(zhí)行new操作后調(diào)用其init函數(shù):
function Vue (options) {
this._init(options)
}
_init函數(shù)的作用可以歸納為兩點:初始化狀態(tài)和掛載節(jié)點。
- 初始化狀態(tài)是對options中的data、props(組件中用到)、methods、computed等進行初始化操作,偏數(shù)據(jù)層面。
- 掛在節(jié)點會對dom進行分析,生成指令,同時生成關(guān)聯(lián)數(shù)據(jù)和指令的watcher對象。在數(shù)據(jù)改變時,會通過watcher通知directive進行視圖的更新。
Vue.prototype._init = function (options) {
...
// 狀態(tài)的初始化
this._initState()
// 掛在到節(jié)點
if (options.el) {
this.$mount(options.el)
}
}
2. 狀態(tài)的初始化
狀態(tài)初始化會的作用作用是把data下面的數(shù)據(jù)字段都設(shè)計成響應(yīng)式的:數(shù)據(jù)獲取(getter)的時候收集其關(guān)聯(lián)到的指令,數(shù)據(jù)變化(setter)的時候通知指令進行視圖的更新。
_initState主要包括以下幾個函數(shù)
Vue.prototype._initState = function () {
this._initProps() // 組件中的props初始化
this._initMeta() // 不知道干嘛的
this._initMethods() // 把methods中的方法綁定到實例上
this._initData() // 數(shù)據(jù)初始化 下面分析
this._initComputed() // computed屬性的初始化
}
// _initMethods 可以直接在實例上獲取到methods中的方法
Vue.prototype._initMethods = function () {
var methods = this.$options.methods
if (methods) {
for (var key in methods) {
this[key] = bind(methods[key], this)
}
}
}
// _initData
Vue.prototype._initData = function () {
...
// observe會遍歷data中的字段,對每個字段執(zhí)行defineReactive操作
// 讀取字段時,會收集其依賴,這里的依賴是watcher對象的列表
// 當設(shè)置字段值時, 會對依賴執(zhí)行notify()操作
observe(data, this)
}
function defineReactive (obj, key, val) {
var dep = new Dep()
...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // 收集字段的依賴
if (childOb) { // 收集子對象的依賴
childOb.dep.depend()
}
...
},
set: function reactiveSetter (newVal) {
...
// 此處會調(diào)用依賴列表的watcher進行數(shù)據(jù)視圖的同步!
// watcher會調(diào)用directive的更新方法
dep.notify()
}
})
}
// _initComputed
// 相對于普通的data字段,computed字段會在指令生成前額外生成一個watcher
// 此watcher也會加入到依賴列表中去;但是此watcher是lazy的。
// 非lazy的watcher會在watcher生成后執(zhí)行g(shù)et函數(shù)
// lazy的watcher 會在computed中的字段讀取值時才會調(diào)用到其getter函數(shù)
Vue.prototype._initComputed = function () {
for (var key in computed) {
var userDef = computed[key]
var def = {
enumerable: true,
configurable: true
}
def.get = makeComputedGetter(userDef, this)
Object.defineProperty(this, key, def)
}
}
function makeComputedGetter (getter, owner) {
var watcher = new Watcher(owner, getter, null, {
lazy: true
})
return function computedGetter () {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
3. 節(jié)點掛載
節(jié)點掛載集中了dom結(jié)構(gòu)分析、指令的生成、指令和數(shù)據(jù)關(guān)聯(lián)的watcher對象生成等幾個功能。
Vue.prototype.$mount = function (el) {
...
this._compile(el)
...
return this
}
這個compile函數(shù)會對DOM節(jié)點進行解析,根據(jù)指令的不同(如:v-model、v-text、{{}})等生成link函數(shù);這些link函數(shù)最終都會執(zhí)行到下面代碼段中的函數(shù),這個函數(shù)的作用可以概括為:生成指令并和節(jié)點進行關(guān)聯(lián)。
// el:指令所在的節(jié)點 例子中的{{a}}
// descriptor: 例子中的解析得到的指令:
// expression:"a"
// filters:undefined
// name:"text"
vm._bindDir(descriptor, el, host, scope, frag)
上述步驟執(zhí)行完后,vue會依據(jù)directive的priority進行排序,然后對指令執(zhí)行bind操作:
function linkAndCapture (linker, vm) {
var originalDirCount = vm._directives.length
linker()
var dirs = vm._directives.slice(originalDirCount)
dirs.sort(directiveComparator)
for (var i = 0, l = dirs.length; i < l; i++) {
// 指令的bind方法:把數(shù)據(jù)和視圖進行關(guān)聯(lián)
// 生成watcher實例 通過watcher進行后續(xù)的數(shù)據(jù)和視圖同步:
// 1、一個雙向綁定的元素,如input,通過監(jiān)聽其input or change事件, 有數(shù)據(jù)更新時,調(diào)用directive的set
// 2、directive的set調(diào)用watcher的set方法
//
dirs[i]._bind()
}
return dirs
}
bind()可以理解為數(shù)據(jù)和dom的一種綁定,其包括兩步:數(shù)據(jù)和watcher綁定、watcher和指令綁定。 也就是數(shù)據(jù)更新是通過watcher通知directive,進而更新view:
Directive.prototype._bind = function () {
...
// watcher的更新回調(diào) 用于directive的更新
// 當數(shù)據(jù)更新時會執(zhí)行到此函數(shù)
if (this.update) {
this._update = function (val, oldVal) {
if (!dir._locked) {
dir.update(val, oldVal)
}
}
}
...
// 1. 生成指令的watcher對象
var watcher = this._watcher = new Watcher(
this.vm,
this.expression,
this._update, // callback
{
filters: this.filters,
twoWay: this.twoWay,
deep: this.deep,
preProcess: preProcess,
postProcess: postProcess,
scope: this._scope
}
)
if (this.afterBind) {
this.afterBind()
} else if (this.update) {
// 2. 已經(jīng)綁定了數(shù)據(jù)的依賴 進行view的更新
this.update(watcher.value)
}
}
this._bound = true
}
watcher實例生成時,會進行數(shù)據(jù)的依賴綁定:
export default function Watcher (vm, expOrFn, cb, options) {
...
vm._watchers.push(this)
...
// get函數(shù)的執(zhí)行主要是收集依賴
this.value = this.lazy
? undefined
: this.get()
}
Watcher.prototype.get = function () {
this.beforeGet() // 依賴配置
...
value = this.getter.call(scope, scope) // 進行收集
}
// 在beforeGet中進行依賴的配置
Watcher.prototype.beforeGet = function () {
// 這個設(shè)置 會讓getter執(zhí)行時 把watcher作為依賴
Dep.target = this
this.newDeps = Object.create(null)
}
// defineReactive中進行收集
export function defineReactive (obj, key, val) {
...
Object.defineProperty(obj, key, {
...
get: function reactiveGetter () {
...
// 依賴收集
if (Dep.target) {
dep.depend()
...
}
4. 數(shù)據(jù)更新
實例中執(zhí)行model.a = 10;
,它會首先進入defineReactive的setter
:
set: function reactiveSetter (newVal) {
...
// 此處會調(diào)用依賴列表的watcher進行數(shù)據(jù)視圖的同步!
// watcher會調(diào)用directive的更新方法
dep.notify()
}
如下是nodtify的定義,subs是依賴到的watcher列表:
Dep.prototype.notify = function () {
// stablize the subscriber list first
var subs = toArray(this.subs)
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
watcher的update函數(shù)會把watcher放到一個列表中,然后統(tǒng)一依次調(diào)用callback執(zhí)行更新,這里的callback是Directive.prototype._bind
中的update:
Watcher.prototype.update = function (shallow) {
...
pushWatcher(this)
}
export function pushWatcher (watcher) {
...
q.push(watcher)
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushBatcherQueue)
}
}
}
function flushBatcherQueue () {
runBatcherQueue(queue)
...
}
function runBatcherQueue (queue) {
for (var i = 0; i < queue.length; i++) {
var watcher = queue[i]
...
watcher.run()
}
}
Watcher.prototype.run = function () {
...
// 此處的cb就是_bind中的update函數(shù)
this.cb.call(this.vm, value, oldValue)
...
}
至此就實現(xiàn)了一個最基本的數(shù)據(jù)和視圖的綁定。如下是一張簡單的架構(gòu)圖: