JavaScript函数式编程

606 阅读19分钟

函数式编程是什么

  • 函数式编程是一种思维方式或者说是一种编写代码的方式(即编程范式),它强调以函数使用为主;它不是一种框架或工具,也不是API;函数式的思维方式与面向对象的思维方式完全不同

  • 从宏观上讲,函数式编程实际上是分解(将程序拆分为小片段)和组合(将小片段连接到一起)直接的相互作用; 函数式思维的学习通常始于将特定的任务分解为逻辑子任务(函数)的过程;如果需要,这些子任务可以进一步分解,直到称为一个个简单的、相互独立的纯函数功能单元

  • 函数式编程中,函数就是一个管道(pipe),这头进去一个值,那头就会出来一个值,没有其他可观察的副作用;

函数式编程的核心

函数式编程的核心就是组合函数

  • 通过组合一些小函数来构建一个新函数;
  • 组合函数真正的优势在于: 不必创建新的函数就可通过函数解决眼前的问题;
  • 仅当函数接收一个参数时,才能将两个函数组合起来;

函数式编程的优点

  • 促使将任务分解成简单的函数; 使用流式的调用链来处理数据
  • 通过响应式范式降低事件驱动代码的复杂性
  • 使用纯函数的代码绝不会更改或破坏全局状态,有助于提高代码的可测试性和可维护性
  • 函数式编程采用声明式分格,易于推理。这提高了应用程序的整体可读性,通过使用组合和lambda表达式使代码更加精简

函数式编程的基本概念

常见的编程范式有命令式编程、面向对象编程、声明式编程等,函数式编程属于声明式编程;

命令式编程 VS 函数式编程

命令式编程函数式编程
有副作用无副作用
关心解决问题的步骤强调函数组合、变换解决问题
关注如何使用代码得到结果关心如何解决问题
具体的告诉代码如何执行某个任务不关心怎么做,只在乎想要的结果
// 命令式编程具体地告诉计算机如何执行任务
// 计算数组中每个值的平方
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for(let i = 0; i < array.length; i++){
    array[i] = Math.pow(array[i], 2);
}
// 声明式编程是将程序的描述与求值分离开来。它关注于如何用各种表达式来描述程序逻辑,而不一定要知名其控制流程或状态的变化
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(num => Math.pow(num, 2))
  • 命令式编程关心解决问题的步骤
这样一个数学表达式: (1 + 2) * 3 - 4

用命令式编程可能这样写
let a = 1 + 2;
let b = a * 3;
let c = b - 4;
  • 函数式编程要求使用函数,我们可以把运算过程定义为不同的函数
let add = (a, b)=> a + b;
let multiplyThree = (c)=> c * 3;
let substractFour = (d)=> d - 4;
let result = substractFour(multiplyThree(add(1, 2), 3), 4); // 函数式风格调用


// 升级
const compose = (...fns) => (...value) => fns.reduce((result, fn) => fn.apply(null, [].concat(result)), value)
let getResult = compose(add, multiplyThree, substractFour);
getResult(1, 2);

面向对象编程 VS 函数式编程

面向对象编程的一些缺点,用一个形象的比喻就是: “猴子只想要一根香蕉,可是主任却给了它一片香蕉树林!”

面向对象编程函数式编程
通过封装变化使代码更易理解通过最小化变化使得代码更易理解
以类、对象为核心函数使用为主
属性、方法冗余只需要用到必须的函数

纯函数

函数式编程旨在尽可能地提高代码的无状态性和不变性。 无状态的代码不会改变或破坏全局的状态;但是要做到这一点需要使用那些没有副作用和状态变化的函数---也称为纯函数 函数式编程基于一个前提,就是使用纯函数构建具有不变性的程序;

纯函数具有以下性质
  • 不会造成超出其作用域的变化,例如:修改全局对象或引用传递的参数
  • 仅取决于提供的输入,而不依赖于任何在函数求值期间或调用间隔时可能变化的隐藏状态和外部状态
  • 纯函数总能够根据输入来做缓存
var counter = 0;
function increment(){
    return ++counter;
}

// 这个函数是不纯的;因为它读取并修改了一个外部变量,即函数作用域外的counter; 一般来说,函数在读取或写入外部资源时都会产生副作用
let students = [
	{ ssn: '111-11-1111', firstname: 'Bruce', lastname: 'lee'},
	{ ssn: '222-22-2222', firstname: 'Lebron', lastname: 'james'},
	{ ssn: '333-33-3333', firstname: 'Michael', lastname: 'jordan'},
	{ ssn: '444-44-4444', firstname: 'C', lastname: 'ronaldo'},
	{ ssn: '555-55-5555', firstname: 'Kobe', lastname: 'bryant'}
];

