1.无副作用(No Side Effects)
2.高阶函数(High-Order Function)
简述:
函数式编程的概念来自于数学中的函数,即自变量映射。中心思想是指:一个函数的返回值,仅仅依赖于参数的值,而不会因为其他外部的状态而不同。比如一个求幂的函数pow(base,exponent),它的计算结果仅仅依赖于base(基数)和exponent(指数)的不同而不同。这个函数无论我们调用多次,只要参数一致,那么返回值绝对一致。
在代码构建的过程中,我们很难将所有函数都构建成符合函数式编程的思维的范式。但如果应用函数式编程,则它的好处主要体现于:引用透明、无副作用、无竞争态、惰性求值。
无副作用:
函数在变现方式上,我们可以将其区分为纯函数和非纯函数。有以下区分:
纯函数的定义是:
-
如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
-
该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。
let seed = 0; //定义一个外部变量
const sum = (x,y)=> x+y; //一个用于求和的函数
sum(10,2); //结果12
sum(10,2); //结果12
console.log(seed); //输出:0
非纯函数:在参数一致的情况下,返回值可能不一致的函数
let seed = 0; //定义一个外部变量
const sum= (x,y)=>{x+y+(++seed)};
sum(10,2); //13
sum(10,2); //14
console.log(seed); //2
纯函数和非纯函数最大的两个不同的表现在于:副作用性和引用透明性。
副作用性是指,该函数的调用过程中,是否对主函数(调用者)产生了附加影响,例如修改了函数外的变量或者参数,我们就认为该函数是有副作用的函数。
引用透明是指,函数的运行不依赖于外部的变量或者状态,仅仅依赖于输入的参数。如果程序中任意两处具有相同输入值的函数调用能够互相置换,而不影响程序的动作,那么该函数就具有引用透明性。
非纯函数造成的最大的问题,就是其不可预知性。如果代码比较复杂时,会为我们梳理程序运行逻辑造成一定的困难。因此,在函数式编程思维中,我们应尽可能的确保我们编写的函数是纯函数。
JavaScript内置对象中的非纯函数
Math.ramdon()、console.log()、element.addEventListener()、Date.now()、Array.prototype.sort()、ajax操作等等
副作用主要表现于:
-
I/O 操作:其结果本身就是无法预估的,因此无法判断给定了的参数,是否能给予我们预期的返回结果;比如接收输入、或者将结果输出。
-
改变全局变量原有值,或者改变参数对象值及其属性:其执行结果也是带有副作用的。
-
抛出异常或以错误中止:函数除了返回一个值之外,还可能发生不太确定的执行结果。
Array
是一个专门用于操作数组的对象,是所有数组的父级对象,设定了所有数组可调用的方法有哪些。
我们常用 slice()
和 splice()
来作为纯函数和非纯函数的典型示例:
函数 | 作用 |
---|---|
slice( [begin, end] ) | 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。 |
splice(start[, deleteCount]) | 方法通过删除现有元素和/或添加新元素来修改数组,并以数组返回原数组中被修改的内容。 |
假设我们现在有一个数组 [1,3,5,7,9]
,当我们希望从 begin
位置开始检索,获取到 end
位置时,我们会这么去做:
const arr = [1,3,5,7,9];
arr.slice(1,3); //[3,5]
arr.slice(1,3); //[3,5]
假设在数组[1,3,5,7,9]中删除相应数据时:
const arr=[1,3,5,7,9]
arr.splice(1,3); //[3,5,7]
arr.splice(1,3); //[9]
arr.splice(1,3); //[]
在一段程序中,我们无法保证所有的函数都是纯函数。但纯函数的覆盖面越大,对于调试、缓存数据及线程安全都会提供越多的便利。有一种说法是,保证80%的函数是纯函数即可。
高阶函数:(High-Order Function)
一等公民
高阶函数(High-Order Function)是函数式编程思维中的重要条件,而满足该条件的编程语言则需要将函数作为该语言的一等公民来看待。符合一等公民的条件是:
-
函数可以作为一种数据类型的值,赋值于一个变量;
-
函数可以作为参数,在其他函数中进行传递;
-
函数可以作为返回值,在其他函数中返回;
将函数视作一等公民的语言有:JavaScript、Golang、Python、Scala、Lua、Lisp、Scheme等。同时,有着越来越多的其他语言看上了函数式编程的出彩之处,以其特有的方式实现着符合自身编程方式的高阶函数。
函数类型:
在JavaScript中,函数是数据类型object的子类型 --- 即是指一种对象类型,可以通过typeof操作符检测一个值是否是一个函数类型:
function foo(x){
return x+10;
}
typeof foo; //返回值 ‘function’
然而,实际上在JavaScript中,函数并非是基本数据类型,函数隶属于对象类型。能够使用typeof操作符进行检测仅仅是语言提供的便利:
// 声明一个 number 类型的变量
const num = 13;
// 一个函数声明
function foo(x){
return x + 10;
}
typeof foo; // 结果:'function'
typeof num; // 结果:'number'
// 检测 num 所持有的值是否是对象
num instanceof Object; // 结果:false
// 检测 foo 所持有的值是否是对象
foo instanceof Function; // 结果:true
Function instanceof Object; // 结果:true
foo instanceof Object; // 结果:true
可见,函数是一个隶属于Function的对象,而Function本身又隶属于顶层Object,是它的子对象。因此,一个函数的实例,也隶属于Object,他们之间拥有间接的继承关系。
很多有 面向对象
经验的同学可能会想,实例化对象不是通过 new
关键字调用类的构造函数来进行的吗?这个问题很简单:拿 Java
例举,Java
中拥有字面量形式的对象声明方式 String str = "I like Java!";
,声明变量 str
的过程中实际上也构建了一个字符串对象,并没有显式的使用关键字 new
。
在 JavaScript
中,以字面量的方式构建对象有很多种,比如:
// 数组字面量
const arr = [1,2,3,4];
// 对象字面量
const obj = {id:1,value:'I love YOU'};
// 函数字面量
function foo(){}; //函数声明
const foo = function(){} //函数表达式
const foo = (x)=>{x+3} // ES6提供的lambda表达式函数
函数入参
由于函数也是一个对象,因此函数也可以在作为其他函数的参数,作为入参:
// 一个函数声明
const add2 = (x) => x + 2; // 参数x基础上加2的函数
const sub2 = (x) => x - 2; // 参数x基础上减2的函数
const result = (y, f) => y * f(y); // 参数y基础上乘以 函数参数f 的运算结果
result(4, add2); // 结果: 4*6=24
result(4, sub2); // 结果: 4*(4-2)=8
在JavaScript内置对象中,也有非常多的函数入参实例,比如Array.prototype.map函数的简单实现:
//一个高仿的Array.prototype.map 函数
const map = (arr,f)=>{
const t = []
for(let i=0;i<arr.length;t.push(f(arr[i],i++,arr)));
return t;
}
//声明一个数组
const a1= [1,2,3,4];
//map函数调用:参数f 所示为每一个值乘以3,并且返回一个新数组
const a1 = map(a1,(e)=>{e*3}); // [3,6,9,12]
函数返回值
函数可以作为参数,当然也可以作为返回值。作为返回值的函数,常用于缓存上一个函数的执行状态:
// 一个函数声明
const add2 = (x) => x + 2; // 参数x基础上加 2 的函数
const mul2 = (x) => x * 2; // 参数x基础上乘以 2 的函数
// 该函数用于计算,计算结果不会直接得出,而是缓存了用于计算的两个函数
const calc = function(f1, f2){
return (y) => f1( f2(y) );
}
// 根据缓存顺序不同,生成的新的函数执行过程也不同
const c01 = calc(add2, mul2);
c01(3); // 结果: 8
const c02 = calc(mul2, add2);
c02(3); // 结果:10