数据绑定可以分为两部分
一、响应式数据的准备
注:下面的
initState
调用的代码位于Vue.prototype._init
中,而Vue.prototype._init
在initMixin
中执行时被定义,在vue.js文件中存在
这样几行代码,在你引入vue.js文件以后会执行initMixin
等后续一系列方法,其中initMixin
会为Vue这个构造函数挂载上_init
这个方法,_init
在new Vue
时会被执行,之后会进入到initState
方法中
主要位于生命周期的beforeCreated和created之间,其中最重要涉及的是initState函数
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);
}
}
这里涉及到了三个函数initData、initComputed、initWatch
,其实这三个函数执行完毕后数据绑定的数据部分已经处理完毕了,下面展开看看
initData
关键代码就在于observe
这里先入为主一下,observe的目的就是给传进来的参数设置响应式,对于普通的对象和数组处理方式不同,普通对象因为存在对象嵌套的情况,所以会对key 进行循环,并为对应的value设置响应式,而数组会对下标循环,对value也设置响应式;其实整体和deepClone的逻辑十分类似
function initData (vm) {
var data = vm.$options.data;
//这里省略了很多代码
// observe data
observe(data, true /* asRootData */);
}
进入到observe
,还是省去大量代码
function observe (value, asRootData) {
// 这里也省去了很多代码
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
}
重点在于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);
}
};
这里并不能省去很多代码,但是精简过后看到主要的逻辑在于传进来的参数是否为数组
如果是数组,那么用observeArray
就对数组进行特殊处理,如果是普通对象则使用walk
进行递归
再看一下这两个函数
//处理普通对象
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
//处理数组
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
那在处理数组中我们再一次看到了observe
函数,到目前为止逻辑是这样的
那再看
defineReactive$$1
,defineReactive$$1
为普通对象的每一个键值对利用defineProperty
设置get和set
方法构建响应式。在这里还是先精简一部分代码,理清流程
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);
}
});
}
这样一来就清晰很多了。
强调:这里只是定义了get set的方法,并没有调用,当组件挂载的时候才会调用,后面会细讲,这里还是理清逻辑为主。
先看这一句
var childOb = !shallow && observe(val);
又出现了observe
,这是用于解决对象嵌套的
{
a: 1,
b: {c: 2}
}
当循环到a:1
时,observe(1)
返回空,当循环到b: {c: 2}
时,observe(b: {c: 2})
返回observer实例,此时对于{c: 2}
的响应式也构建完毕了。
总结下,上面的代码还未运行到Object.defineProperty
时,已经对所有的子结构构建完响应式了
那么到目前为止的流程是这样的
其实到这里响应式基本就结束,下面结合mount时的代码说一下数据和视图时如何绑定的。
二、数据与视图之间的绑定
找到mountComponent
函数,该函数会在$mount调用时被执行
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
构造函数
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();
};
对比下形参和实参:
expOrFn=updateComponent
cb=noop
option= { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } }
得到执行时的相关变量:lazy=false
this.value
的值为this.get()
的返回值
get
是watcher
实例上的方法
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
中执行的vm._render()
会有对于data的访问,也就是触发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
},
出现了var dep = new Dep();
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
其中subs 是用来保存依赖于当前数据的watcher的
强调:subs中存的是watcher实例
进入到get
方法中
Dep.target
表示的是当前处于执行状态的watcher,默认情况下为空
Dep.target = null;
var targetStack = [];
那么Dep.target什么时候会有值呢?再看一下watcher实例上的get方法
Watcher.prototype.get = function get () {
pushTarget(this);
try {
//
} finally {
//
popTarget();
}
return value
};
这个两行很关键
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
其中pushTarget
把Dep.target
设置为自己,设置完之后,访问了data中的数据,此时再看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
},
先执行了dep.depend()
,把当前激活的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);
};
注意下调用链:dep调用depend,depend中把调用当前激活的watcher的addDep方法,参数为dep自身,
addDep
中dep把当前的激活的watcher添加到subs数组中
订阅发布模式:目前为止我们只做到了订阅,这里简单讲一下,vue为什么能做到精准的订阅。
我们再从头理一下。从watcher.get
开始,watcher.get
调用时会访问到视图中所需要的所有的data,同时watcher也会把自身设置为Dep.target
,而访问data的过程中,每个data的key-value会创建一个dep
对象,收集依赖这一个个data的watcher
,恰好当前的watcher能访问到这些data,也因此对这些data产生了依赖,所以通过设置Dep.target能够准确收集依赖。但是做法不一定需要时Dep.target,只需要一个全局变量即可
同样的对于对象嵌套的情况,子对象的dep也应该把当前的watcher加入到数组中
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();
}
对设置的新值进行observe
,保证新值改变的时候也能被观测到
然后就是订阅发布的发布dep.notify()
,主要就是对dep拷贝一份后执行subs中的watcher的回调函数,在这里就不展开了,具体的是利用promise将更新任务放入微队列中,然后取出dep中的watcher执行watcher.run,watcher.run中会执行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);
}
};
欢迎提问