// 任务: 查找ssn为333-33-3333的学生,并将其ssn编号和姓名显示在浏览器中
let elementId = 'container';
function showStudent(ssn){
	let student = null;
	for(let i = 0; i < students.length; i++){
		if(students[i].ssn === ssn){
			student = students[i];
			break;
		}
	}
	
	if(student !== null){
		document.querySelector(`#${elementId}`).innerHTML = `${student.ssn}: ${student.firstname} - ${student.lastname}`;
	} else {
		throw new Error("Student not found!");
	}
	
}

showStudent('444-44-4444');
// 需要对象存储的引用和ID查找学生
let findStudent = curry((students, id)=>{
	console.log("id: ", id)
	var student = students.find(student => student.ssn === id);
	if(student) return student;
	return new Error("Object not found!");
});

// 将学生对象转换成用逗号分隔的字符串
let csv = (student) => `${student.ssn}: ${student.firstname} - ${student.lastname}`;

// 为了在屏幕上显示学生信息,这里需要elementId以及学生信息
let append = curry((elementId, info) => {
	document.querySelector(`${elementId}`).innerHTML = info;
})


let showStudentBySSN = compose(
	append("#container"), 			// 部分设置HTML元素的ID
	csv, 
	findStudent([
		{ ssn: '111-11-1111', firstname: 'Bruce', lastname: 'lee'},
		{ ssn: '222-22-2222', firstname: 'Lebron', lastname: 'james'},
		{ ssn: '333-33-3333', firstname: 'Michael', lastname: 'jordan'},
		{ ssn: '444-44-4444', firstname: 'C', lastname: 'ronaldo'},
		{ ssn: '555-55-5555', firstname: 'Kobe', lastname: 'bryant'}
	])			// 部分设置查找对象为students对象
);

showStudentBySSN('222-22-2222');

引用透明和可置换性

引用透明是定义一个纯函数较为正确的方式;纯度在这个意义上表明一个函数的参数和返回值之间映射的纯的关系。因此,如果一个函数对应相同的输入始终产生相同的结果,那么就说它是引用透明的。

// 不稳定的,不透明的,因为其返回值验证依赖外部变量counter
var counter = 0;
function increment(){
    return ++counter;
}

increment();
increment();
counter = 10; // 如果在调用increment期间,counter发生了变化,则会发生不可预测的结果
increment();  // -> ? 此时值应该是多少

// 命令式版本的结果是不可预测的,并且可能是不一致的。外部变量counter随时会改变,这影响了函数连续调用的结果
// 稳定的
var increment = counter => counter + 1;
// 现在这个函数是稳定的,对于相同的输入每次都返回相同的输出结果;

function compose(...args){
	// args代表调用compose传入的要组合的函数数组
	return function(value){
		// compose返回的函数接收一个初始值value
		
		return args.reverse().reduce((total, fn)=>{
			return fn(total);
		}, value);
	}
}

var plus2 = compose(increment, increment);
plus2(0);   // 该值总为初始值加2

// 

存储不可变数据(不可变性)

不可变数据是指那些被创建后不能更改的数据。与许多其他语言一样,JavaScript中的所有基本类型(String、Number等)从本质上是不可变的。但是其他对象,例如数组,都是可变的---即使用它们作为输入传递给另一个函数,仍然可以通过改变原有内容的方式产生副作用。我们不直接更改原始数据结构,而是创建数据结构的副本,所有操作都使用副本

var sortDesc = function(arr){ return arr.sort(function(a, b){
    return b - a;
})}

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
sortDesc(arr);   // [9, 8, 7, 6, 5, 4, 3, 2, 1]
console.log(arr) // [9, 8, 7, 6, 5, 4, 3, 2, 1]
// 不幸的是,array.sort函数是有状态的,会导致在排序中产生副作用,因为原始的引用被修改了

函数式编程的核心概念

函数式编程的目标是使用函数来抽象作用在数据之上的控制流与操作,从而在系统中消除副作用并减少对状态的改变

纯函数

  • 纯函数指基于参数做计算,并返回一个值的函数;
  • 纯函数至少接受一个参数,而且始终返回一个值或另一个函数;纯函数不会依赖于任何外部数据,所有的一切都必须以参数的形式提供
  • 纯函数没有副作用、不设置全局变量,也不更改应用的状态;
  • 纯函数把参数视为不可变数据;

