數(shù)據(jù)綁定可以分為兩部分
一、響應(yīng)式數(shù)據(jù)的準(zhǔn)備
注:下面的
initState調(diào)用的代碼位于Vue.prototype._init中,而Vue.prototype._init在initMixin中執(zhí)行時(shí)被定義,在vue.js文件中存在image.png
這樣幾行代碼,在你引入vue.js文件以后會執(zhí)行initMixin等后續(xù)一系列方法,其中initMixin會為Vue這個(gè)構(gòu)造函數(shù)掛載上_init這個(gè)方法,_init在new Vue時(shí)會被執(zhí)行,之后會進(jìn)入到initState方法中
主要位于生命周期的beforeCreated和created之間,其中最重要涉及的是initState函數(shù)
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
這里涉及到了三個(gè)函數(shù)initData、initComputed、initWatch,其實(shí)這三個(gè)函數(shù)執(zhí)行完畢后數(shù)據(jù)綁定的數(shù)據(jù)部分已經(jīng)處理完畢了,下面展開看看
initData
關(guān)鍵代碼就在于observe
這里先入為主一下,observe的目的就是給傳進(jìn)來的參數(shù)設(shè)置響應(yīng)式,對于普通的對象和數(shù)組處理方式不同,普通對象因?yàn)榇嬖趯ο笄短椎那闆r,所以會對key 進(jìn)行循環(huán),并為對應(yīng)的value設(shè)置響應(yīng)式,而數(shù)組會對下標(biāo)循環(huán),對value也設(shè)置響應(yīng)式;其實(shí)整體和deepClone的邏輯十分類似
function initData (vm) {
var data = vm.$options.data;
//這里省略了很多代碼
// observe data
observe(data, true /* asRootData */);
}
進(jìn)入到observe,還是省去大量代碼
function observe (value, asRootData) {
// 這里也省去了很多代碼
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
}
重點(diǎn)在于ob = new Observer(value);,那再看一下Observer的定義(省去了小部分代碼)
var Observer = function Observer (value) {
this.dep = new Dep();
if (Array.isArray(value)) {
this.observeArray(value);
} else {
this.walk(value);
}
};
這里并不能省去很多代碼,但是精簡過后看到主要的邏輯在于傳進(jìn)來的參數(shù)是否為數(shù)組
如果是數(shù)組,那么用observeArray就對數(shù)組進(jìn)行特殊處理,如果是普通對象則使用walk進(jìn)行遞歸
再看一下這兩個(gè)函數(shù)
//處理普通對象
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
//處理數(shù)組
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
那在處理數(shù)組中我們再一次看到了observe函數(shù),到目前為止邏輯是這樣的

那再看
defineReactive$$1,defineReactive$$1為普通對象的每一個(gè)鍵值對利用defineProperty設(shè)置get和set方法構(gòu)建響應(yīng)式。在這里還是先精簡一部分代碼,理清流程
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val
return value
},
set: function reactiveSetter (newVal) {
childOb = !shallow && observe(newVal);
}
});
}
這樣一來就清晰很多了。
強(qiáng)調(diào):這里只是定義了get set的方法,并沒有調(diào)用,當(dāng)組件掛載的時(shí)候才會調(diào)用,后面會細(xì)講,這里還是理清邏輯為主。
先看這一句
var childOb = !shallow && observe(val);
又出現(xiàn)了observe,這是用于解決對象嵌套的
{
a: 1,
b: {c: 2}
}
當(dāng)循環(huán)到a:1時(shí),observe(1)返回空,當(dāng)循環(huán)到b: {c: 2}時(shí),observe(b: {c: 2})返回observer實(shí)例,此時(shí)對于{c: 2}的響應(yīng)式也構(gòu)建完畢了。
總結(jié)下,上面的代碼還未運(yùn)行到Object.defineProperty時(shí),已經(jīng)對所有的子結(jié)構(gòu)構(gòu)建完響應(yīng)式了
那么到目前為止的流程是這樣的

