函数式编程 Functional Programming

什么是函数式编程

函数式编程是一种计划简略、性能独立、对作用域外没有任何副作用的编程范式(paradigms):INPUT -> PROCESS -> OUTPUT

函数式编程:

1)性能独立——不依赖于程序的状态(比方可能发生变化的全局变量);

2)纯函数——同一个输出永远能失去同一个输入;

3)无限的副作用——能够严格地限度函数内部对状态的更改。

函数式编程中应用较多的变量和函数,并且只有须要计算时才把数据传给函数,其余状况下函数和数据是拆散的。函数会返回新数值并传递到代码的其余局部。而在面向对象编程中,变量和函数被写在一个对象外部。

一些术语 Terminology

  • 回调 Callbacks:是被传递到另一个函数中调用的函数。 例如在 filter 中,回调函数通知 JavaScript 以什么规定过滤数组。
  • 第一等公民 First-class citizens:函数就像数值一样,能够赋值给变量、传递给另一个函数,或从其它函数返回,这种函数叫做头等 first class 函数。 在 JavaScript 中,所有函数都是头等函数。
  • 高阶函数 Higher order:将其余函数作为参数或/和被调用时返回函数的函数。当函数被传递给另一个函数或从另一个函数返回时,那些传入或返回的函数能够叫做 lambda。
  • 纯函数和副作用 Pure functions and side effects:纯函数指同一输出失去同一输入,传函数没有副作用。副作用指函数使其内部产生扭转的结果,包含:

    • 扭转函数外的变量值或者依赖函数外的变量
    • 调用浏览器 API,甚至是控制台
    • 调用相似 Math.random() 这样的办法 - 因为这个值不能牢靠地反复
const prepareGreenTea = () => 'greenTea';const prepareBlackTea = () => 'blackTea';const getTea = (prepareTea, numOfCups) => {  const teaCups = [];  for(let cups = 1; cups <= numOfCups; cups += 1) {    const teaCup = prepareTea();    teaCups.push(teaCup);  }  return teaCups;};const tea4GreenTeamFCC = getTea(prepareGreenTea, 27);const tea4BlackTeamFCC = getTea(prepareBlackTea, 13);console.log(  tea4GreenTeamFCC,  tea4BlackTeamFCC);

副作用和防止变动 Avoid Mutation and Side Effect

函数式编程的外围准则之一是不扭转任何货色。 变动会导致谬误。 如果一个函数不扭转传入的参数、全局变量等数据,那么它造成问题的可能性就会小很多。

在函数式编程中,扭转或变更叫做 mutation,这种扭转的后果叫做“副作用”(side effect)。 现实状况下,函数应该是不会产生任何副作用的 pure function (纯函数)。

let fixedValue = 4;function incrementer() {    return fixedValue + 1;} // 不能写 fixedValue++;

传参以防止内部依赖

函数式编程的另一个准则是:总是显式申明依赖关系。 如果函数依赖于一个变量或对象,那么将该变量或对象作为参数间接传递到函数中。

这样做会有很多益处。 其中一点是让函数更容易测试,因为晓得参数是什么,并且这个参数也不依赖于程序中的任何其余内容。

其次,这样做能够更加自信地更改,删除或增加新代码。 因为很分明哪些是能够改的,哪些是不能够改的,这样就晓得哪里可能会有潜在的陷阱。

最初,无论代码的哪一部分执行它,函数总是会为同一组输出生成雷同的输入。

let fixedValue = 4;function incrementer(num) {  return num + 1;}

重构全局变量 Refactor

重构代码,使全局数组 bookList 在函数外部不会被扭转。 add 函数能够将指定的 bookName 减少到数组开端并返回一个新的数组(列表)。 remove 函数能够从数组中移除指定 bookName

