函数式编程入门系列一
快速掌握一个知识体系的秘诀就是:抓住概念并理清概念之间的关系
函数
f(x) = Y;
//函数f以x为参数,返回输出Y
/*
1.函数必须总是接受一个参数
2.函数必须总是返回一个值
3.函数应该依据接收到的参数,例如x,而不是外部环信运行
4.对于一个给定的参数x,只会输出唯一的y
*/
函数是一段可以通过其名称被调用的代码,他可以传递参数并返回值
var simple = (a) => {retrun a}
方法是一段必须通过其名称及其关联对象的名称被调用的代码
var obj = { simple:(a)=>{retrun a}};
obj.ssimple(5) //用名称及其关联对象调用
引用透明性
所有的函数对于相同的输入都将返回相同的值,这一属性称之为引用透明性
//函数只是根据传入的参数i进行操作,函数内部并没有全局引入
//var i=1;
var identity = i => {
return i;
};
替换模型
sum(3, 4) + identity(5);
// 直接替换函数的结果,主要因为identity函数的内部逻辑不依赖于其他全局变量
sum(3, 4) + 5;
引用透明性在并发代码和可缓存代码中发挥着至关重要的作用
命令式,声明式与抽象
函数式编程主张声明式编程和编写抽象的代码
命令式语句
//任务: 打印数组的元素
//过程:我们告诉编译器,获取数组长度,循环数组,用下标索引获取每一个元素
//总结: 命令式编程主张告诉编译器'如何做'
var arr=[1,2,3,4,5]
for (var i=0;i<arr.length;i++){
console.log(i)
}
声明式语句
//命令式编程主张告诉编译器'做什么
var arr=[1,2,3,4,5];
arr.forEach((v,i,a)=>console.log(v)) //打印1,2,3,4,5
//使用了一个处理"如何做"的抽象函数,所以开发者只需要关系手头的任务就可以了
纯函数
对给定的输入返回相同的输出的函数
var double = (value) => value*2;
//输入2总是返回4,纯函数遵守引用透明性,所以我们可以将用数字4替换 double(2)
纯函数产生可测试的代码
纯函数不应该依赖任何外部变量,也不应该改变任何外部变量
并发代码
纯函数总是允许我们并发的执行代码,因为纯函数不会改变他的环境
js没有多线程来执行并发,如果有一段node环境中的服务端代码需要并发执行函数,又该怎么办?
let golbal = 10;
let func1 = (input) => {
global = 5
}
let func2 = () => {
if (golbal === 10) {
console.log("业务逻辑")
}
}
如果并发执行,func1在func2之前就执行,由于两个函数都依赖于全局变量,并发执行这些函数就会引起不良的影响
//改为纯函数
let func1 = (input,global) => {
//处理输入,改变global值
global = 5
}
let func2 = (global) => {
if (golbal === 10) {
console.log("业务逻辑")
}
}
可缓存
纯函数总是为给定的输入返回相同的输出,为什么要通过多次的输入来反复调用此函数呢?不能用函数的上一个结果代替函数调用吗?
var longRunningFunction=(ip)=>{
//耗时任务的函数,给定什么返回什么
}
var longRunningFnBookKeeper={2:3,4:5}
//检查key是否在记账对象中
//如果在返回结果,否则更新记账对象
longRunningFnBookKeeper.hasOwnProperty(ip) ?
longRunningFnBookKeeper[ip] :
longRunningFnBookKeeper[ip]=longRunningFunction(ip)
管道与组合
UNIX哲学,用组合或管道完成复杂的任务
纯函数是数学函数
js函数基础
- return语句是可选的
- 作用域
- 函数参数
- export和import
函数式的方法处理循环问题
const forEach = (array, fn) => {
let i;
for (i = 0; i < array.length; i++) {
fn(array[i])
}
}
const arr=['a','b','v','e','r'];
forEach(arr,(data)=>console.log(data))
高阶函数
接受另一个函数作为其参数的函数,称之为高阶函数(HOC - Higher-Order Function)
理解数据
当一门语言允许函数作为任何其他数据类型使用时,函数被称为一等公民
函数可以赋值给变量,作为参数传递,也可以被其他函数返回
js数据类型
Number,String,Boolean,Undefined,Null,Object,Symbol
存储函数
let fn = () => {} //fn就是一个指向函数数据类型的变量
typeof fn // 'function'
fn() //既然fn是函数的引用就可以这么调用
传递函数
var tellType = (arg) =>{
console.log(typeof arg);
}
let data=1;
tellType(data) //number
let dataFn=()=>{
console.log('function')
}
tellType(dataFn) // function
//如果传入的参数是函数就执行
var tellType = (arg) =>{
if(typeof arg === 'function'){
arg()
}else{
console.log(typeof arg);
}
}
返回函数
let crazy = () => { return String }
console.log(crazy()) //[Function: String] 只是返回了一个指向String函数的函数引用
let string=crazy();
console.log(string(123)) //'123'
抽象和高阶函数
高阶函数是接受函数作为参数,或者返回函数作为输出的函数
此函数抽象出了遍历数组的问题,使用forEach函数的用户就不用理解内部是如何实现遍历的
const forEach = (array, fn) => {
let i;
for (i = 0; i < array.length; i++) {
fn(array[i])
}
}
const arr=['a','b','v','e','r'];
forEach(arr,(data)=>console.log(data))
遍历一个js对象步骤:
1.遍历给定对象的所有key
2.识别key是否属于该对象本身
3.如果步骤2为true,则获取key的值
//forEach,forEachObject都是高阶函数,使开发者专注于任务
//通过传递相应的函数,而抽象出遍历的部分
const forEachObject = (obj, fn) => {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
fn(property, obj[property]) //以key和value作为参数调用
}
}
}
let person={name:'cc',age:18};
forEachObject(person,(k,v)=>console.log(v,k))
以抽象的方式实现对控制流程的处理
const forEach = (array, fn) => {
let i;
for (i = 0; i < array.length; i++) {
fn(array[i])
}
}
const unless=(predicate,fn)=>{
if(!predicate) fn()
}
forEach([1,2,3,4,5,6,7],(number)=>{
unless((number%2),()=>{
console.log(number,'is even')
})
})
/*
2 'is even'
4 'is even'
6 'is even'
*/
times高阶函数:接受一个数字,并根据调用者提供的次数调用传入的函数
const times=(times,fn)=>{
for(var i=0;i<times;i++){
fn(i)
}
}
const unless=(predicate,fn)=>{
if(!predicate) fn()
}
times(100,function(n){
unless(n%2,function(){
console.log(n,'is even')
})
})
真实的高阶函数
从简单的高阶函数逐步进入复杂的高阶函数
every函数
经常需要检查数字的内容是否为一个数字,自定义对象或者其他类型.
通常要编写循环来解决,现在我们将这些抽象到一个every函数中,接受两个参数:一个数据和一个函数
使用传入的函数检查数组的所有元素是否为true
const every = (arr, fn) => {
let result = true;
for (let i = 0; i < arr.length; i++) {
//fn需要返回一个布尔值,然后使用&&运算符确保所有的数组内容遵循fn给出的条件
result = result && fn(arr[i])
}
return result;
}
// isNaN作为fn传入,检查给定的数字是否为NaN
console.log(every([NaN,NaN,NaN],isNaN));// true
console.log(every([1,2,1,'1'],isNaN)) //false
console.log(every([1,2,1],isNaN)) //false
some函数
const some=(arr,fn)=>{
let result=false;
for(const value of arr){
result=result||fn(value)
}
return result
}
console.log(some([NaN,NaN,NaN],isNaN));// true
console.log(some([1,NaN,1,'1'],isNaN)) //true
console.log(some([1,NaN,1],isNaN)) //true
sort函数
compareFunction为可选,如果未提供,元素将被转换为字符串并按照Unicode编码点顺序排序
sort函数设计的非常灵活,以致于我们可以排序任何javascript数据,sort函数灵活的原因要归功于高级函数的本质
var people = [{
firstname: 'aaFirstName',
lastname: "ccLastName",
},{
firstname: "ccLastName",
lastname: "aaFirstName",
},{
firstname: "bbFirstName",
lastname: "bbLastName",
}]
//compareFunction代入
// people.sort((a, b) => {
// return (a.firstname < b.firstname) ? -1 : (a.firstname > b.firstname) ? 1 : 0
// })
//设计一个函数,不以函数为参数,单是会返回一个compareFunction函数
const sortBy = (property) => {
return (a, b) => {
var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result
}
}
console.log(people.sort(sortBy('firstname')))
/*
[ { firstname: 'aaFirstName', lastname: 'ccLastName' },
{ firstname: 'bbFirstName', lastname: 'bbLastName' },
{ firstname: 'ccLastName', lastname: 'aaFirstName' } ]
*/
console.log(people.sort(sortBy('lastname')))
/*
[ { firstname: 'ccLastName', lastname: 'aaFirstName' },
{ firstname: 'bbFirstName', lastname: 'bbLastName' },
{ firstname: 'aaFirstName', lastname: 'ccLastName' } ]
*/
闭包和高阶函数
闭包如此强大的原因就是在于他对作用域链的访问
/*
闭包就是一个内部函数,闭包有三个可以访问的作用域
1.在它自身声明之内声明的变量
2.对全局变量的访问
3.对外部函数变量的访问
*/
let global='global'
function outer(){
let outer='outer'
function inner(){
let a=5;
console.log(5)
console.log(global) //全局变量的访问
console.log(outer) //可访问外部函数的变量
}
inner()// 打印5 说明可访问自身声明之内声明的变量
}
上下文
闭包可以记住自己的上下文
let fn=(arg)=>{
let outer='outer'
let innerFn=()=>{
console.log(outer);
console.log(arg)
}
return innerFn
}
let resFn=fn(10);
resFn() //打印outer,10
1.调用fn函数时,js引擎将返回的innerFn视为一个闭包,并相应的设置了它的作用域.
2.闭包有三个作用域层级,在返回innerFn时都被设置了,返回函数的引用保存在resFn中.所以通过作用域链被调用时就记住了arg和outer
tab函数
//tab函数接受一个value并返回一个包含value的闭包函数,该函数将被执行
const tap = (value) =>
(fn) => (
typeof (fn) === 'function' && fn(value),
console.log(value)
)
//(exp1,exp2)表示执行两个参数并返回第二个表达式的结果
tap('fun')((data)=>{console.log('value is',data)})
假设在遍历一个来自服务器的数组,并且发现数据错了,我们想调试一下,看看数据究竟包含了什么
//抽象,高阶函数
const forEach = (array, fn) => {
let i;
for (i = 0; i < array.length; i++) {
fn(array[i])
}
}
const tap = (value) =>
(fn) => (
typeof (fn) === 'function' && fn(value)
)
let resData=[1,2,3,'b'];
forEach(resData,(item)=>{
tap(item)(()=>{
console.log(item)
})
})
unary函数
map实现数组加倍
var arr=[1,2,3,4];
const res=arr.map((v,i,a)=>{
return v*v
})
console.log(res)
假设我们要把字符串数组解析为整数数组.(以下做法不成功)
parseInt接受两个参数 string和radix, map的index值会作为值radix传给parseInt
['2','3','5'].map(parseInt)
// [2, NaN, NaN]
所以我们需要实现unary函数,他的任务就是接受一个给定的多参数函数,并把它转换为一个只接受一个参数的函数
const unary = (fn) =>
fn.length === 1 ? fn : (arg) => fn(arg)
var strArr=['1','2','3'];
var data=strArr.map(unary(parseInt));
console.log(data) //[1,2,3]
以下是特别的高阶函数,开发者控制函数被调用的次数
once函数
有时候只想运行一次给定的函数,比如初始化第一次支付,或者发起第一次银行请求等
const once = (fn) => {
let done = false;
//返回的函数会形成一个覆盖他的闭包作用域,所以返回的函数会访问并检查done是否为true
return function () {
return done ? undefined : (
//done为ture就阻止下一次执行
(done = true), fn.apply(this, arguments)
)
}
}
var func=once(()=>{
console.log("我只执行这一次")
})
func()
func() //undefined
memoized函数
我们知道纯函数只依赖它的参数运行,不依赖外部环境,纯函数结果完全依赖它的参数
// 计算给定数字的阶乘
var factorial = n => {
if (n === 0) {
return 1;
}
//递归
return n * factorial(n - 1);
};
console.log(factorial(3)) //3*2*1=6
问题出现:为什么不能为每一个输入存储结果呢?为了计算3的阶乘,就需要计算2的阶乘,为什么不能重用函数中的计算结果呢?
const memozied = fn => {
const lookupTable = {}; //存在返回函数的闭包上下文中
//接受一个参数并检查是否存在lookupTable中
//如果在,返回对应的值,否则使用心得输入作为key
return arg => lookupTable[arg] || (lookupTable[arg] = fn(arg));
};
let fastFactorial = memozied(n => {
if (n === 0) {
return 1;
}
return n * fastFactorial(n - 1);
});
console.log(fastFactorial(5)); //120
/*
lookupTable:{
0:1,
1:1,
2:2,
3:6,
4:24,
5:120
}
*/
console.log(fastFactorial(3)); //6 直接从上面的lookupTable中返回