纯函数的意义

  • 纯函数可以产生可测试的代码
  • 不依赖外部环境计算,不会产生副作用,提高函数的复用性
  • 可读性强
  • 可以组装成复杂任务的可能性。 符合模块化概念及单一职责原则

纯函数编写规则

使用纯函数让你的开发轻松不少,这是因为纯函数不影响应用的状态。建议遵守下面三条规则编写函数:

  1. 函数应该至少接受一个参数
  2. 函数应该返回一个值或另一个函数
  3. 函数不应该更改任何参数

数据转换

  • 在函数式编程中,经常需要让数据从一种格式变成另一种格式。我们要做的就是使用函数产出转换后的副本;这样的函数减少了代码的命令式属性,从而降低了复杂度;
  • 若想精通JavaScript函数式编程,必须掌握两个核心函数,即Array.map和Array.reduce
Array.map函数
  • map方法返回一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成
  • map是不可变的,因此输出是一个全新的数组,原有数组不会变化
  • map是一个只会从左到右遍历的操作,对于从右到左的操作,必须选反转数组。Javascript中的Array.reverse()操作不能在这里使用,因为它会改变原数组;可以使用Lodash中的功能等价的reverse方法
let schools = [{name: '清华'}, {name: '北大'}, {name: '浙江'}, {name: '复旦'}];
const editName = (originName, name, arr)=> arr.map(item => item.name === originName ? {...item, name} : item);
const updatedSchools = editName("北大", "北京大学", schools);

console.log(updatedSchools); // => [{name: '清华'}, {name: '北京大学'}, {name: '浙江'}, {name: '复旦'}]
console.log(schools); // => [{name: '清华'}, {name: '北大'}, {name: '浙江'}, {name: '复旦'}]
Array.reduce函数

reduce 和 reduceRight 函数可用于把数组转换成任何值,包括数字、字符串、布尔值、对象、甚至函数

const ages = [21, 19, 42, 40, 64, 63, 34];
Math.max(...ages);	// 64

// 数组转换成数字
const max = ages.reduce((max, value)=> max > value ? max : value, ages[0]);
console.log(max)  // => 64


// 数组转换成对象
const schoolArray = [{id: 'qinghua', name: '清华', rank: 1}, {id: 'beida', name: '北大', rank: 2}, {id: 'fudan', name: '复旦', rank: 3}];
const schoolHashMaps = schoolArray.reduce((results, {id, name, rank})=>{
    results[id] = {name, rank};
    return results;
}, {});

// {
// 	beida: {name: '北大', rank: 2}
// 	fudan: {name: '复旦', rank: 3}
// 	qinghua: {name: '清华', rank: 1}
// }



const colors = ['red', 'green', 'red', 'blue', 'green'];
Array.from(new Set(colors));	// ['red', 'green', 'blue']


const uniqueColors = colors.reduce((arr, color)=> arr.includes(color) ? arr : [...arr, color], []);
// ['red', 'green', 'blue']

高阶函数

高阶函数对函数式编程非常重要。高阶函数指用于处理其他函数的函数, 其参数是函数或返回函数,或二者兼而有之

  • 以函数作为参数或返回值的函数被称为高阶函数
  • 高阶函数主要利用函数的闭包来实现缓存特性
  • Array.map、 Array.filter、 Array.reduce都接受函数作为参数,因此它们是一等高阶函数;
方法功能
map转换数据
reduce收集结果
filter删除不需要的元素
  • 高阶函数map(也称为collect)能够将一个迭代函数有序地应用于一个数组中的每个元素,并返回一个长度相等的 新数组

  • map是一个从左到右遍历的操作,对于从右到左的遍历,必须先反转数组。JavaScript中的Array.reverse()操作是不能这样使用的,因为它会改变原数组。

  • 高阶函数reduce将一个数组中的元素精简为单一的值;该值是由每个元素与一个累积值通过一个函数计算出来的。

  • reduce是一个会应用到所有元素的操作,这意味着没有办法将其"短路"来避免其应用于整个数组。 假设需要对一组输入值进行校验,也许你会想到reduce将其转换为一个布尔值来表示所有参数是否合法。 但是redude会比较低效,因为它会访问列表中的每一个值

  • filter操作以一个数组为输入,并施加一个选择条件p,从而产生一个可能较原始数组更小的子集

// 该函数用于让指定函数只能执行一次
const once = (fn)=>{
    let done = false;
    return function(){
        if(!done){
            fn.apply(this,fn);
        }else{
            console.log("this fn is already execute");
        }
        done = true;
    }
}
    
