本节会讲到

  1. 什么是观察者(Observer)模式及代码实现
  2. 什么是 Iterator (迭代器) 模式及代码实现
  3. 什么是 Observable

观察者模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状
态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型
来替代传统的发布—订阅模式。

现实中的发布-订阅模式

不论是在程序世界里还是现实生活中,发布—订阅模式的应用都非常之广泛。我们先看一个
现实中的例子。
小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼
MM 告诉小明,不久后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。
但到底是什么时候,目前还没有人能够知道。
于是小明记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。除
了小明,还有小红、小强、小龙也会每天向售楼处咨询这个问题。一个星期过后,售楼 MM 决
定辞职,因为厌倦了每天回答 1000 个相同内容的电话。
当然现实中没有这么笨的销售公司,实际上故事是这样的:小明离开之前,把电话号码留在
了售楼处。售楼 MM 答应他,新楼盘一推出就马上发信息通知小明。小红、小强和小龙也是一
样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼 MM 会翻开花名册,
遍历上面的电话号码,依次发送一条短信来通知他们。

现实中的发布-订阅模式

在刚刚的例子中,发送短信通知就是一个典型的发布—订阅模式,小明、小红等购买者都是
订阅者,他们订阅了房子开售的消息。售楼处作为发布者,会在合适的时候遍历花名册上的电话
号码,依次给购房者发布消息。
可以发现,在这个例子中使用发布—订阅模式有着显而易见的优点。

  • 购房者不用再天天给售楼处打电话咨询开售时间,在合适的时间点,售楼处作为发布者
    会通知这些消息订阅者。
  • 购房者和售楼处之间不再强耦合在一起,当有新的购房者出现时,他只需把手机号码留
    在售楼处,售楼处不关心购房者的任何情况,不管购房者是男是女还是一只猴子。 而售
    楼处的任何变动也不会影响购买者,比如售楼 MM 离职,售楼处从一楼搬到二楼,这些
    改变都跟购房者无关,只要售楼处记得发短信这件事情。
    第一点说明发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。
    比如,我们可以订阅 ajax 请求的 error、success 等事件。 或者如果想在动画的每一帧完成之后做一
    些事情,那我们可以订阅一个事件,然后在动画的每一帧完成之后发布这个事件。在异步编程中
    使用发布—订阅模式,我们就无需过多关注对象在异步运行期间的内部状态,而只需要订阅感兴
    趣的事件发生点。
    第二点说明发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调
    用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼
    此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修
    改;同样发布者需要改变时,也不会影响。

DOM 事件

实际上,只要我们曾经在 DOM 节点上面绑定过事件函数,那我们就曾经使用过发布—订阅
模式,来看看下面这两句简单的代码发生了什么事情:

document.body.addEventListener( 'click', function(){ alert(2);
}, false ); document.body.click();    // 模拟用户点击

在这里需要监控用户点击 document.body 的动作,但是我们没办法预知用户将在什么时候点
击。所以我们订阅 document.body 上的 click 事件,当 body 节点被点击时,body 节点便会向订阅
者发布这个消息。这很像购房的例子,购房者不知道房子什么时候开售,于是他在订阅消息后等
待售楼处发布消息。
当然我们还可以随意增加或者删除订阅者,增加任何订阅者都不会影响发布者代码的编写:

document.body.addEventListener( 'click', function(){ alert(2);
}, false ); document.body.addEventListener( 'click', function(){alert(3);
}, false ); document.body.addEventListener( 'click', function(){ alert(4);
}, false ); document.body.click();    // 模拟用户点击

实现 观察者模式

分别用 es5 和 es6 实现
下面是es5写法

function Producer() {// 这个 if 只是避免使用者不小心把 Producer 当作函式来调用if(!(this instanceof Producer)) {throw new Error('请用 new Producer()!');// 仿 ES6 行为可用: throw new Error('Class constructor Producer cannot be invoked without 'new'')}this.listeners = [];
}// 加入监听的方法
Producer.prototype.addListener = function(listener) {if(typeof listener === 'function') {this.listeners.push(listener)} else {throw new Error('listener 必须是 function')}
}// 移除监听的方法
Producer.prototype.removeListener = function(listener) {this.listeners.splice(this.listeners.indexOf(listener), 1)
}// 发送通知的方法
Producer.prototype.notify = function(message) {this.listeners.forEach(listener => {listener(message);})
}

es6版本

class Producer {constructor() {this.listeners = [];}addListener(listener) {if(typeof listener === 'function') {this.listeners.push(listener)} else {throw new Error('listener 必须是 function')}}removeListener(listener) {this.listeners.splice(this.listeners.indexOf(listener), 1)}notify(message) {this.listeners.forEach(listener => {listener(message);})}
}

有了上面的方法,可以实例化了

