函数式编程(一)


1.为什么要用函数式编程?

函数式编程是一个非常古老的概念,甚至早于第一台计算机的诞生,但随着React的流行,如今越来越受到人们的关注,连Vue3也逐渐向函数式编程靠拢,这就像当年谷歌地图带火的Ajax一样,是一门不得不了解的编程思想。

2.什么是函数式编程

函数式编程(Functional Programming,FP) 是编程范式之一,常见的还有面向过程编程和面向对象编程。函数式编程的思维方式主要是把现实世界的事物和事物之间的联系抽象到程序世界。(对运算过程进行抽象)。简单来说在程序中会根据输入的值通过某种运算得到相应的输出,开发中会涉及到很多输入输出的函数,函数式编程就是对这些运算过程进行抽象。而面向对象式编程是抽象现实世界中的事物,这是这两种方式从思维模式上的区别。

函数式编程中的函数指的并不是程序中的函数(方法),而是数学中的函数及映射关系,如 y = sin(x)。要求相同的输入始终要得到相同的输出。

// 非函数式
const a = 1;
const b = 2;
const sum = a + b;
console.log(sum);

//函数式
//相同的输入始终要得到相同的输出
function add (n1, n2){
    return n1 + n2;
}
const sum = add(2, 3);
console.log(sum);

3.函数式编程相关概念

3.1函数是一等公民

函数是一等公民有三个概念

  • 函数可以存储在变量中
  • 函数可以作为参数
  • 函数可以作为参数的返回值

在 JavaScript 中函数就是一个普通的对象,我们可以把函数存储到变量或数组中,它还可以作为另一个函数的参数和返回值,甚至我们可以在程序运行的时候通过 new Function('alert('qzhai')') 来构造一个新的函数。

// 把函数赋值给变量
const fn = function(msg){
    console.log(msg);
}
fn('衫小寨主题就是好看!!');

 
const fn_a = {
    index(msg) {
        return fn(msg); // 作为参数返回
    }
}

// 上面的例子 index 的传参 和 fn 的传参是一样的,所以也可以这么写
const fn_a = {
    index:fn, 
}
//函数作为参数赋值给index 而不是调用

3.2 高阶函数

高阶函数就是可以把函数作为参数传递给另一个函数,也可以把函数作为另一个函数的返回结果。

// 函数作为参数
// fn 就是把函数作为一个参数传入。
function filter(arr, fn){
    const results = [];
    for (let i; i < arr.length; i++) {
        if (fn(arr[i])) { // 符合函数条件 才可以push 到返回数组里,这样就把判断条件交给传入的数组,filter 就会变得很通用
            results.push(arr[i]);
        }
    }
}

// 测试
const arr = [1,2,3,4];
filter(arr, function(i){
    return i % 2 === 0;
})
// 函数作为返回值 其实也就是让一个函数生成一个函数
function make_fn () {
    const msg = '衫小寨主题真的很好看!';
    return function () {
        console.log(msg);
    }
}
const fn = make_fn();
fn(); // 调用返回回来的函数。

make_fn()();// 这么调用也可以但不常用

使用高阶函数的意义。

  • 抽象可以帮我们屏蔽实现的细节,只需要关注我们的目标
  • 高阶函数就是用来抽象用的问题

就比如上面 filter 的例子,我们要在一个数组中过滤某些元素,使用面向过程方式变成的话我们需要先循环数组,然后在循环中过滤调节,而在函数式编程中只需要关注我们的目标及过滤什么样的数组。

3.3 闭包

函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。可以理解为在另一个作用于中调用一个函数的内部函数并访问到该函数的作用域中的成员。

上一个例子中 (make_fn) 这个函数其实就用到了闭包,我们通过 make_fn 生成了一个新的函数 fn ,当我们在调用 fn 的时候其实就是调用 make_fn 返回的函数,因此我们可以访问 make_fn 中的 msg 属性。也就是说闭包的核心作用就是把make_fn中的内部成员的作用范围延长了,正常情况下 make_fn 被执行完毕以后 msg 应该被释放掉,但是如果 make_fn 中返回了一个成员并且外部对这个成员有引用那么make_fn中的一些内部成员在执行完毕后就不会被释放。

// 举一个实际的中用到的例子
// 有些时候我们希望一个函数只被执行一次,比如一个购物支付页面的支付按钮,被多次点击,但是支付函数只调用一次。这个就可以用到闭包。
function once (fn) {
    let done = false;
    return function () {
        if (!done) {
            done = true;
            return fn.apply(fn,arguments);
        }
    }
}

const pay = once(function (money) {
    console.log(`支付金额:${money}`);
});

// 仅支付一次
pay(5);
pay(5);
pay(5);

闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。闭包的好处就是延长了外部函数它内部变量的作用范围。

  • 分享:
评论
还没有评论
    发表评论 说点什么