- Published on
打造属于自己的MVVM框架: 3.双向绑定
- Authors
- Name
- Pursue
MVVM 中对 Bingding 的解析只能算 viewModel->view 的单项绑定,但 MVVM 绝不仅仅只有单向绑定,更重要的是如何监控 viewModel 变化,将信息实时的反馈给 view。
如何监控 Object 的变化
你会可能会遇到一下场景:前端 UI 已经渲染完成,但并没有数据,因此发送请求向服务器请求数据,AJAX 回调完成后,用 Callback 里的值去更新 UI(很可能是暴力的 Jquery);当前端根据渲染的数据再次做了操作后,又得去 DOM 中取出修改后的值,一来一去十分麻烦,而且一旦 UI 结构有变化,那简直会被玩死。
而 MVVM 的最大好处就是避免这种复杂的 DOM 操作,取而代之的是监控绑定在页面上的 observable 对象,然后实时的更新 DOM,整个过程均为自动化。
那么问题来了,如何监控一个对象的变化呢?
方案 1.Object.observe
Object.observe
将会成为 ECMAScript 的标准,它不需要任何的库就能实现对象的变化的监控。如下:
// Let's say we have a model with data
var model = {}
// Which we then observe
Object.observe(model, function (changes) {
// This asynchronous callback runs
changes.forEach(function (change) {
// Letting us know what changed
console.log(change.type, change.name, change.oldValue)
})
})
在 Chrome 调试如下:
model.name = "xiaofei";
add name undefined
"xiaofei"
Object.defineProperty
可以在对象上新添一个自定义的属性,属性的所有设置都有自己配置(是否可枚举,是否可读写等),十分灵活。这些并不能实现对象的监控,但它提供了get
和set
的属性器以及事件回调,因此我们可以利用这个set
事件去实现对象的监控。
代码如下:
ko.observable = function (defaultValue) {
var self = {}
var value = defaultValue
var fn = function (val) {
if (val) {
self.value = val
} else {
return self.value
}
}
fn.isObservable = true
Object.defineProperty(self, 'value', {
get: function () {
return value
},
set: function (val) {
value = val
console.log('here I can capture changes event')
},
})
return fn
}
思路如下:
ko.observable 方法会将对象封装为一个 observable 对象,该对象的值由Object.defineProperty
定义,因此当 observable 更新值时会触发Object.defineProperty
的 set 方法,此时可以认为该对象变化了。
在 Chrome 下测试结果为:
var o = observable("xiaofei");
undefined
o();
"xiaofei"
o("new name")
here I can capture changes event
undefined
o()
"new name"
看起来似乎不错,可是为了实现 foreach,我们还需要另外创建一个 observableArray 对象,毕竟数组和基本的对象不太一样,参考上面的实现:
ko.observableArray: function(defaultValue) {
if(defaultValue && defaultValue.constructor.name != 'Array') {
throw "observableArray param must be array."
}
var self = {};
var value = defaultValue;
var fn = function(val) {
if(val) {
self.value = val;
} else {
return self.value;
}
};
fn.isObservable = true;
var keys = ["pop", "push", "reverse", "shift", "sort", "splice", "unshift", "concat", "join", "slice", "indexOf", "lastIndexOf", "forEach", "every", "map", "some", "reduce", "reduceRight", "each", "clone", "min", "max", "average", "sum", "unique", "shuffle", "pluck"];
for(var i in keys) {
var key = keys[i];
fn[key] = (function(key) {
return function() {
var elements = ko.unwrap(fn);
elements[key] && elements[key].apply(elements, arguments);
fn(elements);
};
})(key);
}
Object.defineProperty(self, 'value', {
get: function() {
return value;
},
set: function(val) {
value = val;
console.log("here I can capture changes event");
}
});
return fn;
}
observableArray
的实现在observable
的基础上,继承了 Array 的操作,因为对于observableArray
对象来说,更新元素里面的值的属性并不算它更新了,而只有它的个数活着自身的集合发生变化时才认为是变化了。而在这里我没有直接遍历所有 Array 的方法是因为 Array 的方法默认是不可迭代的,所以只能一一列出,甚至,根本不需要这么多方法,完全可以根据实际情况去定制。
在 Chrome 测试如下:
var arr = ko.observableArray([1,2,3]);
undefined
arr.push(4)
here I can capture changes event
undefined
arr()
[1, 2, 3, 4]
arr.pop();
undefined
arr()
[1, 2, 3]
5.总结
这一节主要介绍了如何实现双向绑定中的对象监控,实现了这个功能后,可以在事件捕获时再次通知模版引擎去重新渲染 UI,这样就形成了双向通信,下一篇将会进行整合。