var egghead = new Producer();
// new 出一个 Producer 实例叫 eggheadfunction listener1(message) {console.log(message + 'from listener1');
}function listener2(message) {console.log(message + 'from listener2');
}egghead.addListener(listener1); // 注册监听
egghead.addListener(listener2);egghead.notify('A new course!!') // 当某件事情方法时,执行

会输出

a new course!! from listener1
a new course!! from listener2

每当 egghead.notify 执行一次,listener1 跟 listener2 就会被通知,而这些 listener 可以额外被添加,也可以被移除!
虽然我们的实作很简单,但它很好的说明了 Observer Pattern 如何在事件(event)跟监听者(listener)的互动中做到去藕合(decoupling)。

迭代器模式 Iterator Pattern

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象
的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即
使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
迭代器 Iterator 本质是一个指针(pointer)对象。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
先让我们来看看原生的 JS 要怎么建立 iterator

var arr = [1, 2, 3];var iterator = arr[Symbol.iterator]();iterator.next();
// { value: 1, done: false }
iterator.next();
// { value: 2, done: false }
iterator.next();
// { value: 3, done: false }
iterator.next();
// { value: undefined, done: true }

自己实现 Iterator

function IteratorFromArray(arr) {if(!(this instanceof IteratorFromArray)) {throw new Error('请用 new IteratorFromArray()!');}this._array = arr;this._cursor = 0;
}IteratorFromArray.prototype.next = function() {return this._cursor < this._array.length ?{ value: this._array[this._cursor++], done: false } :{ done: true };
}

es6版本

class IteratorFromArray {constructor(arr) {this._array = arr;this._cursor = 0;}next() {return this._cursor < this._array.length ?{ value: this._array[this._cursor++], done: false } :{ done: true };}
}

迭代器模式虽然很单纯,但同时带来了两个优势,第一它渐进式取得数据的特性可以拿来做延迟运算(Lazy evaluation),让我们能用它来处理数据结构。第二因为 iterator 本身是序列,所以可以实现所有数组的运算方法像 map, filter... 等!

class IteratorFromArray {constructor(arr) {this._array = arr;this._cursor = 0;}next() {return this._cursor < this._array.length ?{ value: this._array[this._cursor++], done: false } :{ done: true };}map(callback) {const iterator = new IteratorFromArray(this._array);return {next: () => {const { done, value } = iterator.next();return {done: done,value: done ? undefined : callback(value)}}}}
}var iterator = new IteratorFromArray([1,2,3]);
var newIterator = iterator.map(value => value + 3);newIterator.next();
// { value: 4, done: false }
newIterator.next();
// { value: 5, done: false }
newIterator.next();
// { value: 6, done: false }

补充: 延迟运算(Lazy evaluation)

延迟运算,或说 call-by-need,是一种运算策略(evaluation strategy),简单来说我们延迟一个表达式的运算时机直到真正需要它的值在做运算。
以下我们用 generator 实作 iterator 来举一个例子

function* getNumbers(words) {for (let word of words) {if (/^[0-9]+$/.test(word)) {yield parseInt(word, 10);}}}const iterator = getNumbers('30 天精通 RxJS (04)');iterator.next();// { value: 3, done: false }iterator.next();// { value: 0, done: false }iterator.next();// { value: 0, done: false }iterator.next();// { value: 4, done: false }iterator.next();// { value: undefined, done: true }

这么我们写了一个函数用来抓取字串中的数字,在这个函数中我们用 for...of 的方式来取得每个字符并用正则表示式来判断是不是数值,如果为真就转成数值并回传。当我们把一个字串丢进 getNumbers 函式时,并没有马上运算出字串中的所有数字,必须等到我们执行 next() 时,才会真的做运算,这就是所谓的延迟运算(evaluation strategy)

Observable

在了解 Observer 跟 Iterator 后,不知道大家有没有发现其实 Observer 跟 Iterator 有个共通的特性,就是他们都是渐进式(progressive) 的取得数据,差别只在于 Observer 是生产者(Producer)推送数据(push),而 Iterator 是消费者(Consumer)拉数据(pull)!

image.png

Observable 其实就是这两个 Pattern 思想的结合,Observable 具备生产者推送数据的特性,同时能像数组,拥有数组处理数据的方法(map, filter...)!

下节讲 如何创建 Observable 。

参考:
http://es6.ruanyifeng.com/#docs/iterator

