移植Rxjs中部分常用operators到数组

1,130 阅读5分钟

一段时间的响应式编程的研究,对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