到底什么函数式编程?

其实函数式编程是一种编程范式除了函数式编程之外还有 命令式编程,声明式编程 等编程范式

命令式编程

命令式编程 面向
计算机硬件抽象,有变量赋值语句、表达式、控制语句等,可以理解命令式编程就是
冯诺伊曼的指令序列 它的主要思想关注计算机执行步骤,即一步一步告诉计算机先做什么再做什么

比如我们查找数组 numList大于5的所有数字需要这样告诉计算机:

  1. 创建一个存储结果集合变量 results
  2. 遍历这个数字集合 numList
  3. 一个一个判断个数字是不是大于 5,如果是就将这个数字添加结果集合变量 results 中。
let results = [];
for(let i = 0; i < numList.length; i++){
    if(numList[i] > 5){
        results.push(numList[i])
    }
}

声明式编程

声明式编程 是以
数据结构的形式来表达程序执行逻辑。它的主要思想
告诉计算应该什么,但不指定具体要怎么做。SQL 语句就是最明显的一种声明式编程的例子例如

SELECT * FROM collection WHERE num > 5

除了 SQL,网页编程中用到的 HTML 和 CSS 也都属于声明式编程。它的特点:

函数式编程

函数式编程和声明式编程是有所关联的,因为他们思想是一致的:即只关注什么而不是怎么做。但函数式编程不仅仅局限于声明式编程

函数式编程是面向数学抽象,将计算描述为一种
表达式求值其实函数程序就是一个
表达式。

函数式编程本质

函数式编程中
函数部署计算机中的函数,而是指数学中的函数,即自变量映射。函数的值取决于函数的参数的值,不依赖于其他状态比如abs(x)函数计算x绝对值,只要x不变,无论何时调用调用次数,最终的值都是一样。

函数式编程的特点

  • 函数是第一等公民
  • 函数是纯函数

接下来我们分别介绍函数式编程的这两个特点

函数是第一等公民

函数是第一等公民:是指函数跟其它的数据类型一样处于平等地位,可以赋值给其他变量可以作为参数传入另一个函数,也可以作为别的函数的返回值例如如下代码

// 赋值
var func1 = function func1() {  }
// 函数作为参数
function func2(fn) {
    fn()
}   
// 函数作为返回值
function func3() {
    return function() {}
}

函数是纯函数

纯函数是指相同输入总会得到相同输出,并且不会产生副作用的函数。纯函数的两个特点:

无副作用 指的是函数内部操作不会对外部产生影响(如修改全局变量的值、修改 dom 节点等)。

// 是纯函数
function sum(x,y){
    return x + y
}
// 输出确定,不是纯函数
function random(x){
    return Math.random() * x
}
// 有副作用,不是纯函数
function setFontSize(el,fontsize){
    el.style.fontsize = fontsize ;
}
// 输出确定、有副作用,不是纯函数
let count = 0;
function addCount(x){
    count+=x;
    return count;
}

函数式编程的基本运算

函数合成compose

指的是将代表各个动作多个函数合并成一个函数。

上面讲到,函数式编程是对过程的抽象,关注的是动作。看下下面的例子

function add(x) {
    return x + 10
}
function multiply(x) {
    return x * 10
}

console.log(multiply(add(2)))  // 120

合成动作抽象为一个函数 compose如下

function compose(f,g) {
    return function(x) {
        return f(g(x));
    };
}
// 这样我们我们可以通过如下方式得到合成函数
// 执行动作顺序从右往左
let calculate=compose(multiply,add); 
console.log(calculate(2))  // 120

只要往 compose数中传入代表各个动作的函数,我们便能得到最终的合成函数。但上述 compose 函数的局限性是只能够合成两个函数,如果需要合成的函数不止两个呢,所以需要一个通用的 compose 函数。

function compose() {
  let args = arguments;
  let start = args.length - 1;
  return function () {
    let i = start - 1;
    let result = args[start].apply(this, arguments);
    while (i >= 0){
      result = args[i].call(this, result);
      i--;
    }
    return result;
  };
}

// 使用
function add(str){
    return x + 10
}
function multiply(str) {
    return x * 10
}
function minus(str) {
    return x - 10
}

let composeFun = compose(minus, multiply, add);
composeFun(2) // 110

通过 compose上述三个动作代表的函数合并成了一个,并最终输出正确结果