其實(shí)到這里響應(yīng)式基本就結(jié)束,下面結(jié)合mount時(shí)的代碼說一下數(shù)據(jù)和視圖時(shí)如何綁定的。
二、數(shù)據(jù)與視圖之間的綁定
找到mountComponent函數(shù),該函數(shù)會在$mount調(diào)用時(shí)被執(zhí)行
function mountComponent (
vm,
el,
hydrating
) {
//精簡很多代碼
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
}
核心代碼new Watcher,看下watcher構(gòu)造函數(shù)
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (options) {
this.lazy = !!options.lazy;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.deps = [];
this.value = this.lazy
? undefined
: this.get();
};
對比下形參和實(shí)參:
expOrFn=updateComponent
cb=noop
option= { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } }
得到執(zhí)行時(shí)的相關(guān)變量:lazy=false
this.value的值為this.get()的返回值
get是watcher實(shí)例上的方法
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
其中value = this.getter.call(vm, vm);中的getter為updateComponent這里就不多做介紹了,但是需要提到就是在updateComponent中執(zhí)行的vm._render()會有對于data的訪問,也就是觸發(fā)defineProperty中的get方法。
重新回到defineReactive$$1中看看完整的代碼
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = !shallow && observe(val);
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();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
出現(xiàn)了var dep = new Dep();
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
其中subs 是用來保存依賴于當(dāng)前數(shù)據(jù)的watcher的
強(qiáng)調(diào):subs中存的是watcher實(shí)例
進(jìn)入到get方法中
Dep.target表示的是當(dāng)前處于執(zhí)行狀態(tài)的watcher,默認(rèn)情況下為空
Dep.target = null;
var targetStack = [];
那么Dep.target什么時(shí)候會有值呢?再看一下watcher實(shí)例上的get方法
Watcher.prototype.get = function get () {
pushTarget(this);
try {
//
} finally {
//
popTarget();
}
return value
};
這個(gè)兩行很關(guān)鍵
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
其中pushTarget把Dep.target設(shè)置為自己,設(shè)置完之后,訪問了data中的數(shù)據(jù),此時(shí)再看defineReactive$$1中的get
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
先執(zhí)行了dep.depend(),把當(dāng)前激活的watcher添加到dep中
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
注意下調(diào)用鏈:dep調(diào)用depend,depend中把調(diào)用當(dāng)前激活的watcher的addDep方法,參數(shù)為dep自身,
addDep中dep把當(dāng)前的激活的watcher添加到subs數(shù)組中
訂閱發(fā)布模式:目前為止我們只做到了訂閱,這里簡單講一下,vue為什么能做到精準(zhǔn)的訂閱。
我們再從頭理一下。從watcher.get開始,watcher.get調(diào)用時(shí)會訪問到視圖中所需要的所有的data,同時(shí)watcher也會把自身設(shè)置為Dep.target,而訪問data的過程中,每個(gè)data的key-value會創(chuàng)建一個(gè)dep對象,收集依賴這一個(gè)個(gè)data的watcher,恰好當(dāng)前的watcher能訪問到這些data,也因此對這些data產(chǎn)生了依賴,所以通過設(shè)置Dep.target能夠準(zhǔn)確收集依賴。但是做法不一定需要時(shí)Dep.target,只需要一個(gè)全局變量即可
同樣的對于對象嵌套的情況,子對象的dep也應(yīng)該把當(dāng)前的watcher加入到數(shù)組中
if (childOb)
childOb.dep.depend();
再來看下set,相對而言簡單了很多
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
主要就是這幾行代碼
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
childOb = !shallow && observe(newVal);
dep.notify();
}
對設(shè)置的新值進(jìn)行observe,保證新值改變的時(shí)候也能被觀測到
然后就是訂閱發(fā)布的發(fā)布dep.notify(),主要就是對dep拷貝一份后執(zhí)行subs中的watcher的回調(diào)函數(shù),在這里就不展開了,具體的是利用promise將更新任務(wù)放入微隊(duì)列中,然后取出dep中的watcher執(zhí)行watcher.run,watcher.run中會執(zhí)行wachter.get,也就是updatecomponent完成視圖的更新
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
歡迎提問