// 全局变量const bookList = ["The Hound of the Baskervilles", "On The Electrodynamics of Moving Bodies", "Philosophiæ Naturalis Principia Mathematica", "Disquisitiones Arithmeticae"];// 批改这行上面的代码function add (book,bookName) {  let newBook = [...book];  newBook.push(bookName);  return newBook;  // 批改这行下面的代码}// 批改这行上面的代码function remove (book,bookName) {  let newBook = [...book];  const book_index = newBook.indexOf(bookName);  if (book_index >= 0) {    newBook.splice(book_index, 1);    return newBook;    // 批改这行下面的代码    }}const newBookList = add(bookList, 'A Brief History of Time');const newerBookList = remove(bookList, 'On The Electrodynamics of Moving Bodies');const newestBookList = remove(add(bookList, 'A Brief History of Time'), 'On The Electrodynamics of Moving Bodies');console.log(bookList);

map 迭代

函数在 JavaScript 中被视为 First Class Objects,它们能够像任何其余对象一样应用。 它们能够保留在变量中,存储在对象中,也能够作为函数参数传递。

数组的 map 办法(即 Array.prototype.map())。请记住,map办法是迭代数组中每一项的形式之一。 在对每个元素利用回调函数后,它会创立一个新数组(不扭转原来的数组)。 它这样做时没有扭转原始数组。

当调用回调函数时,传入了三个参数。 第一个参数是以后正在解决的数组项。 第二个参数是以后数组项的索引值,第三个参数是在其上调用 map 办法的数组。

看下在 users 上应用 map 办法的例子,返回了一个新数组只蕴含了用户的名字。 为了简化,例子里只应用了回调函数的第一个参数。

const users = [  { name: 'John', age: 34 },  { name: 'Amy', age: 20 },  { name: 'camperCat', age: 10 }];const names = users.map(user => user.name);console.log(names);// [ 'John', 'Amy', 'camperCat' ]

实现 map

const s = [23, 65, 98, 5];Array.prototype.myMap = function(callback) {var newArray = [];for (let i = 0; i < this.length; i++) {    newArray.push(callback(this[i]));  }  return newArray;};const new_s = s.myMap(function(item) {  return item * 2;});

filter 从数组提取数据

另一个有用的数组办法是 filter()(即 Array.prototype.filter())。

filter 接管一个回调函数,将回调函数内的逻辑利用于数组的每个元素,新数组蕴含依据回调函数内条件返回 true 的元素。 换言之,它依据传递给它的函数过滤数组。 和 map 一样,filter 不会扭转原始数组。

回调函数接管三个参数。 第一个参数是以后正在被解决的元素。 第二个参数是这个元素的索引,第三个参数是在其上调用 filter 办法的数组。

看下在 users 上应用 filter 办法的例子,返回了一个蕴含了 30 岁以下的用户新数组。 为了简化,例子里只应用了回调函数的第一个参数。

const users = [  { name: 'John', age: 34 },  { name: 'Amy', age: 20 },  { name: 'camperCat', age: 10 }];const usersUnder30 = users.filter(user => user.age < 30);console.log(usersUnder30);  //  [ { name: 'Amy', age: 20 }, { name: 'camperCat', age: 10 } ]

实现 filter

const s = [23, 65, 98, 5];Array.prototype.myFilter = function(callback) {   let newArray = [];  this.forEach(function(x) {    if (callback(x) == true) {      newArray.push(x);    }  });  return newArray;};const new_s = s.myFilter(function(item) {  return item % 2 === 1;});

concat 拼接

Concatenation 意思是将元素连贯到尾部。 同理,JavaScript 为字符串和数组提供了concat办法。 对数组来说,在一个数组上调用 concat 办法,而后提供另一个数组作为参数增加到第一个数组开端。 它返回一个新数组,不会扭转任何一个原始数组。

function nonMutatingConcat(original, attach) {  return original.concat(attach);}

reduce 剖析数据

reduce()(即Array.prototype.reduce()),是 JavaScript 所有数组操作中最罕用的办法。 简直能够用reduce办法解决所有数组解决问题。

reduce办法是解决数组更通用的形式,而且filtermap办法都能够当作是reduce的非凡实现。 reduce办法遍历数组中的每个我的项目并返回单个值(即字符串、数字、对象、数组)。 这是通过在每次迭代中调用一个回调函数来实现的。