函数柯里化(Currying

函数柯里化又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值时候,之前传入的所有参数都会被一次用于求值

柯里化函数有如下两个特性

function sum(a, b) {
    return a + b;
}
console.log(sum(2, 2)) // 4

假设函数 sum 的柯里化函数是 sumCurry,那么从上述定义可知,sumCurry(2)(2) 应该实现上述代码相同的效果,输出 4 。这里我们可以比较容易的知道sumCurry代码如下

// sumCurrysum 的柯里化函数
function sumCurry(a) {
    return function(b) {
        return a + b;
    }
}
console.log(sumCurry(2)(2));  // 4

如果有一个函数 createCurry 能够实现柯里化,那么我们便可以通过下述的方式得出相同的结果

// sumCurry 返回一个柯里化函数
var sumCurry=createCurry(sum);
console.log(sumCurry(2)(2));  // 4

可以看到,函数 createCurry 传入一个函数 sum 作为参数返回了一个柯里化函数 sumCurry,函数 sumCurry 能够处理 sum 中的剩余参数。这个过程称为函数柯里化,我们称 sumCurry 是 add 的柯里化函数。
怎么得到实现柯里化的函数 createCurry 呢?这里直接给出 createCurry 的代码

// 参数只能从左到右传递
function createCurry(func, arrArgs) {
    var args=arguments;
    var funcLength = func.length;
    var arrArgs = arrArgs || [];

    return function() {
        var _arrArgs = Array.prototype.slice.call(arguments);
        var allArrArgs=arrArgs.concat(_arrArgs)

        // 如果参数个数小于最初的func.length,则递归调用,继续收集参数
        if (allArrArgs.length < funcLength) {
            return args.callee.call(this, func, allArrArgs);
        }

        // 参数收集完毕,则执行func
        return func.apply(this, allArrArgs);
    }
}

// createCurry 返回一个柯里化函数
var sumCurry=createCurry(function(a, b, c) {
    return a + b + c;
});
sumCurry(1)(2)(3) // 6
sumCurry(1, 2, 3) // 6
sumCurry(1)(2,3) // 6
sumCurry(1,2)(3) // 6

柯里化实际上是把简答的问题复杂化了,但是复杂化的同时在使用函数时拥有了更加多的自由度。

柯里化用途

现在需要实现一个功能,将一个全是数字的数组中的数字转换成分数的形式。按照正常的逻辑,我们可以按如下代码实现

function getPercentList(array) {
    return array.map(function(item) {
        return item * 100 + '%'
    })
}

console.log(getPercentList([1, 0.2, 3, 0.4]));   
// 结果:['100%', '20%', '300%', '40%']

如果通过柯里化的方式实现

function map(func, array) {
    return array.map(func);
}
var mapCurry = createCurry(map);
var getNewArray = mapCurry(function(item) {
    return item * 100 + '%'
})
console.log(getPercentList([1, 0.2, 3, 0.4])); 
// 结果:['100%', '20%', '300%', '40%']

上述例子简单以致不能表现出柯里化的强大,具体柯里化的使用还需要结合具体的场景其实,没有必要为了柯里化而柯里化,不管用什么方式我们的最终目的都是为了更好解决问题

高阶函数

满足下列条件之一的函数就可以称为高阶函数:

  1. 函数作为参数被传递

把函数当作参数传递,这代表我们可以抽离出一部分容易变化的业务逻辑,把这部分业务逻辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分。其中一个重要应用场景就是常见回调函数。

下面例子js的函数都是对高阶函数的利用

[1, 4, 2, 5, 0].sort((a, b) => a - b);
// [0, 1, 2, 4, 5]
        
[0, 1, 2, 3, 4].map(v => v + 1);
// [1, 2, 3, 4, 5]
        
[0, 1, 2, 3, 4].every(v => v < 5);
// true

2.函数作为返回值输出

让函数继续返回一个可执行的函数,意味着运算过程是可延续的

const fn = (() => {
    let students = [];
    return {
        addStudent(name) {
            if (students.includes(name)) {
                return false;
            }
            students.push(name);
        },
        showStudent(name) {
            if (Object.is(students.length, 0)) {
                return false;
            }
            return students.join(",");
        }
    }
})();
fn.addStudent("liming");
fn.addStudent("zhangsan");
fn.showStudent(); //输出:liming,zhangsan

同时满足两个条件高阶函数

const plus = (...args) => {
    let n = 0;
    for (let i = 0; i < args.length; i++) {
        n += args[i];
    }
    return n;
}

const mult = (...args) => {
    let n = 1;
    for (let i = 0; i < args.length; i++) {
        n *= args[i];
    }
    return n;
}

const createFn = (fn) => {
    let obj = {};
    return (...args) => {
        let keyName = args.join("");
        if (keyName in obj) {
            return obj[keyName];
        }
        obj[keyName] = fn.apply(null, args);
        return obj[keyName];
    }
}

let fun1 = createFn(plus);
console.log(fun1(2, 2, 2)); //输出:6

let fun2 = createFn(mult);
console.log(fun2(2, 2, 2)); //输出:8

原文地址:https://blog.csdn.net/Ming_xm/article/details/134807788

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_47218.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注