什么是双向数据绑定?
数据变化更新视图,视图变化更新数据
网图 (侵删)
输入框内容变化时,data中的数据同步变化 view => model
data中的数据变化时,文本节点的内容同步变化 model => view
设计思想:观察者模式
Vue的双向数据绑定的设计思想为观察者模式。 Dep对象:Dependency依赖的简写,包含有三个主要属性id, subs, target和四个主要函数addSub, removeSub, depend, notify,是观察者的依赖集合,负责在数据发生改变时,使用notify()触发保存在 subs下的订阅列表,依次更新数据和DOM。
Observer对象:即观察者,包含两个主要属性value, dep。做法是使用getter/setter方法覆盖默认的 取值和赋值操作,将对象封装为响应式对象,每一次调用时更新依赖列表,更新值时触发订阅者。绑定 在对象的__ob__原型链属性上。
new Vue({
el: '#app',
data: { count: 100 },
...
});
下面我们来看看vue源码中怎么初始化上面这段代码的
初始化函数:initMixin:
Vue.prototype._init = function (options) {
...
var vm = this;
...
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
// initState就是我们接下来要跟进的初始化Vue参数
initState(vm);
initInjections(vm);
callHook(vm, '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); }
// 我们的count在这里初始化
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) {
initComputed(vm, opts.computed);
}
if (opts.watch) {
initWatch(vm, opts.watch);
}
}
initData:
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
if (!isPlainObject(data)) {
data = {};
}...
// observe data
observe(data, true /* asRootData */);
将data参数设置为响应式:
/*** Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */
function observe(value, asRootData) {
if (!isObject(value)) { return }
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer)
{ ob = value.__ob__; }
else if
/* 为了防止value不是单纯的对象而是Regexp或者函数之类的,或者是vm实例再或者是不可扩展*/
(
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
Observer类:
/***
* Observer class that are attached to each observed
* object. Once attached, the observer converts target
* object's property keys into getter/setters that
* collect dependencies and dispatches updates.
*/
var Observer = function Observer(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
// def函数是defineProperty的简单封装
def(value, '__ob__', this);
if (Array.isArray(value)) {
// 在es5及更低版本的js里,无法完美继承数组,这里检测并选取合适的函数 // protoAugment函数使用原型链继承,copyAugment函数使用原型链定义(即对每个数组 defineProperty)
var augment = hasProto ? protoAugment : copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
};
observerArray:
/*** Observe a list of Array items. */
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
Dep类:
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
var Dep = function Dep () {
this.id = uid$1++; this.subs = [];
};
walk函数:
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
Observer.prototype.walk = function walk(obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive?1(obj, keys[i], obj[keys[i]]);
}
};
defineReactive:
/**
* Define a reactive property on an Object.
*/
function defineReactive?1(obj, key, val, customSetter) {
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;
var childOb = 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
},
set: function reactiveSetter(newVal) {
var value = getter ? getter.call(obj) : val;
// 脏检查,排除了NaN !== NaN的影响
if (newVal === value || (newVal !== newVal && value !== value)) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = observe(newVal); dep.notify();
}
});
}
Dep.target&depend():
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null;
Dep.prototype.depend = function depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify() {
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
addDep():
/**
* Add a dependency to this directive.
*/
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)) {
// 使用push()方法添加一个订阅者
dep.addSub(this);
}
}
};
dependArray():
/**
* Collect dependencies on array elements when the array is touched, since
* we cannot intercept array element access like property getters.
*/
function dependArray(value) {
for (var e = (void 0), i = 0, l = value.length; i < l; i++) {
e = value[i];
e && e.__ob__ && e.__ob__.dep.depend();
if (Array.isArray(e)) {
dependArray(e);
}
}
}
数组的更新检测:
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort',
'reverse'].forEach(function (method) {
// cache original method
var original = arrayProto[method];
def(arrayMethods, method, function mutator() {
var arguments$1 = arguments;
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
var i = arguments.length;
var args = new Array(i);
while (i--) {
args[i] = arguments$1[i];
}
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
inserted = args;
break
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
if (inserted) { ob.observeArray(inserted); }
// notify change
ob.dep.notify();
return result
});
});
总结:
从上面的代码中我们可以一步步由深到浅的看到Vue是如何设计出双向数据绑定的,最主要的两点:
-
使用getter/setter代理值的读取和赋值,使得我们可以控制数据的流向。
-
使用观察者模式设计,实现了指令和数据的依赖关系以及触发更新。