函数式编程(FP,functional programming)

函数式编程是一种编程模式。将代码抽象成各种各样的函数,并且将函数当做一等公民,能够作为其他函数的参数或者返回值传递。

JavaScript 既支持面对对象编程,也支持函数式编程。
面向对象编程是一种编程模式,将代码抽象成各种各样的对象。
面向对象的三大特性:

  1. 封装:将属性和方法封装到一个对象或者类中,就称之为封装。
  2. 继承:子类可以继承父类的属性和方法,可以减少重复的代码和逻辑。
  3. 多态:不同的数据类型进行同一个操作,表现出不同的行为。
function add(param1, param2) {
	return param1 + param2
}
add(1, 2)
add('Hello', 'World')

但是也有人认为 JS 中的这种表现不是面对对象语言中严格意义上的多态。这个问题千人千面,并没有一个官方解释。

函数式编程常用的核心概念:

声明式代码:

声明式的代码会描述一系列的操作,但是并不会暴露它们内部是如何实现的。

例如SQL语句就是一种很典型的声明式编程,它由一个个描述查询结果应该是什么样的断言组成,对数据检索的内部机制进行了抽象。

// 命令式
var makes = [];
for (var i = 0; i < cars.length; i++) {
  makes.push(cars[i].make);
}

// 声明式
var makes = cars.map(function(car){ return car.make; });//map()方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

命令式的写法要求先实例化一个数组,然后再直接迭代cars列表,手动增加计数器,push进数组。它指明的是怎么做。

声明式的写法是一个表达式,如何进行计数器迭代、返回的数组如何收集,这些细节都隐藏了起来。它指明的是做什么,而不是怎么做。

不可变数据:

不可变数据是指创建后不能更改的数据。

javaScript里的基本类型(String、Number等)从本质上是不可变的,但是对象就是在任意的地方都可变。

var sortDesc = function(arr) {
    return arr.sort(function(a, b) {
            return b - a    
      })
 } 
 var arr = [1, 3, 2]
 sortDesc(arr) // [1, 2, 3]
 arr // [1, 2, 3]

无副作用:

指的是调用函数不依赖于外部环境,不会产生附加的影响。这里的依赖可能是读取外部变量,也可能是修改外部变量、修改参数等。

一个函数如果依赖外部环境,那么它的行为就会变得不可预测。因为不知道外部环境什么时候就被改变了。

var counter = 0
function add() {
    return ++counter // 有副作用。既引用了外部变量,也修改了外部变量
}
console.log(add())//1
console.log(add())//2

引用透明:

指一个函数只会用到传递给它的变量以及自己内部创建的变量,不会使用到其他变量。

var a = 1;
var b = 2;
// 函数内部使用的变量并不属于它的作用域
function test1() {
  return a + b;
}
// 函数内部使用的变量是显式传递进去的
function test2(a, b) {
  return a + b;
}

纯函数:

给定相同的输入,总是返回相同的输出,而且没有任何副作用的函数,叫做纯函数。

纯函数的优势在于,它保证了其"纯粹性",同样的输入无论调用多少次,都会是一样的输出,并且不用担心调用过程会修改外部环境。这让我们在做代码复用或者重构时,不用去担心函数是否会影响到其他地方。

slice() :对于相同的输入,总是给出相同的输出,且不会修改到原数组,因此是纯函数。
splice() :会修改到原数组,因此不是纯函数。

function fn(num1, num2) {
	return num1 + num2 // 对于相同的输入,总是给出相同的输出,且执行过程中没有副作用
}
fn(10, 20)

函数柯里化:

传递给函数一部分参数來调用它,让它返回一个函数去处理剩下的函数,这个过程就称之为函数柯里化。

function fn1(x, y, z) {
	console.log(x +y +z)
}
fn1(10, 20, 30)

// 函数柯里化之后:
function fn2(x) {
	return function(y) {
		return function(z) {
			console.log(x +y + z)
		}
	}
}
fn2(10)(20)(30)

事实上,函数柯里化是一种预加载函数的方法,通过传递少量的参数,得到一个已经记住了这些参数的新函数,某种意义上来说,这是一种对参数的缓存,是一种比较高效的编写函数的方法;并且每个函数职责单一,还可以复用。但是,由于形成了闭包,也会造成内存泄漏。因此,不要滥用函数柯里化。

