30 天精通 RxJS (04): 什么是 Observable ?

這是轉(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í)行一次,listener1listener2 就會(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é)果:

  1. 在最后一個(gè)元素前: { done: false, value: elem }
  2. 在最后一個(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)!

image.png

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我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • 介紹 RxJS是一個(gè)異步編程的庫(kù),同時(shí)它通過(guò)observable序列來(lái)實(shí)現(xiàn)基于事件的編程。它提供了一個(gè)核心的類型:...
    泓滎閱讀 16,764評(píng)論 0 12
  • 本篇文章介主要紹RxJava中操作符是以函數(shù)作為基本單位,與響應(yīng)式編程作為結(jié)合使用的,對(duì)什么是操作、操作符都有哪些...
    嘎啦果安卓獸閱讀 2,973評(píng)論 0 10
  • “羊有跪乳之恩,鴉有反哺之義” 01 今晚回家回得早,媽媽洗過(guò)澡準(zhǔn)備睡覺(jué),我突然間想起有件很久就想做,而一直沒(méi)有做...
    Coco賴閱讀 406評(píng)論 4 2
  • 中午去參加一個(gè)宴會(huì),跟同事英子姐坐位相臨,平日里大家聯(lián)系也不多,可近距離接觸,卻不禁讓我刮目相看。 原因是,她的兒...
    安然ZCR閱讀 1,118評(píng)論 6 3

友情鏈接更多精彩內(nèi)容