vue在引入后會進行一些初始化操作,主要是在全局對象和vue原形鏈上掛載函數,這些函數在后續的實例化過程中用到時再進行討論。
一個例子
如下是vue一個最簡單的例子,基于此例展開對vue整體架構的分析
<div id="app">{{a}}</div>
<script>
var model = new Vue({
el: '#app',
data: {
a: 1
}
});
model.a = 10;
</script>
1. 初始化
vue本身是個函數,執行new操作后調用其init函數:
function Vue (options) {
this._init(options)
}
_init函數的作用可以歸納為兩點:初始化狀態和掛載節點。
- 初始化狀態是對options中的data、props(組件中用到)、methods、computed等進行初始化操作,偏數據層面。
- 掛在節點會對dom進行分析,生成指令,同時生成關聯數據和指令的watcher對象。在數據改變時,會通過watcher通知directive進行視圖的更新。
Vue.prototype._init = function (options) {
...
// 狀態的初始化
this._initState()
// 掛在到節點
if (options.el) {
this.$mount(options.el)
}
}
2. 狀態的初始化
狀態初始化會的作用作用是把data下面的數據字段都設計成響應式的:數據獲取(getter)的時候收集其關聯到的指令,數據變化(setter)的時候通知指令進行視圖的更新。
_initState主要包括以下幾個函數
Vue.prototype._initState = function () {
this._initProps() // 組件中的props初始化
this._initMeta() // 不知道干嘛的
this._initMethods() // 把methods中的方法綁定到實例上
this._initData() // 數據初始化 下面分析
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中的字段,對每個字段執行defineReactive操作
// 讀取字段時,會收集其依賴,這里的依賴是watcher對象的列表
// 當設置字段值時, 會對依賴執行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) {
...
// 此處會調用依賴列表的watcher進行數據視圖的同步!
// watcher會調用directive的更新方法
dep.notify()
}
})
}
// _initComputed
// 相對于普通的data字段,computed字段會在指令生成前額外生成一個watcher
// 此watcher也會加入到依賴列表中去;但是此watcher是lazy的。
// 非lazy的watcher會在watcher生成后執行get函數
// lazy的watcher 會在computed中的字段讀取值時才會調用到其getter函數
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. 節點掛載
節點掛載集中了dom結構分析、指令的生成、指令和數據關聯的watcher對象生成等幾個功能。
Vue.prototype.$mount = function (el) {
...
this._compile(el)
...
return this
}
這個compile函數會對DOM節點進行解析,根據指令的不同(如:v-model、v-text、{{}})等生成link函數;這些link函數最終都會執行到下面代碼段中的函數,這個函數的作用可以概括為:生成指令并和節點進行關聯。
// el:指令所在的節點 例子中的{{a}}
// descriptor: 例子中的解析得到的指令:
// expression:"a"
// filters:undefined
// name:"text"
vm._bindDir(descriptor, el, host, scope, frag)
上述步驟執行完后,vue會依據directive的priority進行排序,然后對指令執行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方法:把數據和視圖進行關聯
// 生成watcher實例 通過watcher進行后續的數據和視圖同步:
// 1、一個雙向綁定的元素,如input,通過監聽其input or change事件, 有數據更新時,調用directive的set
// 2、directive的set調用watcher的set方法
//
dirs[i]._bind()
}
return dirs
}
bind()可以理解為數據和dom的一種綁定,其包括兩步:數據和watcher綁定、watcher和指令綁定。 也就是數據更新是通過watcher通知directive,進而更新view:
Directive.prototype._bind = function () {
...
// watcher的更新回調 用于directive的更新
// 當數據更新時會執行到此函數
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. 已經綁定了數據的依賴 進行view的更新
this.update(watcher.value)
}
}
this._bound = true
}
watcher實例生成時,會進行數據的依賴綁定:
export default function Watcher (vm, expOrFn, cb, options) {
...
vm._watchers.push(this)
...
// get函數的執行主要是收集依賴
this.value = this.lazy
? undefined
: this.get()
}
Watcher.prototype.get = function () {
this.beforeGet() // 依賴配置
...
value = this.getter.call(scope, scope) // 進行收集
}
// 在beforeGet中進行依賴的配置
Watcher.prototype.beforeGet = function () {
// 這個設置 會讓getter執行時 把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. 數據更新
實例中執行model.a = 10;
,它會首先進入defineReactive的setter
:
set: function reactiveSetter (newVal) {
...
// 此處會調用依賴列表的watcher進行數據視圖的同步!
// watcher會調用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函數會把watcher放到一個列表中,然后統一依次調用callback執行更新,這里的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函數
this.cb.call(this.vm, value, oldValue)
...
}
至此就實現了一個最基本的數據和視圖的綁定。如下是一張簡單的架構圖: