函数式编程 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
办法是解决数组更通用的形式,而且 filter
和map
办法都能够当作是 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
的返回值决定数组元素的排序形式:如果两个元素 a
和 b
,compareFunction(a,b)
返回一个比 0 小的值,那么 a
会在 b
的后面。如果两个元素 a
和 b
,compareFunction(a,b)
返回一个比 0 大的值,那么 b
会在 a
的后面。如果两个元素 a
和 b
,compareFunction(a,b)
返回等于 0 的值,那么 a
和 b
的地位放弃不变。
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