30 天精通 RxJS (03): Functional Programming 通用函式

30 天精通 RxJS (03): Functional Programming 通用函式

了解 Functional Programming 的通用函式,能让我们写出更简洁的程式码,也能帮助我们学习 RxJS。

这是【30天精通 RxJS】的 03 篇,如果还没看过 02 篇可以往这边走:
30 天精通 RxJS (02): Functional Programming 基本观念

读者可能会很好奇,我们的主题是 RxJS 为什麽要特别讲 Functional Programming 的通用函式呢? 实际上,RxJS 核心的 Observable 操作观念跟 FP 的阵列操作是极为相近的,只学会以下几个基本的方法跟观念后,会让我们之后上手 Observable 简单很多!

今天的程式码比较多,大家可以直接看影片!

视频

ForEach

forEach 是 JavaScript 在 ES5 后,原生就有支援的方法。

原本我们可能要透过 for loop 取出阵列中的每一个元素

var arr = ['Jerry', 'Anna'];

for(var i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

现在可以直接透过阵列的 forEach 取出每一个元素。

var arr = ['Jerry', 'Anna'];

arr.forEach(item => console.log(item));

forEach 是 FP 操作阵列的基本方法,我们可以用这个方法来实作下面三个我们今天要讲的重点分别为 map, filter, concatAll。

Map

试著把 newCourseList 每个元素的 { id, title } 塞到新的阵列 idAndTitlePairs

var newCourseList = [
    {
        "id": 511021,
        "title": "React for Beginners",
        "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
        "rating": 5
    },
    {
        "id": 511022,
        "title": "Vue2 for Beginners",
        "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
        "rating": 5
    },
    {
        "id": 511023,
        "title": "Angular2 for Beginners",
        "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
        "rating": 5
    },
    {
        "id": 511024,
        "title": "Webpack for Beginners",
        "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
        "rating": 4
    }
], idAndTitle = [];

newCourseList.forEach((course) => {
    idAndTitle.push({ id: course.id, title: course.title });
});

虽然我们成功的把 newCourseList 转成 idAndTitlePairs,但这样的写法还是显得有点太複杂了,我们可以用更抽象化的方式来完成。

上面我们练习到 newCourseList 转换成一个新的阵列 idAndTitlePairs,这个转换的过程其实就是两件事

  • 遍历 newCourseList 所有的元素
  • 把每个元素的预期值给到新的阵列

把这个过程抽象化成一个方法 map,以下是简化的基本思路:

  1. 我们会让每个 阵列 都有一个 map 方法
  2. 这个方法会让使用者自订传入一个 callback function
  3. 这个 callback function 会回传使用者预期的元素

虽然 ES5 之后原生的 JavaScript 阵列有 map 方法了,但希望读者自我实做一次,能帮助理解。

// 我们希望每一个阵列都有 map 这个方法,所以我们在 Array.prototype 扩充 map function
Array.prototype.map = function(callback) {
  var result = []; // map 最后一定会返回一个新阵列,所以我们先宣告一个新阵列

  this.forEach(function(element, index) {
      // this 就是呼叫 map 的阵列
      result.push(callback(element, index));
      // 执行使用者定义的 callback, callback 会回传使用者预期的元素,所以我们把它 push 进新阵列
  })

  return result;
}

这裡用到了 JavaScript 的 prototype chain 以及 this 等观念,可以看此影片了解!

到这裡我们就实作完成 map 的方法了,让我们来试试这个方法吧!

var idAndTitle = newCourseList
                 .map((course) => {
                     return { id: course.id, title: course.title };
                 });

可以看到我们的程式码更加的简洁!

Filter

如果我们希望过滤一个阵列,留下阵列中我们想要的元素,并产生一个新的阵列,要怎麽做呢?
先让我们用 forEach 完成!

让我们过滤出 rating 值是 5 的元素

var ratingIsFive = [];

newCourseList.forEach((course) => {
    if(course.rating === 5) {
        ratingIsFive.push(course);
    }
});

同样的我们试著来简化这个过程,首先在这个转换的过程中,我们做了两件事:

  1. 遍历 newCourseList 中的所有元素
  2. 判断元素是否符合条件,符合则加到新的阵列中
Array.prototype.filter = function(callback) {
    var result = [];
    this.forEach((item, index) => {
        if(callback(item, index))
            result.push(item);
    });
    return result;
}

试试这个方法

var ratingIsFive = newCourseList
                   .filter((course) => course.rating === 5);

会发现我们的程式码又变简单了,接著我们试著把 filter, map 串起来。

如果我想要取出所有 rating 是 5 的所有 course title

var ratingIsFive = newCourseList
                   .filter((course) => course.rating === 5)
                   .map(course => course.title);

ConcatAll

有时候我们会遇到组出一个二维阵列,但我们希望阵列是一维的,问题如下:

假如我们要取出 courseLists 中所有 rating 为 5 的课程,这时可能就会用到两个 forEach

var user = {
  id: 888,
  name: 'JerryHong',
  courseLists: [{
    "name": "My Courses",
    "courses": [{
      "id": 511019,
      "title": "React for Beginners",
      "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
      "tags": [{ id: 1, name: "JavaScript" }],
      "rating": 5
    }, {
      "id": 511020,
      "title": "Front-End automat workflow",
      "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
      "tags": [{ "id": 2, "name": "gulp" }, { "id": 3, "name": "webpack" }],
      "rating": 4
    }]
  }, {
    "name": "New Release",
    "courses": [{
      "id": 511022,
      "title": "Vue2 for Beginners",
      "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
      "tags": [{ id: 1, name: "JavaScript" }],
      "rating": 5
    }, {
      "id": 511023,
      "title": "Angular2 for Beginners",
      "coverPng": "https://res.cloudinary.com/dohtkyi84/image/upload/v1481226146/react-cover.png",
      "tags": [{ id: 1, name: "JavaScript" }],
      "rating": 4
    }]
  }]
};

var allCourseIds = [];

user.courseLists.forEach(list => {
  list.courses
    .filter(item => item.rating === 5)
    .forEach(item => {
      allCourseIds.push(item)
    })
})

可以看到上面的程式码,我们用了较为低阶的操作来解决这个问题,我们刚刚已经试著用抽象化的方式实作了 map 跟 filter,那我们同样也能够定义一个方法用来 摊平二维阵列。

让我们来加入一个 concatAll 方法来简化这段程式码吧!
concatAll 要做的事情很简单,就是把一个二维阵列转成一维。

Array.prototype.concatAll = function() {
  var result = [];

  // 用 apply 完成
  this.forEach((array) => {
    result.push.apply(result, array);
  });

  // 用两个 forEach 完成
  // this.forEach((array) => {
  //   array.forEach(item => {
  //     result.push(item)
  //   })
  // });

  // 用 ES6 spread 完成
  // this.forEach((array) => {
  //   result.push(...array);
  // })

  return result;
};

同样的我们用前面定要好的 courseLists 来试试 concatAll 吧!

var allCourseIds = user.courseLists.map(list => {
    return list.courses.filter(course => course.rating === 5)
}).concatAll()

这边出一个比较难的题目,大家可以想想看要怎麽解

var courseLists = [{
  "name": "My Courses",
  "courses": [{
    "id": 511019,
    "title": "React for Beginners",
    "covers": [{
      width: 150,
      height: 200,
      url: "http://placeimg.com/150/200/tech"
    }, {
      width: 200,
      height: 200,
      url: "http://placeimg.com/200/200/tech"
    }, {
      width: 300,
      height: 200,
      url: "http://placeimg.com/300/200/tech"
    }],
    "tags": [{
      id: 1,
      name: "JavaScript"
    }],
    "rating": 5
  }, {
    "id": 511020,
    "title": "Front-End automat workflow",
    "covers": [{
      width: 150,
      height: 200,
      url: "http://placeimg.com/150/200/arch"
    }, {
      width: 200,
      height: 200,
      url: "http://placeimg.com/200/200/arch"
    }, {
      width: 300,
      height: 200,
      url: "http://placeimg.com/300/200/arch"
    }],
    "tags": [{
      "id": 2,
      "name": "gulp"
    }, {
      "id": 3,
      "name": "webpack"
    }],
    "rating": 5
  }]
}, {
  "name": "New Release",
  "courses": [{
    "id": 511022,
    "title": "Vue2 for Beginners",
    "covers": [{
      width: 150,
      height: 200,
      url: "http://placeimg.com/150/200/nature"
    }, {
      width: 200,
      height: 200,
      url: "http://placeimg.com/200/200/nature"
    }, {
      width: 300,
      height: 200,
      url: "http://placeimg.com/300/200/nature"
    }],
    "tags": [{
      id: 1,
      name: "JavaScript"
    }],
    "rating": 5
  }, {
    "id": 511023,
    "title": "Angular2 for Beginners",
    "covers": [{
      width: 150,
      height: 200,
      url: "http://placeimg.com/150/200/people"
    }, {
      width: 200,
      height: 200,
      url: "http://placeimg.com/200/200/people"
    }, {
      width: 300,
      height: 200,
      url: "http://placeimg.com/300/200/people"
    }],
    "tags": [{
      id: 1,
      name: "JavaScript"
    }],
    "rating": 5
  }]
}];

/* 
var result = courseList
不得直接使用索引 covers[0],请用 concatAll, map, filter, forEach 完成
result 结果为 [
    {
      id: 511019,
      title: "React for Beginners",
      cover: "http://placeimg.com/150/200/tech"
    }, {
      id: 511020,
      title: "Front-End automat workflow",
      cover: "http://placeimg.com/150/200/arch"
    }, {
      id: 511022,
      title: "Vue2 for Beginners",
      cover: "http://placeimg.com/150/200/nature"
    }, {
      id: 511023,
      title: "Angular2 for Beginners",
      cover: "http://placeimg.com/150/200/people"
    },
 ]
*/

练习连结: JSBin | JSFiddle

这题有点难,大家可以想想看,我把答案写在这裡了!

如果大家还想做更多的练习可以到这个连结:http://reactivex.io/learnrx/

这个连结是 Jafar 大神为他的 RxJS workshop 所做的练习网站!

今日小结

今天讲了 FP 操作阵列的三个通用函式 forEach, map, filter,以及我们自己定义的一个方法叫 concatAll。这几天我们把学习 RxJS 的前置观念跟知识基本上都讲完了,明天我们就开始进入 RxJS 的重点核心 Observable 萝!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容