目的

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

js观察者模式与Model的更多相关文章

  1. Backbone.js 中的Model被Destroy后,不能触发success的一个原因

    下面这段代码中, 当调用destroy时,backbone会通过model中的url,向服务端发起一个HTTP DELETE请求, 以删除后台数据库中的user数据. 成功后,会回调触发绑定到dest ...

  2. Node.js与Sails~Model和ORM的持久化

    回到目录 上一讲说了在sails里定义model及相关参数的说明,这一讲主要说一下如何将你的Model持久化到文件,关系数据库和Nosql数据库里,在持久化这点上,sails是统一管理的,它可以在/c ...

  3. Backbone.js学习之Model

    首先,我们看一下官方文档中对Model的解释(或者说定义): Models are the heart of any JavaScript application, containing the in ...

  4. Backbone.js 中使用 Model

    前面几篇 Backbone.js 的例子中有使用到 template, 及数据的填充,其实这已经很接近 Model 了.现在来学习怎么创建自己的 Model 类,并简单的使用.Backbone.js ...

  5. [Node.js] Create a model to persist data in a Node.js LoopBack API

    In this lesson you will learn what a LoopBack model is, you will create a Product model using the Lo ...

  6. js - 观察者模式与订阅发布模式

    零.序言 转载&参考: 1.JavaScript 设计模式系列 - 观察者模式 2.JavaScript 设计模式(六):观察者模式与发布订阅模式 一.观察者模式(observer) 概要: ...

  7. Node.js与Sails~Model数据模型

    回到目录 对于Sails来说,它的Model与数据库对应,不过它并没有采用目前比较流行的poco贫血模型,而是采用了类似DDD的充血模型,即它的数据实体里即有数据库字段(属性)而且还有方法,而模型里的 ...

  8. js观察者模式

    观察者模式存在观察者和被观察者 被观察者的状态发生改变,通知观察者调用观察者的update方法,观察者的update方法对被观察者的状态进行检测,做出相应的操作 被观察者存在接口attach,deta ...

  9. 浅谈js观察者模式

    观察者模式又叫发布订阅模式,它可以让多个观察者对象同时监听某一个主题对象,即在一个事件发生时,不同的对象迅速对其进行相应.就比如当又人闯红灯,不同的人对这件事迅速发起响应,当然这个比喻不太恰当,不过在 ...

随机推荐

  1. ARM汇编指令集

    一.跳转指令.跳转指令用于实现程序流程的跳转,在ARM程序中有以下两种方法可以实现程序流程的跳转. Ⅰ.使用专门的跳转指令.Ⅱ.直接向程序计数器PC写入跳转地址值. 通过向程序计数器PC写入跳转地址值 ...

  2. UI常用控件

    UICommonlyUsedControls [UI常用控件] 不需要学习多么深入,但是要知道系统提供的有用的控件. 一.UISwitch(开关) 二.UIActivityIndicatorView( ...

  3. Android学习笔记之Intent(1)

    1.Intent指定启动目标组件 2.Intentfilter描述基本组件所在地址 3.其他包引入资源文件时记得引入R所在的包 package com.jikexueyuan.intent; impo ...

  4. Android学习笔记之Broadcast Receiver

    可程序间通信 注册通信,注销通信,发送消息 package com.jiahemeikang.helloandroid; import com.jiahemikang.service.EchoServ ...

  5. Firefox 插件 JSview是一套比较实用的JS,CSS文件查看工具,很方便,很快捷地查看页面引用了哪些文件,作为Web前端开发者是一套必备的插件,由于Firefox升级过快,插件很快不兼容了,这里对插件做了一些调整,可以兼容最新Firefox浏览器(目前FireFox 21)

    JSView Firefox Plugins Download  点击下载

  6. ibatis参数传递小技巧 - 疯狂的菠菜 - ITeye技术网站

    body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI ...

  7. openstack名称发音收集

    MariaDB:    maria['mɛərɪr] Corosync:    coro[kə'roʊ]    sync[sɪŋk] pacemaker:    [ˈpesˌmekɚ] galera: ...

  8. 解决SSh连接过慢

    ssh 连接的时候特别慢 解决方法: 修改配置文件 /etc/ssh/sshd_config GSSAPIAuthentication yes --->no 81行左右 #UseDNS yes ...

  9. 如何在微软Hyper-V下发挥SQL Server最大功效

    要建设稳定运行的虚拟化SQL Server系统,关键是确保虚拟化管理软件配置能提供数据库所需的资源.SQL Server是CPU密集型技术,因此支撑它的虚拟机需要能获得充足的处理器资源,同时不能引起与 ...

  10. 【python】matplotlib在windows下安装

    昨晚装了好久的这玩意,终于在凌晨成功搞定,然后跑起了一个人人网抓取好友关系的脚本~开心. 以下是我参考的最给力的文档,全部安装一遍,就可以啦~ 但是!在安装前一定要先确认自己的python版本!本人自 ...