likes
comments
collection
share

JS 函数式编程

作者站长头像
站长
· 阅读数 20

JS支持面向对象编程、函数式编程、结构化编程三大范式,本文谈谈JS函数式编程。

什么是函数式编程?

这是一种封装函数思维。相比于面向对象编程,函数式编程思想在JS中更有用,运用函数式编程,我们的代码会更简洁、更优雅、更实用。

函数式编程具体指什么?

函数式编程在实践中,有五个核心概念,它们分别是:函数是一等公民、纯函数、高阶函数、柯里化、组合函数,可以把它们看作函数式编程思想的细化,也可以理解成某种技术。

下面,我们来逐一介绍它们。

函数是一等公民

所谓一等公民,也就是普通公民,也就是说在JS中,函数没什么特殊的,我们可以像对待任何其它数据一样对待函数,比如:

  • 可以把函数赋值给一个变量
  • 可以把函数存在数组里
  • 可以把函数作为另一个函数的参数
  • 可以把函数作为另一个函数运行的返回值

了解这些有什么用呢?最直接的好处,就是我们在写业务代码时,可以对很多冗余的代码写法进行优化,比如下面这两个例子:

function delay() {
    console.log('5000ms之后执行该方法.');
}

var timer = setTimeout(function() {
    delay();
}, 5000);

// 因为函数可以直接作为参数,所以可以优化代码,定时器部分可以简写成:
var timer = setTimeout(delay, 5000);
function getUser(path, callback) {
    return $.get(path, function(info) {
       	return callback(info);
    })
}

getUser('/api/user', function(resp) {
    console.log(resp);
})

// 因为函数只有一个作为返回值的函数执行,所有getUser函数可以简写成:
var getUser = $.get;

纯函数

所谓纯函数,指的是相同的输入总会得到相同的输出,并且不会产生副作用的函数。

由纯函数的定义,我们可以合理的推断出其特点:

因为纯函数相同的输入总有相同的输出,所以具有可缓存性

又因为纯函数要做到这点,且不会产生副作用,就必定会满足另一个特点,即除了传入的参数外,不应该依赖任何外界的信息与状态

因为纯函数不依赖参数之外的值,所以它具有可移植性

js原始支持很多不纯的方法,它们会把我们的函数搞得非常混乱,虽然在实践中并不是所有的场景都能使用纯函数,但还是应该尽量在合适的场景使用它。

举例:

var source = [1, 2, 3, 4, 5];

source.slice(1, 3); // 纯函数 返回[2, 3] source不变
source.splice(1, 3); // 不纯的 返回[2, 3, 4] source被改变

source.pop(); // 不纯的
source.push(6); // 不纯的
source.shift(); // 不纯的
source.unshift(1); // 不纯的
source.reverse(); // 不纯的

// 我也不能短时间知道现在source被改变成了什么样子,干脆重新约定一下
source = [1, 2, 3, 4, 5];

source.concat([6, 7]); // 纯函数 返回[1, 2, 3, 4, 5, 6, 7] source不变
source.join('-'); // 纯函数 返回1-2-3-4-5 source不变

高阶函数

凡是接收一个函数作为参数的函数,或者返回一个新函数的函数,就是高阶函数,常常利用高阶函数来封装一些公共的逻辑。

高阶函数在JavaScript中有广泛的应用,例如处理数组、事件监听、回调函数、异步编程等。

高阶函数在JavaScript中非常常见,它们提供了代码复用和抽象化的强大机制,使得我们能够编写更加模块化和可维护的代码。

一些应用高阶函数的例子:

// 1. 使用map遍历数组
const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(num => num * 2);// 使用高阶函数map,将每个数字乘以2

console.log(doubled); // 输出: [2, 4, 6, 8, 10]
	
// 2.使用filter筛选数组元素
const numbers = [1, 2, 3, 4, 5, 6];

const evenNumbers = numbers.filter(num => num % 2 === 0);// 使用高阶函数filter,筛选出偶数

console.log(evenNumbers); // 输出: [2, 4, 6]

// 3.使用reduce对数组元素进行累加
const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);// 使用高阶函数reduce,计算数组元素的总和

console.log(sum); // 输出: 15

// 4.使用回调函数进行异步操作

// 使用setTimeout高阶函数,在一段时间后执行回调函数
setTimeout(() => {
  console.log('This will run after 2 seconds');
}, 2000);

// 使用Promise的then方法,处理异步操作的结果
new Promise((resolve, reject) => {
  setTimeout(() => resolve('Resolved!'), 1000);
}).then(result => {
  console.log(result); // 输出: 'Resolved!'
});	

// 5.自定义高阶函数

// 自定义高阶函数,接受一个函数并返回一个新函数,新函数会执行传入的函数并打印结果
function logResult(fn) {
  return function(...args) {
    const result = fn(...args);
    console.log(`Result: ${result}`);
    return result;
  };
}

// 使用自定义高阶函数
const add = (a, b) => a + b;
const loggedAdd = logResult(add);
console.log(loggedAdd(1, 2)); // 输出: 'Result: 3' 并且返回 3

柯里化

柯里化属于高阶函数的一种。

柯里化是指这样一个函数,它接收函数A作为参数,运行后能够返回一个新的函数,并且这个新的函数能够处理函数A的剩余参数,本质是通过闭包记住了这些剩余参数

简单的说,它是将一个接受多个参数的函数转化为一系列接受单一参数的函数的过程,即单值化的过程。

你可以一次性地调用柯里化函数,也可以每次只传一个参数分多次调用。

举例:

var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2); // 3

addTen(2); // 12

组合函数

函数是可以组合的,一旦我们知道一个函数的输出类型可以匹配另一个函数的输入,那它们就可以进行组合。

举例:

// 例一:使用组合函数,定义一个容器Box,这样做的好处是能够在不离开 Box 的情况下操作容器里面的值,正如es6中的map、filter、promise等函数一样.
const Box = x => ({
    map: f => Box(f(x)), // map是把函数执行的结果重新包装到Box中后然返回一个新的Box类型
    fold: f => f(x), // fold是直接把函数执行的结果 return 出来
})

// 使用Box实现链式调用
Box(2)
    .map(x => x + 2)
    .fold(x => x)  // => 4

// 例二:解析Box的链式调用
const Box = x => ({
    map: f => Box(f(x)),
    fold: f => f(x),
})

const partial =
    (fn, ...presetArgs) =>
        (...laterArgs) =>
            fn(...presetArgs, ...laterArgs);

const double = n => n * 2
const map = (fn, F) => F.map(fn)
const mapDouble = partial(map, double)

const res = mapDouble(Box(1)).fold(x => x)
console.log(res)  // => 2

// 为什么最后输出结果为2,分析如下,一共六步:
1. partial(map, double) = map(double,...laterArgs)
2. mapDouble(Box(1)) = map(double,Box(1))
3. map(double,Box(1)) =Box(1).map(double)
4. Box(1).map(double) =Box(double(1))
5. Box(double(1)) =Box(2)
6. Box(2).fold(x => x) = 2

总结

正如面向对象编程之于java一样,函数式编程对于js有同样的重要性,因为js是更符合函数式编程思想的语言,可以说函数式编程思想在js中无处不在,本文在大体上总结了一下函数式编程的五个特性,在实践中合理的运用,一定会让你的js编码水平提升一层。

转载自:https://juejin.cn/post/7393314542095499274
评论
请登录