函数式编程
定义:数据映射关系:y = f(x)
无歧义;唯一确定性;
函数是一等公民
函数: 普通对象
- 存储在变量/数组中
- 作为参数和返回值
//相同功能函数,可通过赋值方法本身优化代,不加括号
const BlogController = {
index: View.index,
show: View.show,
create: Pg.create,
}
高阶函数
- 可把函数作为参数传递给另一个函数
//高阶函数-函数作为参数
// 模拟实现forEach 和 filter
function forEach(array, fn){
for (let i = 0; i < array.length; i++){
fn(array[i]);
}
}
let arrA = [1, 2, 3];
forEach(arrA, (item)=>{
console.log(item);
})
// 1
// 2
// 3
//filter
function filter (array, fn){
let results = [];
for(let i = 0; i < array.length; i++){
if( fn(array[i]) ){
results.push(array[i]);
}
}
return results;
}
let res = filter(arrA, (item) => {
return item > 1;
})
console.log(res);
// [2,3]
- 可把函数作为另一个函数的返回结果
//模拟once
function once (fn){
let done = false; //初始标记位:未执行
return function (){
if(!done){
done = true;
return fn.apply(this, arguments); //fn接收返回值函数的参数
}
}
}
let pay = once(function(money) {
console.log(`spent: ${money} RMB`);
})
pay(9);
pay(9);
pay(9);
// spent 9 RMB
意义:
- 屏蔽细节
- 抽象通用问题,复用
常用高阶函数
-
forEach
遍历数组,不返回新数组
//模拟 function forEach(array, fn){ for (let i = 0; i < array.length; i++){ fn(array[i]); } }
-
map
遍历数组,返回新数组(处理后每个数组元素结果集)
//模拟 const map = (array, fn) => { let results = []; for( let value of array){ results.push(fn(value)); } return results; }
-
filter
遍历数组,返回符合筛选方法的新结果数组
//模拟 function filter (array, fn){ let results = []; for(let i = 0; i < array.length; i++){ if( fn(array[i]) ){ results.push(array[i]); } } return results; }
-
every
判断数组中每个元素是否匹配设定条件,返回Boolean类型
//模拟 const every = (array, fn) => { let result = true; for(let value of array){ result = fn(value); if(!result){ break; } } return result; } let arrA = [1, 2, 3]; let r = every( arrA, v => v < 4 ) console.log(r);
-
some
判断数组中是否有元素满足匹配设定条件,返回Boolean类型
//模拟 const some = (array, fn) => { let result = false; for(let value of array){ result = fn(value); if(result){ break; } } return result; }
-
find/findIndex
-
reduce
-
sort
闭包
闭包 (Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是
堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
//生成计算n次幂的运算函数
function makePower(power) {
return function (number) {
return Math.pow(number, power)
}
}
//求平方
let power2 = makePower(2);
console.log(power2(4));
// 16
纯函数
相同输入永远得到相同输出,无任何可观察的副作用
- 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
- 我们可以把一个函数的执行结果交给另一个函数去处理
例:数组slice 和 splice
-
slice
Array.slice( startIndex, endIndex) 不包含endIndex位置的元素
纯函数,返回数组中指定部分,不会改变原数组;
-
splice
Array.splice( startIndex, removeN) 返回移除n个元素后改变的原数组
非纯函数,对数组操作后返回该数组,会改变原数组;
lodash是纯函数的功能库
提供了对数组、数字、对象、字符串、函数等操作的一些方法
https://lodash.com/
纯函数优势:
-
可缓存
//lodash const _ = require('lodash'); //记忆函数 function getArea(r){ console.log(r); return Math.PI *r *r; } let getAreaWithMemory = _.memoize(getArea); console.log(getAreaWithMemory(5)); console.log(getAreaWithMemory(5)); console.log(getAreaWithMemory(5)); console.log(getAreaWithMemory(5)); // 5 // 78.53981633974483 // 78.53981633974483 // 78.53981633974483 // 78.53981633974483 //模拟理解 //模拟理解 memoize function memoize (fn){ let cache = { }; return function () { let key = JSON.stringify (arguments); cache[key] = cache[key] || fn.apply(fn, arguments); return cache[key]; } } let getAreaWithMemory = memoize(getArea); console.log(getAreaWithMemory(5)); console.log(getAreaWithMemory(5)); console.log(getAreaWithMemory(5)); console.log(getAreaWithMemory(5));
-
可测试
-
并行处理
- 在多线程环境下并行操作共享的内存数据很可能会出现意外情况
- 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数 (Web Worker)
纯函数副作用:
副作用让一个函数变的不纯, 若函数依赖于外部状态则无法保证输出相同,会带来副作用.
副作用来源:
(所有外部交互可能导致)
- 配置文件
- 数据库
- 获取用户输入
柯里化(Haskell Books Curry)
- 当一个函数有多个参数时,先传递一部分参数调用它(这部分参数之后永不变)
- 然后返回一个新的函数接收剩余的参数,返回结果
//柯里化
function checkAge(min){
return function (age){
return age >= min;
}
}
//es6
let checkAge = min => (age => age >= min);
let checkAge18 = checkAge(18);
checkAge18(22);
lodash 中的柯里化函数
-
_.curry(fn)
- 功能: 创建一个函数,该函数接收一个或多个 func 的参数,如果 func 所需要的参数都被提供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。
- 参数:需要柯里化的函数
- 返回值:柯里化后的函数
//lodash中的curry基本使用 const _ = require("lodash"); const getSum = (a, b, c) => ( a + b + c); const curried = _.curry(getSum); console.log( curried(1, 2, 3) ); console.log(curried(1)(2,3)); console.log(curried(1, 2)(3)); //柯里化案例 const match = _.curry((reg, str) => str.match(reg) ); const haveSpace = match(/\s+/g); // console.log( haveSpace("try again")); const filter = _.curry( (fn, array) => array.filter(fn) ); const findSpace = filter( haveSpace ); console.log( filter( haveSpace, ["first thing", "secondThing"])); console.log( findSpace(["first thing", "secondThing"]));
模拟实现lodash中的curry方法
const curry = func =>{
return function curriedFn(...args){
//判断实参和形参的个数
if(args.length < func.length){
return function(){
//闭包保存应用args参数并结合新参数, 新参数arguments是伪数组需用Array.from函数处理,接收参数并转化为对应实参,调用curriedFn函数判断参数是否满足(递归调用直至满足参数条件)
return curriedFn(...args.concat(Array.from(arguments)));
}
}
return func(...args);
}
}
const curried = curry(getSum);
console.log( curried(1, 2, 3) );
console.log(curried(1)(2,3));
柯里化总结:
- 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
- 这是一种对函数参数的’缓存’
- 让函数变的更灵活,让函数的粒度更小
- 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能