移植Rxjs中部分常用operators到陣列

BIRD發表於2019-02-28

一段時間的響應式程式設計的研究,對Rxjs有了一些熟悉。雖然工作中絕大部分時候很少會有複雜的需求用到Rxjs,但是對於Rxjs的思想和響應式程式設計、觀察者模式等相關知識是值得學習的!
而且其中的觀察者模式Observable已經列入了JavaScript規範中。Observable 目前處於 stage 1,但它已被 TC39 委員會標記為 “ready to advance” 並獲得了瀏覽器廠商的大力支援,因此有望很快推進到下一階段。

從Rxjs的operators移植一些到日常開發中的可復現場景。

1.合併陣列concatAll
Array.prototype.concatAll = function () {
        let results = [];
        this.forEach((subArray) => {
            results.push.apply(results, subArray);
        });
        return results;
    };

        console.log(JSON.stringify([[1, 2, 3], [4, 5, 6], [7, 8, 9]].concatAll()))  
        //[1,2,3,4,5,6,7,8,9]
複製程式碼

當然,js陣列的concat方法也可實現,eg

console.log(JSON.stringify([1, 2, 3].concat([4, 5, 6], [7, 8, 9])))    
//[1,2,3,4,5,6,7,8,9]
複製程式碼

但concatAll是為擴充套件後文的幾個operator而重新定義的合併陣列的方法,看後文

2.concatMap

concatMap:map()+concatAll()

Array.prototype.concatMap = function (projectionFunctionThatReturnsArray) {
        return this.map(function (item) {
            return projectionFunctionThatReturnsArray(item);
        }).
            // 使用concatAll方法來打平陣列 
            concatAll();
    };


    let spanishFrenchEnglishWords = [["cero", "rien", "zero"], ["uno", "un", "one"], ["dos", "deux", "two"]];
    // map返回三個陣列,concatAll返回打平後的一個陣列
    let allWords = [0, 1, 2].
        concatMap(index => spanishFrenchEnglishWords[index]);

    console.log(JSON.stringify(allWords))
    // ["cero","rien","zero","uno","un","one","dos","deux","two"]
複製程式碼

常用場景:打平陣列,獲取陣列中的深層資料,eg:

let movieLists = [
        {
            name: "Instant Queue",
            videos: [
                {
                    "id": 70111470,
                    "title": "Die Hard",
                    "boxarts": [
                        { width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard150.jpg" },
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard200.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 4.0,
                    "bookmark": []
                },
                {
                    "id": 654356453,
                    "title": "Bad Boys",
                    "boxarts": [
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys200.jpg" },
                        { width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys150.jpg" }

                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 5.0,
                    "bookmark": [{ id: 432534, time: 65876586 }]
                }
            ]
        },
        {
            name: "New Releases",
            videos: [
                {
                    "id": 65432445,
                    "title": "The Chamber",
                    "boxarts": [
                        { width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/TheChamber150.jpg" },
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/TheChamber200.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 4.0,
                    "bookmark": []
                },
                {
                    "id": 675465,
                    "title": "Fracture",
                    "boxarts": [
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture200.jpg" },
                        { width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture150.jpg" },
                        { width: 300, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture300.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 5.0,
                    "bookmark": [{ id: 432534, time: 65876586 }]
                }
            ]
        }
    ];

    const data = movieLists.concatMap((movieList) => {
        return movieList.videos.concatMap((video) => {
            return video.boxarts.
                filter((boxart) => boxart.width === 150 && boxart.height === 200).
                map((boxart) => {
                    return { id: video.id, title: video.title, boxart: boxart.url };
                });
        });
    });
    console.log(JSON.stringify(data))
    
    /*   [
         {"id":70111470,"title":"Die Hard","boxart":"http://cdn-0.nflximg.com/images/2891/DieHard150.jpg"},
         {"id":654356453,"title":"Bad Boys","boxart":"http://cdn-0.nflximg.com/images/2891/BadBoys150.jpg"},
         {"id":65432445,"title":"The Chamber","boxart":"http://cdn-0.nflximg.com/images/2891/TheChamber150.jpg"},
         {"id":675465,"title":"Fracture","boxart":"http://cdn-0.nflximg.com/images/2891/Fracture150.jpg"}
        ] 
         */
複製程式碼
3.reduceArray

即reduce並返回陣列,為的是能鏈式呼叫,以結合map等方法

// [1,2,3].reduceArray(function(accumulatedValue, currentValue) { return accumulatedValue + currentValue; }); === [6];
    // [1,2,3].reduceArray(function(accumulatedValue, currentValue) { return accumulatedValue + currentValue; }, 10); === [16];
    Array.prototype.reduceArray = function (combiner, initialValue) {
        let counter,
            accumulatedValue;
        if (this.length === 0) {
            return this;
        }
        else {
            if (arguments.length === 1) {
                counter = 1;
                accumulatedValue = this[0];
            }
            else if (arguments.length >= 2) {
                counter = 0;
                accumulatedValue = initialValue;
            }
            else {
                throw "Invalid arguments.";
            }

            while (counter < this.length) {
                accumulatedValue = combiner(accumulatedValue, this[counter])
                counter++;
            }
            return [accumulatedValue];
        }
    };

    let movieLists = [
        {
            name: "New Releases",
            videos: [
                {
                    "id": 70111470,
                    "title": "Die Hard",
                    "boxarts": [
                        { width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard150.jpg" },
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard200.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 4.0,
                    "bookmark": []
                },
                {
                    "id": 654356453,
                    "title": "Bad Boys",
                    "boxarts": [
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys200.jpg" },
                        { width: 140, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys140.jpg" }

                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 5.0,
                    "bookmark": [{ id: 432534, time: 65876586 }]
                }
            ]
        },
        {
            name: "Thrillers",
            videos: [
                {
                    "id": 65432445,
                    "title": "The Chamber",
                    "boxarts": [
                        { width: 130, height: 200, url: "http://cdn-0.nflximg.com/images/2891/TheChamber130.jpg" },
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/TheChamber200.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 4.0,
                    "bookmark": []
                },
                {
                    "id": 675465,
                    "title": "Fracture",
                    "boxarts": [
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture200.jpg" },
                        { width: 120, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture120.jpg" },
                        { width: 300, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture300.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 5.0,
                    "bookmark": [{ id: 432534, time: 65876586 }]
                }
            ]
        }
    ];

    /* 使用concatMap,map,reduceArray來生成以下陣列,注:返回的是width*height最小的
    [
        {"id": 675465,"title": "Fracture","boxart":"http://cdn-0.nflximg.com/images/2891/Fracture120.jpg" },
        {"id": 65432445,"title": "The Chamber","boxart":"http://cdn-0.nflximg.com/images/2891/TheChamber130.jpg" },
        {"id": 654356453,"title": "Bad Boys","boxart":"http://cdn-0.nflximg.com/images/2891/BadBoys140.jpg" },
        {"id": 70111470,"title": "Die Hard","boxart":"http://cdn-0.nflximg.com/images/2891/DieHard150.jpg" }
    ]; */
    
    const data = movieLists.concatMap(type => {
        return type.videos.concatMap(video => {
            return video.boxarts.reduceArray((prev, next) => {
                return (prev.width * prev.height < next.width * next.height) ? prev : next;
            }).map(boxart => {
                return { id: video.id, title: video.title, boxart: boxart.url }
            })
        })
    })

    console.log(data)
複製程式碼

4.zip

給陣列新增一個靜態方法zip(),zip接收三個引數(第一個陣列的元素,第二個陣列中與第一個陣列index相同的元素,對此兩個元素進行的操作).由於zip方法需要2個陣列中各自的一個元素,所以zip方法返回的元素個數與輸入的兩個陣列中的短陣列length相同

// JSON.stringify(Array.zip([1,2,3],[4,5,6,7,8], function(left, right) { return left + right })) === `[5,7,9]`

    Array.zip = function (left, right, combinerFunction) {
        let counter,
            results = [];
        for (counter = 0; counter < Math.min(left.length, right.length); counter++) {
            results.push(combinerFunction(left[counter], right[counter]));
        }
        return results;
    };
    
複製程式碼

zip()常用於組合兩個陣列,返回的陣列由輸入的兩個陣列中的元素組成,eg:

   let movieLists = [
        {
            name: "New Releases",
            videos: [
                {
                    "id": 70111470,
                    "title": "Die Hard",
                    "boxarts": [
                        { width: 150, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard150.jpg" },
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/DieHard200.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 4.0,
                    "interestingMoments": [
                        { type: "End", time: 213432 },
                        { type: "Start", time: 64534 },
                        { type: "Middle", time: 323133 }
                    ]
                },
                {
                    "id": 654356453,
                    "title": "Bad Boys",
                    "boxarts": [
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys200.jpg" },
                        { width: 140, height: 200, url: "http://cdn-0.nflximg.com/images/2891/BadBoys140.jpg" }

                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 5.0,
                    "interestingMoments": [
                        { type: "End", time: 54654754 },
                        { type: "Start", time: 43524243 },
                        { type: "Middle", time: 6575665 }
                    ]
                }
            ]
        },
        {
            name: "Instant Queue",
            videos: [
                {
                    "id": 65432445,
                    "title": "The Chamber",
                    "boxarts": [
                        { width: 130, height: 200, url: "http://cdn-0.nflximg.com/images/2891/TheChamber130.jpg" },
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/TheChamber200.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 4.0,
                    "interestingMoments": [
                        { type: "End", time: 132423 },
                        { type: "Start", time: 54637425 },
                        { type: "Middle", time: 3452343 }
                    ]
                },
                {
                    "id": 675465,
                    "title": "Fracture",
                    "boxarts": [
                        { width: 200, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture200.jpg" },
                        { width: 120, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture120.jpg" },
                        { width: 300, height: 200, url: "http://cdn-0.nflximg.com/images/2891/Fracture300.jpg" }
                    ],
                    "url": "http://api.netflix.com/catalog/titles/movies/70111470",
                    "rating": 5.0,
                    "interestingMoments": [
                        { type: "End", time: 45632456 },
                        { type: "Start", time: 234534 },
                        { type: "Middle", time: 3453434 }
                    ]
                }
            ]
        }
    ];

    const data = movieLists.concatMap(movieList => {
        return movieList.videos.concatMap(video => {
            return Array.zip(
                video.boxarts.reduceArray((acc, curr) => {
                    return (acc.width * acc.height < curr.width * curr.height) ? acc : curr;
                }),
                video.interestingMoments.filter(interestingMoment => {
                    return interestingMoment.type === "Middle";
                }),
                (boxart, interestingMoment) => {
                    return { id: video.id, title: video.title, time: interestingMoment.time, url: boxart.url };
                });
        });
    });
    console.log(JSON.stringify(data))
    
    /*
    [{"id":70111470,"title":"Die Hard","time":323133,"url":"http://cdn-0.nflximg.com/images/2891/DieHard150.jpg"},
    {"id":654356453,"title":"Bad Boys","time":6575665,"url":"http://cdn-0.nflximg.com/images/2891/BadBoys140.jpg"},
    {"id":65432445,"title":"The Chamber","time":3452343,"url":"http://cdn-0.nflximg.com/images/2891/TheChamber130.jpg"},
    {"id":675465,"title":"Fracture","time":3453434,"url":"http://cdn-0.nflximg.com/images/2891/Fracture120.jpg"}]
*/
複製程式碼

總結:

從Rxjs上移植過來的4個操作符,可以很好的擴充套件陣列方法及鏈式呼叫,常用場景有:

  1. 陣列的鏈式呼叫
  2. 陣列的打平
  3. 陣列轉換成Object
  4. 待發掘…

參考:

  1. Rxjs中文文件
  2. 學習 RxJS
  3. Functional Programming in Javascript

相關文章