function test(){
    console.log("test...");
}
let myfn =  once(test);

myfn();    // test...
myfn();    // this fn is already execute

递归

递归技术指的是重新调用自身的函数;如果要解决的问题涉及循环,往往就可以转而使用递归函数;递归模式特别适合在异步操作中使用。 函数在做好准备时调用自身,例如数据可用时,或者当计时器结束时

递归不是一个容易掌握的概念。与函数式编程一样,最难的部分是忘记常规的方法; 一个复杂的工作系统总是从一个简单的系统发展而来的

// 假设任务是: 从10开始倒计时
1、我们可以使用for循环
for(let i = 10; i >=0; i--){
    console.log(i)
}


2、使用递归
const countDown2 = (value)=>{
    if(value > 0){
        console.log(value)
        countDown2(value - 1);
    }
}


// 改进方法
const countDown = (value, fn)=>{
    fn(value)
    return value > 0 ? countDown(value - 1, fn) : value;
}

countDown(10, value=>console.log(value));

函数式编程基础

函数柯里化的概念

  • 柯里化是一种词法作用域(闭包),它返回的函数是一个接收后续参数的包装器(简单嵌套函数)
  • 函数柯里化是一种在所有参数被提供之前,挂起或"延迟"函数执行,将多参函数转换为一元函数序列的技术
  • 所谓柯里化就是把一个多参数的函数,转化为单参数的函数(同一个函数被调用多次处理多个参数)
  • 将多个参数函数拆解为单参数的依次调用;就是利用函数执行可以形成一个不销毁的私有作用域; 把预先处理的内容放到不销毁的作用域里面,返回一个函数供以后使用
// 柯里化方法
function curry(func){
	
	return function curriedFn(...args){
		
		if(args.length < func.length){
			return function(){
				// 等待传递的剩余参数
				// 第一部分参数在args里面,第二部分参数在arguments里面
				// console.log("Arguments: ", arguments)
				return curriedFn(...args.concat(Array.from(arguments)));
			}
		}
		
		
		return func(...args);
		
		
	}

}
// 示例
function getSum(a, b, c){
	return a + b + c;
}

const curriedTest = curry(getSum)

console.log(curriedTest(1, 2, 3))  // 6
console.log(curriedTest(1)(2, 3))  // 6
console.log(curriedTest(1, 2)(3))  // 6

柯里化的函数求值

将函数的返回值作为传递给一元函数是十分容易的,但如果目标函数需要更多的参数呢? 为了理解JavaScript的柯里化,首先必须了解柯里化求值和常规(非柯里化的)求值之间的区别。

  • JavaScript是允许在缺少参数的情况下对常规或非柯里化函数进行调用。 换句话说,如果定义一个函数f(a,b,c),并只在调用时传递a, JavaScript运行时的机制会将a和c设为undefined;

常规或非柯里化函数

求值(定义) f(a)   ------> 运行(调用) f(a, undefined, undefined) 
// 在缺少参数的情况下调用非柯里化函数会导致缺失参数的实参变成undefined

柯里化函数

  • 柯里化函数,它要求所有参数都被明确地定义,因此当使用部分参数调用时,它会返回一个新的函数,在真正运行之前等待外部提供其余的参数

  • 柯里化是一种在所有参数被提供之前,挂起或"延迟"函数执行,将多余函数转换为一元函数序列的技术

求值                 结果
f(a)       ----->    f(b, c)
f(a, b)    ----->    f(c)
f(a, b, c) ----->    result

// 柯里化函数f,只有提供了所有参数,该函数才会输出具体的结果,否则它会返回另一个等待参数传递的函数;于此同时,我们可以看到curry是一种函数到函数的映射

柯里化与偏应用

柯里化与偏应用都是函数式编程中常用的工具

名称定义区别
柯里化 (Currying)柯里化是指固定一个函数的部分参数,生成一个更少参数的函数(待后续传入)固定一个函数的一个或者多个参数,也就是将一个 N元函数转换成一个N - x 的函数
偏函数 (Partial application)是一种将使用多个参数的函数转换成逐步返回的使用一个参数的函数的技巧将一个多参数函数转换成多个单参数函数,也就是将一个N元函数转换成N个嵌套的一元函数
// partial可应用于任何含有多个参数的函数。
const partial = function(fn, ...partialArgs){
	let args = partialArgs;
	
	return function(...fullArguments){
		
		let arg = 0;
		for(let i = 0; i < args.length && arg < fullArguments.length; i++){
			if(args[i] === undefined){
				args[i] = fullArguments[arg++]
			}
		}
		return fn.apply(null, args);
	}
}

