這是轉(zhuǎn)載【30天精通 RxJS】的 04 篇,如果還沒(méi)看過(guò) 03 篇可以往這邊走:
30 天精通 RxJS (03): Functional Programming 通用函式
要理解 Observable 之前,我們必須先談?wù)剝蓚€(gè)設(shè)計(jì)模式(Design Pattern), Iterator Pattern 跟 Observer Pattern。今天這篇文章會(huì)帶大家快速的了解這兩個(gè)設(shè)計(jì)模式,并解釋這兩個(gè) Pattern 跟 Observable 之間的關(guān)系!
Observer Pattern
Observer Pattern 其實(shí)很常遇到,在許多 API 的設(shè)計(jì)上都用了 Observer Pattern 實(shí)作,最簡(jiǎn)單的例子就是 DOM 物件的事件監(jiān)聽(tīng),程式碼如下
function clickHandler(event) {
console.log('user click!');
}
document.body.addEventListener('click', clickHandler)
在上面的程式碼,我們先宣告了一個(gè) clickHandler 函式,再用 DOM 物件 (范例是 body) 的 addEventListener 來(lái)監(jiān)聽(tīng)點(diǎn)擊(click)事件,每次使用者在 body 點(diǎn)擊滑鼠就會(huì)執(zhí)行一次 clickHandler,并把相關(guān)的資訊(event)帶進(jìn)來(lái)!這就是觀察者模式,我們可以對(duì)某件事注冊(cè)監(jiān)聽(tīng),并在事件發(fā)生時(shí),自動(dòng)執(zhí)行我們注冊(cè)的監(jiān)聽(tīng)者(listener)。
Observer 的觀念其實(shí)就這麼的簡(jiǎn)單,但筆者希望能透過(guò)程式碼帶大家了解,如何實(shí)作這樣的 Pattern!
首先我們需要一個(gè)建構(gòu)式,這個(gè)建構(gòu)式 new 出來(lái)的實(shí)例可以被監(jiān)聽(tīng)。
這裡我們先用 ES5 的寫法,會(huì)再附上 ES6 的寫法
function Producer() {
// 這個(gè) if 只是避免使用者不小心把 Producer 當(dāng)作函式來(lái)調(diào)用
if(!(this instanceof Producer)) {
throw new Error('請(qǐng)用 new Producer()!');
// 仿 ES6 行為可用: throw new Error('Class constructor Producer cannot be invoked without 'new'')
}
this.listeners = [];
}
// 加入監(jiān)聽(tīng)的方法
Producer.prototype.addListener = function(listener) {
if(typeof listener === 'function') {
this.listeners.push(listener)
} else {
throw new Error('listener 必須是 function')
}
}
// 移除監(jiān)聽(tīng)的方法
Producer.prototype.removeListener = function(listener) {
this.listeners.splice(this.listeners.indexOf(listener), 1)
}
// 發(fā)送通知的方法
Producer.prototype.notify = function(message) {
this.listeners.forEach(listener => {
listener(message);
})
}
這裡用到了 this, prototype 等觀念,大家不了解可以去看我的一支影片專門講解這幾個(gè)觀念!
附上 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);
})
}
}
有了上面的程式碼后,我們就可以來(lái)建立物件實(shí)例了
var egghead = new Producer();
// new 出一個(gè) Producer 實(shí)例叫 egghead
function listener1(message) {
console.log(message + 'from listener1');
}
function listener2(message) {
console.log(message + 'from listener2');
}
egghead.addListener(listener1); // 注冊(cè)監(jiān)聽(tīng)
egghead.addListener(listener2);
egghead.notify('A new course!!') // 當(dāng)某件事情方法時(shí),執(zhí)行
當(dāng)我們執(zhí)行到這裡時(shí),會(huì)印出:
a new course!! from listener1
a new course!! from listener2
每當(dāng) egghead.notify 執(zhí)行一次,listener1 跟 listener2 就會(huì)被通知,而這些 listener 可以額外被添加,也可以被移除!
雖然我們的實(shí)作很簡(jiǎn)單,但它很好的說(shuō)明了 Observer Pattern 如何在事件(event)跟監(jiān)聽(tīng)者(listener)的互動(dòng)中做到去藕合(decoupling)。
Iterator Pattern
Iterator 是一個(gè)物件,它的就像是一個(gè)指針(pointer),指向一個(gè)資料結(jié)構(gòu)并產(chǎn)生一個(gè)序列(sequence),這個(gè)序列會(huì)有資料結(jié)構(gòu)中的所有元素(element)。
先讓我們來(lái)看看原生的 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 }
JavaScript 到了 ES6 才有原生的 Iterator
在 ECMAScript 中 Iterator 最早其實(shí)是要採(cǎi)用類似 Python 的 Iterator 規(guī)范,就是 Iterator 在沒(méi)有元素之后,執(zhí)行
next會(huì)直接拋出錯(cuò)誤;但后來(lái)經(jīng)過(guò)一段時(shí)間討論后,決定採(cǎi)更 functional 的做法,改成在取得最后一個(gè)元素之后執(zhí)行next永遠(yuǎn)都回傳{ done: true, value: undefined }
JavaScript 的 Iterator 只有一個(gè) next 方法,這個(gè) next 方法只會(huì)回傳這兩種結(jié)果:
- 在最后一個(gè)元素前:
{ done: false, value: elem } - 在最后一個(gè)元素之后:
{ done: true, value: undefined }
當(dāng)然我們可以自己實(shí)作簡(jiǎn)單的 Iterator Pattern
function IteratorFromArray(arr) {
if(!(this instanceof IteratorFromArray)) {
throw new Error('請(qǐng)用 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 };
}
}
Iterator Pattern 雖然很單純,但同時(shí)帶來(lái)了兩個(gè)優(yōu)勢(shì),第一它漸進(jìn)式取得資料的特性可以拿來(lái)做延遲運(yùn)算(Lazy evaluation),讓我們能用它來(lái)處理大資料結(jié)構(gòu)。第二因?yàn)?iterator 本身是序列,所以可以實(shí)作所有陣列的運(yùn)算方法像 map, filter... 等!
這裡我們利用最后一段程式碼實(shí)作 map 試試
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 }
補(bǔ)充: 延遲運(yùn)算(Lazy evaluation)
延遲運(yùn)算,或說(shuō) call-by-need,是一種運(yùn)算策略(evaluation strategy),簡(jiǎn)單來(lái)說(shuō)我們延遲一個(gè)表達(dá)式的運(yùn)算時(shí)機(jī)直到真正需要它的值在做運(yùn)算。
以下我們用 generator 實(shí)作 iterator 來(lái)舉一個(gè)例子
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 }
這裡我們寫了一個(gè)函式用來(lái)抓取字串中的數(shù)字,在這個(gè)函式中我們用 for...of 的方式來(lái)取得每個(gè)字元并用正則表示式來(lái)判斷是不是數(shù)值,如果為真就轉(zhuǎn)成數(shù)值并回傳。當(dāng)我們把一個(gè)字串丟進(jìn) getNumbers 函式時(shí),并沒(méi)有馬上運(yùn)算出字串中的所有數(shù)字,必須等到我們執(zhí)行 next() 時(shí),才會(huì)真的做運(yùn)算,這就是所謂的延遲運(yùn)算(evaluation strategy)
Observable
在了解 Observer 跟 Iterator 后,不知道大家有沒(méi)有發(fā)現(xiàn)其實(shí) Observer 跟 Iterator 有個(gè)共通的特性,就是他們都是 漸進(jìn)式(progressive) 的取得資料,差別只在于 Observer 是生產(chǎn)者(Producer)推送資料(push),而 Iterator 是消費(fèi)者(Consumer)要求資料(pull)!

Observable 其實(shí)就是這兩個(gè) Pattern 思想的結(jié)合,Observable 具備生產(chǎn)者推送資料的特性,同時(shí)能像序列,擁有序列處理資料的方法(map, filter...)!
更簡(jiǎn)單的來(lái)說(shuō),Observable 就像是一個(gè)序列,裡面的元素會(huì)隨著時(shí)間推送。
注意這裡講的是 思想的結(jié)合,Observable 跟 Observer 在實(shí)作上還是有差異,這我們?cè)谙乱黄恼轮兄v到。
今日小結(jié)
今天講了 Iterator 跟 Observer 兩個(gè) Pattern,這兩個(gè) Pattern 都是漸進(jìn)式的取得元素,差異在于 Observer 是靠生產(chǎn)者推送資料,Iterator 則是消費(fèi)者去要求資料,而 Observable 就是這兩個(gè)思想的結(jié)合!
今天的觀念需要比較多的思考,希望讀者能多花點(diǎn)耐心想一想,如果有任何問(wèn)題請(qǐng)?jiān)谙路搅粞越o我。