目的

观察者模式是常见的设计模式,可以被应用到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的变化并不会被发布。

最新文章

  1. bzoj3674同上(好短)
  2. 深入理解js——作用域和上下文环境
  3. C++builder中使用TScrollBox 以后,让scrollBox相应鼠标的上下滑动
  4. 不就是抽个血吗,至于么-jQuery,Linux完结篇
  5. 电商、商城类APP常用标签"hot"--第三方开源--LabelView
  6. POJ 3074 Sudoku (Dancing Links)
  7. Android完全退出应用程序,完美解决方案
  8. Ambiguous handler methods mapped for HTTP path
  9. IOS“多继承”
  10. MiniProfiler.3.0.10 用于MVC4.0中不能显示SQL语句
  11. COJ 0885 LCS???
  12. 1005 Number Sequence(HDU)
  13. JPA 系列教程16-继承-联合子类-JOINED
  14. json省市县数据源
  15. Ubuntu命令行连接WPA/WPA2无线网线
  16. VS2017Release+x64失败,LNK1104,无法打开文件"msvcprt.lib"
  17. shell脚本中单双引号疑惑
  18. 使用sklearn估计器构建K-Means聚类模型
  19. common lisp里的几个操作符
  20. httpd服务器的真实ip获取难题

热门文章

  1. DWR 整合之Struts2.3.16
  2. Qt5:不规则按钮的实现---通过贴图实现
  3. Python 100例(上)
  4. Python之路: 面向对象
  5. stm32 RAM分配及占有(转)
  6. 如何用70行Java代码实现深度神经网络算法
  7. MAC 10.10解决 PHP GD库的问题
  8. CodeForces 618B Guess the Permutation
  9. 用div做下拉列表
  10. linux命令学习-4-lsof