JavaScript

函数式编程入门系列一

2019-11-20  本文已影响0人  Eastboat

快速掌握一个知识体系的秘诀就是:抓住概念并理清概念之间的关系

函数

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函数基础

  1. return语句是可选的
  2. 作用域
  3. 函数参数
  4. 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中返回


上一篇 下一篇

猜你喜欢

热点阅读