回调函数承受四个参数。 第一个参数称为叠加器,它是上一次迭代中回调函数的返回值,第二个参数是以后正在解决的数组元素,第三个参数是该参数的索引,第四个参数是在其上调用 reduce 办法的数组。

除了回调函数,reduce 还有一个额定的参数做为叠加器的初始值。 如果没有第二个参数,会跳过第一次迭代,第二次迭代给叠加器传入数组的第一个元素。

见上面的例子,给 users 数组应用 reduce 办法,返回所有用户数组的和。 为了简化,例子仅应用了回调函数的第一个参数和第二个参数。

const users = [  { name: 'John', age: 34 },  { name: 'Amy', age: 20 },  { name: 'camperCat', age: 10 }];const sumOfAges = users.reduce((sum, user) => sum + user.age, 0);console.log(sumOfAges); // 64

sort 排序

sort 办法能够依据回调函数对数组元素进行排序。

JavaScript 的默认排序办法是 Unicode 值程序排序,有时可能会失去意想不到的后果。 因而,倡议提供一个回调函数来指定如何对数组我的项目排序。 这个回调函数通常叫做 compareFunction,它依据 compareFunction 的返回值决定数组元素的排序形式: 如果两个元素 abcompareFunction(a,b) 返回一个比 0 小的值,那么 a 会在 b 的后面。 如果两个元素 abcompareFunction(a,b) 返回一个比 0 大的值,那么 b 会在 a 的后面。 如果两个元素 abcompareFunction(a,b) 返回等于 0 的值,那么 ab 的地位放弃不变。

function alphabeticalOrder(arr) {return arr.sort(function (a, b) {    return a === b ? 0 : a < b ? -1 : 1;  })}alphabeticalOrder(["a", "d", "c", "a", "z", "g"]);

split 拆分字符串成数组

split 办法将一个字符串宰割成一个字符串数组。 它须要一个参数作为分隔符,它能够是用于拆分字符串或正则表达式的一个字符。 举个例子,如果分隔符是空格,失去一个单词数组;如果分隔符是空字符串,失去一个由字符串中每个字符组成的数组。

function splitify(str) {  return str.split(/\W/);}splitify("Hello World,I-am code");

join 数组组合成字符串

join 办法用来把数组中的所有元素放入一个字符串。 并通过指定的分隔符参数进行分隔。

function sentensify(str) {  return str.split(/\W/).join(" ");}sentensify("May-the-force-be-with-you");

every 全副元素

every 办法用于检测数组中所有 元素是否都合乎指定条件。 如果所有元素满足条件,返回布尔值 true,反之返回 false

function checkPositive(arr){  return arr.every(item => item > 0);}

some 局部元素

some 办法用于检测数组中任何 元素是否满足指定条件。 如果有一个元素满足条件,返回布尔值 true,反之返回 false

function checkPositive(arr) {  return arr.some(item => item > 0);}

函数柯里化和部分调用 Currying and Partial Application

arity(参数个数)是函数所需的形参的数量。 函数柯里化(Currying)意思是把承受多个 arity 的函数变换成承受繁多 arity 的函数。

换句话说,就是重构函数让它接管一个参数,而后返回接管下一个参数的函数举个例子:

function unCurried(x, y) {  return x + y;}function curried(x) {  return function(y) {    return x + y;  }}const curried = x => y => x + ycurried(1)(2) // 3

柯里化在不能一次为函数提供所有参数状况下很有用。 因为它能够将每个函数的调用保留到一个变量中,该变量将保留返回的函数援用,该援用在下一个参数可用时承受该参数。 上面是应用柯里化函数的例子:

const funcForY = curried(1);console.log(funcForY(2)); // 3

相似地,部分调用( partial application)的意思是一次对一个函数利用几个参数,而后返回另一个利用更多参数的函数。 这是一个示例:

function impartial(x, y, z) {  return x + y + z;}const partialFn = impartial.bind(this, 1, 2);partialFn(10); // 13