// 柯里化方法
function curry(func){
	
	return function curriedFn(...args){
		
		if(args.length < func.length){
			return function(){
				// 等待传递的剩余参数
				// 第一部分参数在args里面,第二部分参数在arguments里面
				// console.log("Arguments: ", arguments)
				return curriedFn(...args.concat(Array.from(arguments)));
			}
		}
		
		
		return func(...args);
		
		
	}

}

函数的组合

如果一个值需要经过多个函数,才能变成一个(期望的)值,就可以把所有中间步骤合并成一个函数,这就叫做函数的组合(合成)

  • 函数式程序是把逻辑拆分成一系列关注特定任务的小型纯函数。最终,把这些小型函数整合到一起。 具体来说要综合运用,就需要按一定顺序或并行调用,或者合成一些大型函数,直到构建出一个应用为止;

  • 函数式程序是由一些简单函数组成的,尽管每个函数只完成一小部分功能,但组合在一起就能够解决很多复杂的任务, 函数组合是一种将分解的简单任务组织成复杂行为的整体过程

  • 组合像一些列管道那样,把不同的函数联系在一起,数据就可以[ 也必须 ] 在其中流动----毕竟纯函数就是输入对输出

PS: 函数的组合有点像(如下图)工厂的生产流水线,一条生产线由多台相关机器组合而成,每台机器在整条生产线中负责完成自己的生产工序;与此同时,每台机器又不局限于某一条生产线,它可以单独完成特定功能,也将它组合到其他生产线上完成特定功能;

面包生产线.png

2a978d276d3f9fe30a1ba1e18b745f7e.jpg

  • 函数式程序的目标就是找到那些可以被组合的结构,这正是函数式编程的核心
  • 为了能够正确的使用组合,所选的函数必须是无副作用的
function compose(fns){
	let args = arguments;
	let start = args.length -1;
	
	return function(){
		let i = start;
		let result = args[start].apply(this, arguments);
		
		while(i--){
			result = args[i].call(this, result);
			return result;
		}
	}
}
// compose接受的参数是函数,返回一个函数; 
const compose = (...fns) => arg => fns.reduce((composedValue, fn) => fn(composedValue), value)

// 格式化书写
const compose = (...fns)=>{
	return (value)=>{
		return fns.reduce((composedValue, fn)=>{
			return fn(composedValue);
		}, value);
	}
}
// 示例: 将数组反转,并将第一个元素的字母转为大写

const reverse = arr => arr.reverse();
const first = arr => arr[0];
const toUpper = s => s.toUpperCase();

const fTest = compose(reverse, first, toUpper);
console.log(fTest(['one', 'two', 'three']));

组合和管道

  • 组合(compose)和管道(pie)做相同的事情,只是数据流方向不同而已;
  • 组合compose的函数是从右往左执行每个函数;
  • 管道(或序列)pipe的函数是由左到右地执行每个函数;
  • 为避免在团队成员间引起混淆,项目中选择只用一种,不要同时使用;

方法链与函数管道

名称实现特点
方法链通过对象的方法紧密连接紧耦合、表现力有限
函数的管道化函数作为组件连接松耦合 灵活
// 方法链
/**
 * 将字符串以英文逗号分割成数组并且去掉前后空格
 * @param str
 * @returns {[]}
 */
trimSplitComma: str => {
    if (!str)  return [];
    return str.trim()
                      .split(',')
                      .filter(item => !!item)
                      .map(item => item.trim());
}

函数式编程实践

需求: 构建一个时钟,以民用时间格式显示时分秒和上下午。每个字段都由两个数字组成,如果时间只有一个数字,则在前面添上零。时钟每隔一秒走一次

  • 非函数式写法
setInterval(logClockTime, 1000);

function logClockTime() {
  // 获取时间字符串
  let time = getClockTime();

  // 清空控制台,输出时间
  console.clear();
  console.log(time);
}