自动柯里化函数:
function fn(x, y, z) {
	console.log(x +y +z)
}

// 自动函数柯里化:传入一个函数作为参数,返回一个新的柯里化之后的函数
function currying(fn) {
	function curryFn(...args) {
		// 如果传入柯里化之后的函数的参数个数已经大于等于原函数的参数个数,那么执行原函数
		if (args.length >= fn.length) {
			return fn(...args)
		} else {
			// 如果传入柯里化之后的函数的参数个数小于原函数的参数个数,那么返回一个新的函数,继续柯里化的过程
			return function (...newArgs) {
				return curryFn(...args.concat(newArgs))
			}
		}
	}

	return curryFn
}

var fnCurry = currying(fn)
fnCurry(10)(20)(30)

函数组合:

函数组合是将两个或更多的函数组合成一个新函数,依次调用的过程。是为了解决函数嵌套,形成洋葱代码 h(g(f(x))) 的问题。

例如:对一个数字先乘以 2,然后加 10。

如果操作很多,嵌套的函数就会非常深,而且代码是由内往外执行的,不直观。

function double(num) {
	return num * 2
}
function pow(num) {
	return num ** 2
}
console.log(pow(double(10))) // 形成了洋葱代码

可以将函数进行组合来解决。

// 将上面的两个函数组合在一个,返回一个新的函数
function composeFn(...fns) {
	return function(...args) {
		// 从左到右执行传入的函数,第一个函数接收传入的参数作为其参数,其他函数接收前一个函数的返回值作为参数
		var result = fns[0](...args)
		for (var i = 1; i < fns.length; i++) {
			result = fns[i](result)
		}
		return result
	}
}
const compose = composeFn(double, pow)
console.log(compose(10))

高阶函数:

高阶函数指的是一个函数以函数为参数,或以函数为返回值,或者既以函数为参数又以函数为返回值。

var add = function (a, b) {
    return a + b;
};

function math(func, array) {
    return func(array[0], array[1]);
}

math(add, [1, 2]); // 3

与面向对象编程的对比:

面向对象编程(OOP)的应用状态经常是共享的,并且和方法一起定义在一些对象中,所以会导致数据修改的不确定性。

函数式编程由于所有的数据都是不可变的,所以所有的变量在程序运行期间都是一直存在的,非常占用运行资源。同时由于函数式的先天性设计导致性能一直不够,运行速度也不够快。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. 编程思想 函数式编程Functional ProgrammingFP)是一种编程思想,强调函数的运算而不是程序的状态。它将程序的运算视为数学函数的运算,避免了程序状态对计算结果的影响。面向对象编程(Object-Oriented Programming,OOP)则是一种以对象为基本单位的编程思想,它将数据和行为封装在一个对象中,通过对象间的消息传递实现程序的功能。 2. 变量和函数的定义 函数式编程中,变量是不可变的,函数也是不可变的。变量只能被赋值一次,函数也只能被定义一次。这种不可变性保证了程序的稳定性和可重复性。面向对象编程中,变量和函数都是可变的。对象的状态和方法可以随时被修改,这种灵活性使程序的开发和维护更加方便。 3. 控制流程的实现 函数式编程中,控制流程通常是通过函数的递归实现的。递归是一种简单而强大的控制流程,但它可能导致堆栈溢出和性能问题。面向对象编程中,控制流程通常是通过对象的方法调用实现的。方法调用可以实现类似于递归的功能,但它不会导致堆栈溢出和性能问题。 4. 数据结构的处理 函数式编程中,数据结构通常是不可变的。每次对数据结构的修改都会返回一个新的数据结构,而不是修改原有的数据结构。这种不可变性保证了程序的稳定性和可重复性。面向对象编程中,数据结构通常是可变的。对象的状态可以随时被修改,这种灵活性使程序的开发和维护更加方便。 5. 并发处理的实现 函数式编程中,函数是不可变的,没有共享的状态,因此可以很容易地实现并发处理。面向对象编程中,对象的状态可以被并发访问,因此需要采用同步机制来保证程序的正确性。这种同步机制可能导致死锁和性能问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值