乐趣区

关于javascript:JavaScript-函数式编程

函数式编程 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 + y

curried(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
退出移动版