目的
观察者模式是常见的设计模式,可以被应用到MV*框架的Model上,来实现对数据变化的监听。
基本概念
观察者模式是一种常见的设计模式。被观察者可以被订阅(subscribe),并在状态发生改变时通知订阅者。
观察者模式的实现主要涉及三个接口:
1. subscribe (evtName, handler):订阅被观察者的指定事件。
2. unsubscribe (evtName, handler):取消对被观察者指定事件的订阅。
3. publish (evtName, data):被观察者发布指定事件。
这些接口也常被命名为on,off,once,trigger。
代码实现
代码很简单易懂,就不多做解释了:
function Observer() { this.fns = {}; } Observer.prototype = { subscribe: function (evtName, handler) { if (!this.fns[evtName]) { this.fns[evtName] = []; } this.fns[evtName].push(handler); }, unsubscribe: function (evtName, handler) { var arr = this.fns[evtName]; if (!arr) { return; } var index = arr.indexOf(handler); if (index !== -1) { arr.splice(index, 1); } }, publish: function (evtName, data) { var arr = this.fns[evtName]; if (!arr) { return; } arr.forEach( function (handler) { handler(data); } ); } };
使用起来是这样的:
var benben = new Observer(); benben.subscribe('evtA', function (data) { console.log(data); }); benben.publish('evtA', {name: '笨笨'});
与Model结合
接下来我们就要让Model变为可观察的(Observable):
function Model () { Observer.call(this); // => this.fns = {}; this._data = {}; // 我们将实际数据存放在这里 } $.merge(Model.prototype, Observer.prototype); // 将方法扩展到Model的prototype上 var model = new Model();
现在我们要做的就是,监听model数据的变化,并发布(publish)。方式包括(并不限于):
1. get与set函数:
Model.prototype.set = function (key, value) { var oldValue = this._data[key]; this._data[key] = value; this.publish('change:' + key, { key: key, value: value, oldValue: oldValue }); } Model.prototype.get = function (key) { return this._data[key]; } model.set('name', '笨笨');
2. setter与getter:
Model.prototype.defineKeys = function (definitions) { var that = this, data = this._data; for (key in definitions) { this[key] = definitions[key]; this.__defineGetter__(key, function () { return data[key]; }); this.__defineSetter__(key, function (value) { var oldValue = data[key]; data[key] = value; that.publish('change:' + key, { key: key, value: value, oldValue: oldValue }); }); } } /* 或者使用defineProperty Object.defineProperty(this, key, { get: function() { // }, set: function(value) { // } }); */ // 需要先define后赋值 model.defineKeys({name: undefined}); model.name = '笨笨';
这两种方式都可以将对model的赋值与事件发布绑定到一起。当然它们各自有各自的缺陷:后者的赋值方式更“自然”,但需要先对字段定义。其他的方式还包括数据脏检测(dirty checking)等,但目的是统一的:将model的变化发布给订阅者,比如通知View来更新等等。
小结
通过使用观察者模式,我们就能监听Model的数据变化(也可以reject不符合条件的赋值等等),并作出相应的动作,比如更新View等等。
扩展:ES6(7)中的Object.observe
Object.observe是未来ES标准之一,包括Chrome 36(beta)+的浏览器已经支持之一特性,不过何时标准最终定稿和普遍实现还是未知。
让我们来了解一下Object.observe:
var obj = {name: 'benben'}, arr = [], onChange = function (changes) { changes.forEach(function (change) { console.log( change.type, // add, delete, update change.object, change.name, change.oldValue ); }); }; Object.observe(obj, onChange); Object.observe(arr, onChange); obj.name = '笨笨'; arr.push(1);
试试看,第一个情况我们被通知name的变化,第二种情况则被通知[0]和length发生了变化,是不是很方便呢。
我们还可以指定我们感兴趣的字段,以及取消监听:
Object.observe(obj, onChange, ['name', 'gender']); Object.unobserve(obj, onChange);
同时还提供了 Object.getNotifier和notifier.notify两个API来帮助我们发布事件:
function Square () { this.edge = 0; } Square.prototype.setEdge = function (val) { var notifier = Object.getNotifier(this); this.edge = val; notifier.notify({ object: this, type: 'update', area: val * val }); } var s = new Square(), onChange = function (changes) { console.log('onChange...'); changes.forEach(function (change) { console.log(change); }); }; Object.observe(s, onChange); s.setEdge(5);
注意s并没有area字段,我们通过notifier的notify方法来发布变化事件。
如果被观察者认为观察者并不应该关注某些字段的变化(不同于观察者只选择观察指定字段集),这是我们可以使用notifier的performChange方法:
Square.prototype.setEdge = function (val) { var notifier = Object.getNotifier(this); notifier.performChange('area', function() { this.edge = val; }, this); notifier.notify({ object: this, type: 'update', area: val * val }); }
我们在performChange的回调中设置了edge的值,这种情况下,edge的变化并不会被发布。