JavaScript 函数式编程一直存在很久,但似乎从 2016 年开始,它变得越来越火热。这可能是因为 ES6 语法对于函数式编程更为友好,也可能是因为诸如 RxJS ReactiveX 等函数式框架的流行。
概念的问题大家可以自己去查阅。重点展示在 JavaScript 中到底什么是函数式的代码、函数式代码与一般写法有什么区别、函数式的代码能给我们带来什么好处以及常见的一些函数式模型都有哪些。
理解函数式编程
以函数作为主要载体的编程方式,用函数去拆解、抽象一般的表达式
与命令式相比,这样做的好处在哪?主要有以下几点:
- 语义更加清晰
- 可复用性更高
- 可维护性更好
- 作用域局限,副作用少
基本的函数式编程
例子:实现字母的首字母大写
//一般写法
let arr = ['apple', 'pen', 'apple-pen'];
for(let i in arr){
let c = arr[i][0];
arr[i] = c.toUpperCase() + arr[i].slice(1);
}
console.log(arr);
for in 和 for of 的区别。以及在这里如何用 for of 去编写同样的方式
实现用函数式的方式去编写
function upperFirst(word) {
return word[0].toUpperCase() + word.slice(1);
}
function wordToUpperCase(arr) {
return arr.map(upperFirst);
}
console.log(wordToUpperCase(['apple', 'pen', 'apple-pen']));
当情况变得更加复杂时,表达式的写法会遇到几个问题:
表意不明显,逐渐变得难以维护
复用性差,会产生更多的代码量
会产生很多中间变量
当我们用函数式方式去写的时候,它利用了函数封装性将功能做拆解,并封装为不同的函数,而再利用组合的调用达到目的。这样做使得表意清晰,易于维护、复用以及扩展。其次利用 高阶函数,Array.map 代替 for…of 做数组遍历,减少了中间变量和操作。
链式优化
// 计算数字之和
// 一般写法
console.log(1 + 2 + 3 - 4)
// 函数式写法
function sum(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
console.log(sub(sum(sum(1, 2), 3), 4);
如果是1+2+3+5+6+8+11+29+22+55+11-1-2-2 呢?
会导致横向延展 的比较极端的情况,可读性比较差。还容易产生错误。
因此链式优化就显得比较重要了。
我们可以这样写。 ---- lodash的链式写法
const utils = {
chain(a) {
this._temp = a;
return this;
},
sum(b) {
this._temp += b;
return this;
},
sub(b) {
this._temp -= b;
return this;
},
value() {
const _temp = this._temp;
this._temp = undefined;
return _temp;
}
};
console.log(utils.chain(1).sum(2).sum(3).sub(4).value());
这样写的好处是,结构清晰,每个方法做什么都会比较清晰。
在es5之前,我们处理并发的时候,通常是采用回调。但是在es6之后我们可以采用promise的写法。
var ok = 1;
var promise = new Promise((resolve,reject) =>{
if(typeof ok === 'number'){
resolve(ok)
}else{
reject(new TypeError("ok is not number"))
}
}).then((result) =>{
console.log(result);
}).then((result) =>{
console.log(result)
}).catch((error)=>{
console.log(error)
})
打印结果->提出问题
链式写法同函数式写法的结合
var func1 = new Promise((resolve,reject)=>{
resolve("this is func1");
})
var func2 = new Promise((resolve,reject)=>{
reject("this is func2 but it's not done for the project");
})
var mainfunc = new Promise((resolve,reject)=>{
resolve("mainfunc start ");
}).then((result)=>{
console.log(result);
return func1;
}).then((result) => {
console.log(result);
return func2;
}).then((result) => {
console.log(result)
console.log("mission complete")
}).catch((error) => {
console.error("error "+error);
})
常见的函数式编程模型。
闭包(Closure)
可以保留局部变量不被释放的代码块,被称为一个闭包
闭包的概念比较抽象,相信大家都或多或少知道,用到这个特性。那么闭包到底能给我们带来什么样子的好处?
先来看一个如何创建一个闭包:
// 创建一个闭包
function makeCounter() {
let k = 0;
return function() {
return ++k;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
makeCounter 这个函数的代码块,在返回的函数中,对局部变量 k ,进行了引用,导致局部变量无法在函数执行结束后,被系统回收掉,从而产生了闭包。而这个闭包的作用就是,“保留住“ 了局部变量,使内层函数调用时,可以重复使用该变量;而不同于全局变量,该变量只能在函数内部被引用。
换句话说,闭包其实就是创造出了一些函数私有的 ”持久化变量“。
所以从这个例子,我们可以总结出,闭包的创造条件是:
存在内、外两层函数
内层函数对外层函数的局部变量进行了引用
闭包的用途
闭包的主要用途就是可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等等。
接下来我们来写一个简单的缓存工具。使用匿名函数创造一个闭包。
const cache = (function(){
//匿名函数创建了一个闭包对象
const store = {};
return {
get(key){
return store[key];
},
set(key, val) {
store[key] = val;
}
}
}());
cache.set('a',1);
console.log(cache.get('a'));
上面例子是一个简单的缓存工具的实现,匿名函数创造了一个闭包,使得 store 对象 ,一直可以被引用,不会被回收。
闭包的弊端
持久化变量不会被正常释放,持续占用内存空间,很容易造成内存浪费,所以一般需要一些额外手动的清理机制。
高阶函数
接受或者返回一个函数的函数称为高阶函数
JavaScript语言是原生支持高阶函数的,因为JavaScript的函数是一等公民,它既可以作为参数又可以作为另一个函数的返回值使用。
Array.filter Array.map Array.reduce
-
filter:对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。
-
map:每次函数调用的结果组成的数组
-
reduce:(归并)方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值。
arr.reduce([callback, initialValue]) var max = (pre,cur)=>Math.max(pre,cur); var func = [1,2].reduce(max); //如果是 var func = [1,2].reduce(max,5); 呢? console.log(func);
下面以map为例(映射)
数组中每一个项加一,组成一个新的数组
//一般写法
const arr = [1,2,3];
const rs = [];
for(const n in arr){
rs.push(++arr[n]);
}
console.log(rs)
//map
let arr1 = [2,4,6,8];
let ar1 = arr1.map(n=>++n);
console.log(ar1);
上面的写法,利用for...of循环的方式遍历数组会产生额外的操作,而且有改变原数组的风险
而 map 函数封装了必要的操作,使我们仅需要关心映射逻辑的函数实现即可,减少了代码量,也降低了副作用产生的风险。
网友评论