相关文章
【函数式编程】基于JS 进行函数式编程(一)引入 | 什么是函数式编程 | 函数式编程的优点
【函数式编程】基于JS进行函数式编程(二)高阶函数 | 函数代替数据传递 | 函数是一等公民 | 闭包 | 使用高阶函数实现抽象 | 数组的高阶函数
【函数式编程】基于JS进行函数式编程(三)柯里化 | 偏函数 | 组合与管道
【函数式编程】基于JS进行函数式编程(四)函子 | MayBe函子 | Monad函子
函数作为参数传递是js规范中的一部分。而,
允许以函数代替数据传递是一个值得关注的概念
。
我们把接受函数作为其参数的函数称为高阶函数(HOC)
。
理解函数代替数据传递
函数是一等公民
我们知道,js支持以下几种数据:
- Number
- String
- Boolean
- Object
- null
- undefined
但是,值得注意的是,函数也可以作为js的一种数据类型
!所以,在js中与对String和Number类型的操作类似,我们可以把函数存入一个变量等方式进行传递。
那么,当一门语言允许函数作为任何其他数据类型使用时,函数就被称为“一等公民”
。即,函数可被赋值给变量,作为参数传递,也可被其他函数返回。
简言之,在js中我们就把 函数 理解为 数据 ! 既然它是数据,就可以把它存入一个变量,如: let fn = () =>{} //fn是一个指向函数类型的变量 ,fn是函数的引用 fn(); //调用,指向fn指向的函数
- 那么,具体是怎么
“传递函数”
呢?
我们看下面这段示例代码:
let fn = () => {console.log('本函数fn作为作为数据进行传递')} let tellType = (arg) => { if(typeof arg === "function"){arg();} else {console.log('传递过来的数据是'+ arg)} } tellType(fn); //输出 :本函数fn作为作为数据进行传递
- 那,怎么
返回函数
呢?
let fn = () => {return String} fn(); //String() { [native code ]} //返回一个指向String函数的函数引用(fn值返回函数引用,并没有调用。) fn()("abc"); //调用
由此可见,高阶函数式接受函数作为参数,并且/或者返回函数作为输出的函数。
闭包
高阶函数的运行机制,得益于js中的闭包。
什么是闭包
简言之,闭包是一个内部函数(即,是在另一个函数内部的函数)。
例如:
functioin outer() { function inner() {} //inner()就是一个闭包函数 }
之所以闭包函数强大,在于它对作用域链/层级的访问。
通常,闭包有3个可访问的作用域:
- 1、在闭包函数,自身之内声明的变量,如:
function outer() { function inner() { let a = 5; console.log(a); } inner();//调用inner函数,输出5。 }
值得注意的是:inner函数在outer函数外部是不可见的!
- 2、对全局变量的访问,如:
let global = 'global'; function outer() { function inner() { let a = 5; console.log(global); } inner();//调用inner函数,输出global。 }
- 3、
对外部函数变量的访问(关键)
,该性质使得闭包函数变得非常强大!
如:
let global = 'global'; function outer() { let outer = "outer"; function inner() { let a = 5; console.log(outer); } inner();//调用inner函数,输出outer。 }
由此,我们将外部函数成为包裹闭包函数的函数。
闭包可记住它的上下文
先看一个示例代码:
let fn = (arg) => { let outer = "visible"; let innerFn = () => { console.log(outer); console.log(arg); } return innerFn; } let closureFn = fn(5); //1、fn被参数5调用,返回innerFn。当innerFn被返回时, js执行引擎将innerFn作为一个闭包,并相应地设置它的作用域。 //2、返回函数的引用存储在closureFn中。当closureFn通过作用域链被调用时就记住了arg、outer值! closureFn();//输出: visible 5
抽象
高阶函数通常用在能够抽象通用的问题
。即,高阶函数就是在定义抽象。
抽象:是一种管理计算机系统复杂性的技术。它通过建立一个人与系统进行交互的复杂程序,把更复杂的细节抑制在当前水平之下。程序员应该使用理想的界面,并且可以添加额外级别的功能,否则处理起来将会复杂!
即,抽象让我们专注于预定的目标,而无须关心底层的系统概念。
使用高阶函数实现抽象
例如:
const forEach = (array,fn) => { for(let i=0;array.length;i++){ fn(array[i]) } } forEach([1,2,3],(data)=>{ //data作为参数从forEach函数传到当前的函数 });
//检查数组的内容是否为一个数字、自定义对象或其他类型 const every = (arr,fn) => { let result = true; for(const value of arr) result = result && fn(value); return result; } every([NaN,NaN,3],isNaN);//检查给定的数字中是否有NaN类型的内容
//some函数:如果数组中的一个元素通过传入的函数返回true,some函数就返回true const some = (arr,fn) => { let result = false; for(const value of arr) result = result || fn(value); return result; } some([NaN,NaN,3],isNaN);//true
//unary const unary = (fn) => { fn.length === 1 ? fn : (arg) => fn(arg) } let res = ['1','2','3'].map(unary(parseInt)) console.log(res); //输出 [1,2,3]
//once const once = (fn) => { let done = false; return function() { return done ? undefined: ((dine=true),fn.apply(this,arguments)) } } //声明一个名为done的变量,初始值为false。返回的函数会形成一个覆盖它的闭包作用域。因此,返回的函数会访问并检查done是否为true,如果是,则返回undefined,否则将done设为true,并用必要的参数调用函数fn。
注:apply函数允许我们设置函数的上下文,并为给定的函数传递参数。
//memoized:使函数记住其计算结果 const memoized = (fn) => { const lookupTable = {}; //返回函数将接受一个参数并检查它是否在lookupTable中,如果在,返回对应的值,否则使用新的输入作为key,fn的结果作为value,更新lookupTable对象 return (arg) => lookupTable[arg] || (lookupTable[arg] = fn(arg)); } let fastFactorial = memoized((n)=>{ if(n===0){return 1;} return n* fastFactorial(n-1); })
对数组有用的高阶函数
在js中,常需要对数组进行遍历。同时我们也使用数组进行存储、操作和查找以及转换数据格式等操作。
那么,在函数式编程中有哪些对数组有用的高阶函数呢?
map
forEach函数隐藏了遍历的通用问题,但是我们不能在所有的情况下都是用forEach。例如:假设把所有的数组内容都平方并在一个新的数组中返回。通过forEach要如何实现?forEach只能执行传入的函数,不能用来返回数据。
所以,这里我们想到map。
const map = (array,fn) => { let results = [] for(const value of array) results.push(fn(value)); return results; } map([1,2,3],(x)=>x*x); //[1,4,9]
filter
//筛选数组 const filter = (array,fn) => { let results = [] for(const value of array) (fn(value)) ? results.push(value) : undefined; return results; } filter([{"rating":[4.5]},{"rating":[4.6]},{"rating":[3.5]}],(item)=>item.rating[0]>4.5); //[{"rating":[4.6]}]
concatAll
//把所有嵌套数组连接到一个数组中 const concatAll = (array,fn) => { let results = [] for(const value of array) results.push.apply(results,value); return results; }
reduce
- 归约操作:对所有数组重复某个过程
//1、当initivalValue未定义时,我们从第二个元素开始循环数组,将它作为累加器的初始值。 //2、如果initivalValue由调用者传入,就需要遍历整个数组。 const reduce = (array,fn,initivalValue) => { let accumlator ; if(initivalValue !=undefined) accumlator = initivalValue; else accumlator = array[0]; if(initivalValue === undefined) for(let i=1;i<array.length;i++) accumlator = fn(accumlator,array[i]) else for(const value of array) accumlator = fn(accumlator,value) return [accumlator] } reduce([1,2,3],(acc,val)=>acc+val) //[6] reduce([1,2,4],(acc,val)=>acc*val,1) //[8]
zip
- 合并两个给定的数组
const zip = (leftArr,rightArr,fn) => { let index,results = []; for(index=0;index<Math.min(leftArr.length,rightArr.length);index++) results.push(fn(leftArr[index],rightArr[index])); return results; } zip([1,2,3],[4,5,6],(x,y)=>x+y); //[5,6,7]