function getClockTime() {
  // 获取当前时间
  let date = new Date();
  let time = "";

  let timeObj = {
    hours: date.getHours(),
    minites: date.getMinutes(),
    seconds: date.getSeconds(),
    ampm: "AM",
  };

  // 转换成民用格式(12小时计)
  if (timeObj.hours >= 12) { timeObj.ampm = "PM"; }
  if (timeObj.hours > 12) { timeObj.hours -= 12; }

  // 给时分秒前面+0
  if (timeObj.hours < 10) { timeObj.hours = "0" + timeObj.hours; }
  if (timeObj.minites < 10) { timeObj.minites = "0" + timeObj.minites; }
  if (timeObj.seconds < 10) { timeObj.seconds = "0" + timeObj.seconds; }

  return timeObj.hours + ":" + timeObj.minites + ":" + timeObj.seconds;
}

  • 函数式编程写法
const compose = (...fns)=>{
    return (value)=>{
        return fns.reduce((composed, fn)=>{
                return fn(composed);
        }, value);
    }
}

const oneSecond = () => 1000;
const getCurrentTime = () => new Date();
const clear = (a)=> {
	console.clear()
};
const log = message => console.log(message);

const serializeClockTime = date => ({
	hours: date.getHours(),
	minites: date.getMinutes(),
	seconds: date.getSeconds()
})

const civilianHours = clockTime => ({
	...clockTime,
	hours: clockTime.hours > 12 ? clockTime.hours - 12 : clockTime.hours
})

const appendAMPM = (clockTime)=>({
	...clockTime,
	ampm: clockTime.hours >= 12 ? 'PM' : 'AM'
})

const display = target => time => target(time);

const formatClock = format => time => format.replace("hh", time.hours).replace("mm", time.minites).replace("ss", time.seconds).replace("tt", time.ampm);

// 添加前导0
const prependZero = key => clockTime => ({
	...clockTime,
	[key]: clockTime[key] < 10 ? "0" + clockTime[key] : clockTime[key]
})

// convertToCivilianTime 一个独立的函数,接受时钟时间为参数,把它转换成民用时间格式

// doubleDigits 一个独立函数,接受民用时钟时间为参数,添加所需的前导零,确保时分秒都显示为两位数

// startTicking 设置一个时间间隔,每隔一秒调用一次回调,启动时钟。回调是使用我们自定义的函数合成的。 每隔一秒清理控制台、获取当前时间、转换格式
// 变成民用格式、格式化、最后显示

const convertToCivilianTime = clockTime => compose(appendAMPM, civilianHours)(clockTime)

// 上面写法相当于这样处理
//const convertToCivilianTime = (clockTime) => {
//	return compose(appendAMPM, civilianHours)(clockTime);
//}

const doubleDigits = civilianTime => compose(prependZero("hours"), prependZero("minites"), prependZero("seconds"))(civilianTime)

// const doubleDigits = civilianTime => {
//	 return compose(prependZero("hours"), prependZero("minites"), prependZero("seconds"))(civilianTime);
// }


const startTicking = () => {
	setInterval(
		compose(
			clear, 
			getCurrentTime, 
			serializeClockTime, 
			convertToCivilianTime,
			doubleDigits,
			formatClock("hh:mm:ss tt"),
			display(log)
		), 
		oneSecond()
	);
}

startTicking();

RamdaJS 和 LodashJs

LodashJS

  • Lodash 是一个一致性、模块化、高性能的JavaScript实用工具库 Lodash
为什么选择 Lodash ?
  • Lodash 通过降低 array、number、objects、string 等等的使用难度从而让 JavaScript 变得更简单。 Lodash 的模块化方法 非常适用于:

    1.遍历 array、object 和 string 2.对值进行操作和检测 3.创建符合功能的函数

RamdaJS

  • 一款实用的JavaScript函数式编程库 Ramda
Why Ramda?
  • 目前已经存在许多优秀的函数式的库。通常它们作为通用工具包,可以用于多种编程范式。
  • Ramda 的目标更为专注:专门为函数式编程风格而设计,更容易创建函数式 pipeline、且从不改变用户已有数据;
What's Different?
  • Ramda 主要特性如下:

    1.Ramda 强调更加纯粹的函数式风格。数据不变性和函数无副作用是其核心设计理念。这可以帮助你使用简洁、优雅的代码来完成工作。 2.Ramda 函数本身都是自动柯里化的。这可以让你在只提供部分参数的情况下,轻松地在已有函数的基础上创建新函数。 3.Ramda 函数参数的排列顺序更便于柯里化。要操作的数据通常在最后面。 4.最后两点一起,使得将多个函数构建为简单的函数序列变得非常容易,每个函数对数据进行变换并将结果传递给下一个函数。Ramda 的设计能很好地支持这种风格的编程。

“路漫漫其修远兮 吾将上下而求索!”,前路很漫长,欢迎指导!欢迎一起交流!欢迎一起学习!