函数式编程是一种编程范式,主要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。函数式编程意味着你可以在更短的时间内编写具有更少错误的代码。
1 纯函数定义
函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
-
在react开发中纯函数是被多次提及的;
-
比如react中组件就被要求像是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,也是要求必须是一个纯函数;
-
所以掌握纯函数对于理解很多框架的设计是非常有帮助的;
纯函数的定义:
1.确定的输入,一定会产生确定的输出;
2.函数在执行过程中,不能产生副作用;
那么这里又有一个概念,叫做副作用,什么又是副作用呢?
**副作用(side effect):在计算机f科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储;
2 纯函数的案例
var names = ["abc", "cba", "nba", "dna"]
// slice只要给它传入一个start/end, 那么对于同一个数组来说, 它会给我们返回确定的值
// slice函数本身它是不会修改原来的数组
// slice -> this
// slice函数本身就是一个纯函数
// var newNames1 = names.slice(0, 3)
// console.log(newNames1)
// console.log(names)
// ["abc", "cba", "nba", "dna"]
// splice在执行时, 有修改掉调用的数组对象本身, 修改的这个操作就是产生的副作用
// splice不是一个纯函数
var newNames2 = names.splice(2)
console.log(newNames2)// ['nba', 'dna']
console.log(names)//(2) ['abc', 'cba']
var fruits = ["Banana", "Orange", "Apple", "Mango"];
var newFruits = fruits.splice(2, 1, "Lemon", "Kiwi");
console.log(newFruits);//['Apple']
console.log(fruits);//['Banana', 'Orange', 'Lemon', 'Kiwi', 'Mango']
//splice() 方法向/从数组添加/删除项目,并返回删除的项目。
3 纯函数的优势
为什么纯函数在函数式编程中非常重要呢?
-
因为你可以安心的编写和安心的使用;
-
你在写的时候保证了函数的纯度,只是单纯实现自己的业务逻辑即可,不需要关心传入的内容是如何获得的或
者依赖其他的外部变量是否已经发生了修改;
- 你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出;
4 JavaScript柯里化
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化;
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数;
这个过程就称之为柯里化;
5 柯里化的结构
function add(x, y, z){
return x + y + z
}
var result = add(10, 20, 30)
console.log(result);
//对上面的函数进行柯里化
function sum1(x){
return function(y){
return function(z){
return x + y + z
}
}
}
var result1 = sum1(10)(20)(30)
console.log(result1);
/* var sum2 = (x) => {
return (y) => {
return (z) => {
return x + y + z
}
}
}
var result2 = sum2(10)(20)(30);
console.log(result2); */
//简化箭头函数 如果参数只有一个, ()可以省略
//step1
/* var sum2 = x => {
return y => {
return z => {
return x + y + z
}
}
} */
//如果函数执行体只有一行代码, 那么{}也可以省略
// 强调: 并且在{}省略的前提下它会默认将这行代码的执行结果作为返回值
//step2
var sum2 = x => y => z => x + y + z;
var result2 = sum2(10)(20)(30);
console.log(result2);
6 为什么需要有柯里化
1.让函数的职责单一
- 在函数式编程中,我们其实往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个
函数来处理;
- 那么我们是否就可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结
果;
- 比如上面的案例我们进行一个修改:传入的函数需要分别被进行如下处理
第一个参数 + 2
第二个参数 * 2
第三个参数 平方
function foo(x){
x = x + 2;
return function(y){
y = y * 2;
return function(z){
z = z*z
return x + y + z
}
}
2.复用参数逻辑
对一些逻辑做一个复用
-
makeAdder函数要求我们传入一个count(并且如果我们需要的话,可以在这里对count进行一些修改);
-
在之后使用返回的函数时,我们不需要再继续传入count了;
// function sum(m, n) {
// return m + n
// }
// // 假如在程序中,我们经常需要把5和另外一个数字进行相加
// console.log(sum(5, 10))
// console.log(sum(5, 14))
// console.log(sum(5, 1100))
// console.log(sum(5, 555))
function makeAdder(count) {
count = count * count
return function(num) {
return count + num
}
}
// var result = makeAdder(5)(10)
// console.log(result)
var adder5 = makeAdder(5)
adder5(10)
adder5(14)
adder5(1100)
adder5(555)
逻辑的复用案例
function log(date, type, message){
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`);
}
// log(new Date(), "DEBUG", "查找到轮播图的bug")
// log(new Date(), "DEBUG", "查询菜单的bug")
// log(new Date(), "DEBUG", "查询数据的bug")
//// 柯里化的优化
var log = date => type => message =>{
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`);
}
// 如果我现在打印的都是当前时间
var nowLog = log(new Date());
console.log(nowLog("Debug")("查找到轮播图的bug"));
console.log(nowLog("FEATURE")("查找到轮播图的bug"));
//如果都是"DEBUG"
var nowAndDebugLog = log(new Date())("DEBUG");
console.log(nowAndDebugLog("查询数据的bug"));
console.log(nowAndDebugLog("查询菜单的bug"));
7 js字符模板串
ES6中引入了模板字符串,让我们告别了使用大量'' 和 +来连接字符串了写法。
要创造一个模板字符串,只需要使用反引号``将字符串包起来,模板字符串中的变量用${变量名}替代即可
var a = 'o,';var b = 'd!'
console.log (`Hell${a}worl${b}`);
// Hello,world!
1、多行字符串
在模板字符串内,换行和空格是会保存并展示出来的
var a = 'o,';var b = 'd!'
console.log(`Hell${a}
worl${b}`);
/*
Hello,
world!
*/
2、嵌入表达式
var a = 1; b = 2;
console.log (`${a} + ${b} = ${a + b} `)
//1 + 2 = 3
1
2
3
3、调用函数
function string(){
return "Hello!";
}
console.log(`${string()} world!`);
//Hello! world!
8 柯里化函数的实现
function add(x, y, z){
return x + y + z
}
/* function hyCurring(fn){
var myRet = function(...arg){
if (fn.length > arg.length){
return function(...arg1){
if(fn.length > (arg.length + arg1.length)){
return function(...arg2){
if(fn.length > (arg.length + arg1.length + arg2.length)){
return 1111
}else{
return fn(...arg, ...arg1, ...arg2)
}
}
}else{
return fn(...arg,...arg1)
}
}
}else{
return fn(...arg)
}
}
return myRet
} */
function hyCurring(fn){
//fn.length 得到函数fn有几个参数
function curried(...arg){
// 判断当前已经接收的参数的个数, 可以参数本身需要接受的参数是否已经一致了
// 1.当已经传入的参数 大于等于 需要的参数时, 就执行函数
if (arg.length >= fn.length){
return fn.apply(this, arg)
}else{
// 没有达到个数时, 需要返回一个新的函数, 继续来接收的参数
return function(...arg2){
// 接收到参数后, 需要递归调用curried来检查函数的个数是否达到
return curried.apply(this, arg.concat(arg2))
}
}
}
return curried
}
var curryAdd = hyCurring(add);
console.log(curryAdd(10)(20)(30));
console.log(curryAdd(10, 20)(30));
console.log(curryAdd(10, 20, 30));
9 理解组合函数
组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式:
- 比如我们现在需要对某一个数据进行函数的调用,执行两个函数double和square,这两个函数是依次执行的;
- 那么如果每次我们都需要进行两个函数的调用,操作上就会显得重复;
- 那么是否可以将这两个函数组合起来,自动依次调用呢?
- 这个过程就是对函数的组合,我们称之为组合函数(Compose Function);
function double(num) {
return num * 2
}
function square(num) {
return num ** 2
}
var count = 10
var result = square(double(count))
console.log(result)
// 实现最简单的组合函数
function composeFn(m, n) {
return function(count) {
return n(m(count))
}
}
var newFn = composeFn(double, square)
console.log(newFn(10))
10 通用的组合函数的实现
function double(m) {
return m * 2
}
function square(n) {
return n * n
}
function add(j){
return j + 120
}
//var newFn = hyCompose(double, square, add)
var newFn = hyCompose()
console.log(newFn(10))//[10]
function hyCompose(...fns){
var length = fns.length;
for(var i = 0; i < length; i++){
if(typeof fns[i] !== 'function'){
throw new TypeError("Expected arguments are functions")
}
}
function composed(...args){
var result = length? fns[0].apply(this, args):args;
for(var i = 1; i < fns.length; i++){
result = fns[i].call(this,result)
}
return result
}
return composed
}
网友评论