JavaScript函数式编程
1.什么是函数式编程
和面向过程、面向对象编程一样函数式编程是一种编程范式,主要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。
举个例子,要把字符串functional programming is great 变成每个单词首字母大写,可以这样实现:
var string = 'functional programming is great';
var result = string
.split(' ')
.map(v => v.slice(0, 1).toUpperCase() + v.slice(1))
.join(' ');
上面先用 split 把字符串转换数组,然后再通过 map 把各元素的首字母转换成大写,最后通过 join 把数组转换成字符串。整个过程就是join(map(split(str))),体现了函数式编程的核心思想:通过函数对数据进行转换。
2.命令式和声明式
2.1 命令式
通过编写一条又一条指令去让计算机执行一些动作,一般会涉及到很多繁杂的细节。
// 命令式
var CEOs = [];
for (var i = 0; i < companies.length; i++) {
CEOs.push(companies[i].CEO)
}
2.2 声明式编程
通过写表达式的方式来声明操作,而非通过一步一步的指示。表达式通常是某些函数调用的复合、一些值和操作符,用来计算出结果值。
// 声明式
var CEOs = companies.map(c => c.CEO);
从上面的例子中,可以看到声明式的写法是一个表达式,无需关心如何进行计数器迭代,返回的数组如何收集,它指明的是做什么,而不是怎么做。
函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。
3.为什么使用函数式编程?
- 函数式编程能提高复用性和可拓展性;
- 方便Tree-shaking
Tree-shaking的本质是通过文档流的引入判断是否使用某个方法(就是没有用到的部分,不进行最后的打包操作),但是面向对象的编程方案无法记录。
4.常见的函数式编程模型?
4.1 闭包
// 简单的缓存工具
// 匿名函数创造了一个闭包
const cache = (function () {
const store = {};
return {
get(key) {
return store[key];
},
set(key, val) {
store[key] = val;
}
}
}());
console.log(cache) // {get: ƒ, set: ƒ}
cache.set('a', 1);
cache.get('a'); // 1
上面是一个简单的缓存工具的实现,匿名函数创造了一个闭包,使得 store 对象 ,一直可以被引用,不会被回收。
闭包也有弊端:持久化变量不会被正常释放,持续占用内存空间,很容易造成内存浪费,所以一般需要一些额外手动的清理机制。
4.2 高阶函数
高阶函数指的是可以接收函数作为参数或者返回一个新函数的函数。在JS中,常用的高阶函数有map、reduce、filter等,这些函数可以方便地处理数组和对象等数据类型。
// 定义一个高阶函数,接收一个函数作为参数,并调用它
function higherOrderFunc(func) {
console.log("调用高阶函数");
func();
}
// 定义一个函数作为参数
function someFunc() {
console.log("调用了函数作为参数");
}
// 调用高阶函数,并传入函数作为参数
higherOrderFunc(someFunc);
4.3 函数科里化
// 定义一个柯里化函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
// 如果传入参数个数达到函数的形参个数,则直接调用该函数
return fn.apply(this, args);
} else {
// 如果传入参数不足,则返回一个接收剩余参数的新函数
return function (...rest) {
return curried.apply(this, args.concat(rest));
};
}
};
}
// 定义一个普通函数,用于演示柯里化
function add(x, y, z) {
return x + y + z;
}
// 使用柯里化给add函数添加复用性
const curriedAdd = curry(add);
// 调用柯里化函数
console.log(curriedAdd(1, 2, 3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1)(2)(3)); // 6
5.优缺点
优点
- 更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况
- 更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响
- 更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性
缺点:
- 性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销
- 资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式
- 递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作

1596

被折叠的 条评论
为什么被折叠?