3. 说清 Observable相关推荐

  1. Knockoutjs之observable和applyBindings的使用

    observable在Knockoutjs中属于一个核心功能,在做监控数据的改变时,必须要用到Knockoutjs的监控属性--observable. ko.observable()的简单使用 首先来 ...

  2. Android 高清加载巨图方案 拒绝压缩图片

    Android 高清加载巨图方案 拒绝压缩图片 转载请标明出处:  http://blog.csdn.net/lmj623565791/article/details/49300989:  本文出自: ...

  3. 另存为里面没有jpg_选用打印捕捉功能将 SolidWorks 画面另存为JPG格式高清大图

    3D建模时,我们通常都是截屏,然后保存成图片,对于简单零件来说,这个没有问题,但对于复杂零件.以及大型零部件装配,截屏就满足不了要求了,这时候需要将屏幕内容保存成高清图片,细节部分也可以看得很清楚,方 ...

  4. 关于在linux下清屏的几种技巧

    在windows的DOS操作界面里面,清屏的命令是cls,那么在linux 里面的清屏命令是什么呢?下面笔者分享几种在linux下用过的清屏方法. 1.clear命令.这个命令将会刷新屏幕,本质上只是 ...

  5. 华为atn980传输设备_在头发丝中实现每秒1000张高清DVD传输

    这是近日央视<对话>栏目"超高清视频带来的产业诱惑"节目中,提到的随着4K.8K超高清视频发展,顺应时代发生的变化之一. 超高清视频将为消费者带来全新的.超乎想象的体验 ...

  6. 一般熟练盲打需要多久_进口攻略!一般货物进口清关需要多久?如何有效提高清关效率?...

    ***请点击上方红色的"关注"我们,您的关注,是我们努力的动力!谢谢!*** 一.清关是什么意思? 在回答"清关一般要多久之前"我们一起来看下,就是清关是什么意 ...

  7. linux清屏命令_linux下的7个常用命令的基本使用

    02.Linux下的7个常用命令的基本使用 小技巧: 使用[ctrl + shift + = ]可以放大终端窗口的字体显示使用[ctrl + -]可以缩小终端窗口的字体显示 ls:查看当前目录下的文件 ...

  8. java清空栈_java - 如何使用Intent.FLAG_ACTIVITY_CLEAR_TOP清除活动堆栈?

    java - 如何使用Intent.FLAG_ACTIVITY_CLEAR_TOP清除活动堆栈? 我已经阅读了几篇关于使用它的帖子,但必须遗漏一些因为它不适合我. 我的活动A在清单中有launchmo ...

  9. Angular 可观察对象(Observable)

    可观察对象(Observable) 可观察对象支持在应用的发布者和订阅者之间传递消息. 可观察对象是声明式的 -- 即定义的用于发布值的函数,在有消费者订阅它之前,这个函数不会实际执行. 可观察对象可 ...

  10. 有光照就能上网 0.2秒即可下载一部高清电影

    再也不用费尽心思询问 WIFI 密码了,以后,哪里有光照,哪里就可以上网.中国"可见光通信系统关键技术研究"近日取得重大突破,实时通信速率提升至 50Gbps,也就是说: 0.2 ...

最新文章

  1. C#检测电脑的一些设置通用类(经典推荐)
  2. Eclipse导出可执行Jar文件(包含第三方Jar包)
  3. LeetCode Implement Trie (Prefix Tree)(字典树)
  4. 【面向对象设计模式】 接口型模式 (一)
  5. Azure Remoteapp 使用指南
  6. 单片机汇编编程300例_pic单片机编程串烧,pic单片机汇编语言讲解下篇
  7. 【多媒体内容与体验创新】
  8. WordList02
  9. 想要摆脱手工报表困境?1个工具+5个场景解决80%工作难题
  10. 漫步线性代数二十一——行列式引言
  11. 进入docker容器中查看文件夹_理解 docker 容器中的 uid 和 gid
  12. css 三栏布局 圣杯布局 双飞翼 flex
  13. 微信小程序跳转微信内置浏览器
  14. 多线程私有数据pthread_key_create
  15. 如何创建海外美区Apple ID,并使用支付宝购买Apple Store礼品卡,十分钟学会
  16. java多线程(8):Lamda表达式
  17. 浅谈css3的轮播图
  18. TFLite Objec Detection IOS 检测核心代码说明
  19. 公羊优链蜘蛛池【高权重】(白狐 公羊seo)
  20. 刘强东的“强式”回归

热门文章

  1. linux下epoll网络编程模型,C++ - 网络编程模型 - Linux EPOLL
  2. Flink Weekly | 每周社区动态更新-12/24
  3. 大龄程序员失业后,看他们是如何破局突围的?
  4. 提示overwrite是什么意思_Linux Shell 提示符#和$表示什么意思?
  5. python学生管理系统gui版好例子网_python界面版学生管理系统 相关实例(示例源码)下载 - 好例子网...
  6. dell服务器运维,施用smartctl查dell服务器坏道实录
  7. android 原型模式,原型模式-下拉刷新的全局设置
  8. 戴尔服务器720dx格式化硬盘,戴尔首个基于 Ocarina 的解决方案--DX6000G 存储压缩节点...
  9. 创建crawlspider爬虫 学习笔记
  10. python excel案例导入jira_用Python脚本批量添加JIRA用户,python,jira