Javascript进阶——函数式编程(2)

作者 : 开心源码 本文共5345个字,预计阅读时间需要14分钟 发布时间: 2022-05-13 共208人阅读

函数式编程中,常用的函数及使用:

组合函数 (Compose,Pipe)

概念

将需要嵌套执行的函数平铺,嵌套执行是指将一个函数作为参数传递给另外一个函数,主要有以下特点:

  • 第一函数接受参数, 其余函数接受的上一个函数的返回值
  • 第一个函数可以接受多个参数,其余函数只接受一个参数(即上一个函数的返回值)
作用:
  • 实现函数式编程中的pointfree,使我们专注于转换而不是数据,不使用需要的值,只合成运算
  • 简化代码
  • 设计和笼统功能到具体函数里,以便复用;还实现中间件功能,如webpack的loader,redux是通过Compose函数实现的
Compose函数实现

自右向左按顺序执行

let compose=function(){    let args=[].slice.call(arguments);    return function(s){        args.reduceRight(function(res, cb){           return cb(res);        }, x)    }}// 调用compose(fn1, fn2, ..)(args); // fn1,和fn2都是函数,args是第一个函数接受的参数,最先执行最后边的函数,依次往左执行// ES6的写法const compose= function(...funcs) {  if (funcs.length === 0) {    return arg => arg;  }  return funcs.reduce((a, b) => (...args) => a(b(...args)));}
Pipe函数实现

Pipe函数和Compose函数几乎一致,唯一不同的是,Pipe函数的执行顺序是从左到右

const pipe= function(...funcs) {  if(funcs.length==0)  return args=>args;  return funcs.reduce((a,b)=> (...args)=> b(a(...args)));}

高阶函数

概念

高阶函数时对其余函数进行操作的函数,可以将它们作为参数或者返回值。
简单来说就是

  • 一个函数可以接收另一个函数作为参数
  • 函数可以作为返回值被返回
常用的原生高阶函数详情
  1. map
    在JavaScript里,map()方法定义在Array中,它返回一个新的数组,数组中的元素为原始数组调用函数解决后的值。
    语法:array.map(function(currentValue,index,arr), thisValue)
  • 第一个参数function(currentValue,index,arr)是必需的,表示要对每个元素操作的函数
    – currentValue, 必需。当前元素的值
    – index, 可选。当前元素的索引值
    – arr, 可选。表示原数组
  • thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 “this” 的值

举个栗子:

//创立一个新数组,其元素是原数组值的两倍let array = [1, 2, 3, 4];let newArray = array.map((item) => {    return item * 2;})console.log(newArray)  // [2, 4, 6, 8]

注意:
map() 不会对空数组进行检测
map() 不会改变原始数组。

  1. reduce
    reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
    语法:array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  • 第一个参数function(total, currentValue, currentIndex, arr)是必需的,用于执行每个数组元素的函数
    -total 必须。初始值, 或者者计算结束后的返回值
    -currentValue,必须。当前元素
    -currentIndex,必须。当前索引
    -arr, 可选,原数组
  • initialValue, 可选。传递给函数的初始值

举个栗子:返回数组元素的累加之和

    let arr = [1, 2, 3, 4];    const sum = arr.reduce((total, current) => {      return total + current;    }, 0);    console.log(sum); // 10

注意: reduce() 对于空数组是不会执行回调函数的

  1. filter
    filter() 方法创立一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
    语法:array.filter(function(currentValue,index,arr), thisValue)
  • 第一个参数function(currentValue,index,arr)是必需的,数组中的每个元素都会执行这个函数
    -currentValue,必须。当前元素
    -currentIndex,必须。当前索引
    -arr, 可选,原数组
  • thisValue, 可选。对象作为该执行回调时使用,传递给函数,用作 “this” 的值。假如省略了 thisValue ,”this” 的值为 “undefined”

举个栗子:返回对象数组中price高于10的元素到一个新数组

    const products = [      { name: "p1", price: 12 },      { name: "p2", price: 10 },      { name: "p3", price: 2 },      { name: "p4", price: 3 },    ];    const newArr = products.filter((f) => f.price > 10);    console.log(newArr); // [{ name: "p1", price: 12 }]

注意:
filter() 不会对空数组进行检测
filter() 不会改变原始数组。

  1. flat
    flat()用于将嵌套的数组拉平,即数组扁平化,将多维数组变成一维数组。
    语法:array.flat(level);

参数,level可选,表示要拉平数组的层数,默认为1

举个栗子:

    const arr = [1, 2, 3, [4, 5, [7, 8]]];    const flatArr = arr.flat(3); // 假如不清楚多维数组有几层,可使用Infinity关键字作为参数    console.log(flatArr); // [1,2,3,4,5,7,8]

注意:
flat() 不会改变原始数组。
flat方法尚未在所有浏览器和node版本兼容

其余常用函数
  1. 缓存函数 memorizion
    缓存函数是指将上次的计算结果缓存起来,下次调用函数时,假如遇到相同的参数,就直接返回缓存中的结果,不必再执行一次运算
    原理:把参数和对应的结果保存到一个对象中,调用时,判断参数对应的结果能否存在,存在就直接返回对象中的数据
    实现:
let memoize = function (func, hasher) {  const memoize = function (...args) {    const key = [].slice.call(args);    var cache = memoize.cache;    // 参考underscore里memoize写法,hasher也是一个函数,用于计算key的值,没有就直接使用参数作为key    var address = " " + (hasher ? hasher.apply(this, args) : key);    if (!cache[address]) {      cache[address] = func.apply(this, args);    }    return cache[address];  };  memoize.cache = {};  return memoize;};// 使用例子let add = (a, b) => a + b;let adder = memorize(add);adder(1, 2);adder(1, 2); // 第二次使用相同参数调用时,将直接返回缓存中的结果

对于重复计算量大的递归调用,使用缓存函数可以加快速度

  1. 柯里化函数 curry
    柯里化官方解释是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
    简单来说就是:只传递给函数一部分参数来调用它,让它返回一个函数去解决剩下的参数。了解起来比较笼统,我们来看一个代码示例:
// 普通函数let add = (a, b, c) => {  return a + b + c;};// 柯里化函数let curryAdd = (a) => {  return (b) => {    return (c) => {      return a + b + c;    };  };};const ret = add(1, 2, 3);console.log(ret); // 6const ret2 = curryAdd(1)(2)(3);console.log(ret2); // 6

以上只是一个非常简单的例子用以说明,并不通用,通用的柯里化函数应该达到如下效果:
curryAdd(1)(2,3)
curryAdd(1,2,3)
curryAdd(1,2)(3)
curryAdd(1)(2)(3)
四种不同调用都得到相同结果

结合上面的例子,我们可以看出,柯里化其实是利用闭包将每次传进来的参数储存起来,而后在最里层的函数里面解决一律逻辑,做一个简单封装,就得到如下通用实现:

// 通过递归来收集参数,收集完成就执行原始函数本身const _curry = (fn, args) => {  var len = fn.length;  var args1 = args || [];  return (...args) => {    var _args = [].slice.call(args);    [].push.apply(_args, args1);    // 假如参数个数小于最初的fn.length,则递归调用,继续收集参数    if (_args.length < len) {      return _curry.call(this, fn, _args);    }    // 参数收集完毕,则执行fn    return fn.apply(this, _args);  };};const curryAdd = _curry(add);console.log(curryAdd(1, 2, 3)); // 6console.log(curryAdd(1, 2)(3)); // 6console.log(curryAdd(1)(2, 3)); // 6console.log(curryAdd(1)(2)(3)); // 6

上面add的例子太过简单(实际上只是拿来演示,方便了解柯里化原理),比较难以体会它实际的好处,可以再看一个小栗子:

// 比方前面学习纯函数时举的正则验证的代码,我们也可以使用柯里化方法来简化,达到复用参数的目的// 这里调换了一下参数顺序,将可以复用的参数放在前面const regValidate = function (reg, value) {  const v = value.toString();  return reg.test(v);};console.log(regValidate(3.22, /^(\+|-)?\d+(\.\d+)?$/ig)); // 验证数字const _validate=_curry(regValidate); // 柯里化const isNumber=_validate(/^(\+|-)?\d+(\.\d+)?$/ig); const isString=_validate(/\s/ig);isNumber(3.22); // 使用,不用每次传入正则表达式,代码变得更简洁isString("sss");

实际工作中,我们可以使用少量库封装好的柯里化函数,无需自己去实现,比方lodash库,已经包含了_.curry()函数

函数的防抖和节流

开发过程中,resize, scroll, mousemove, mousehover等事件,会被频繁的触发,不做限制的话,有可能一秒执行几十次,几百次,假如这些事件内部执行了其余复杂函数,尤其是DOM操作,会造成计算机资源的白费,降低程序运行速度,甚至引起浏览器卡死,崩溃。假如包含了ajax请求,短时间大量重复请求,则会引起数据混乱,造成网络阻塞,添加服务器端的压力。

防抖

在一个动作发生了肯定时间后,才去执行特定事件。假如客户一直触发某个事件,且每次触发的间隔小于delay
实现:

let deBounce = (fn, delay) => {    let timer = null;    return function (...args) {        if (timer) {            clearTimeout(timer);        }        timer = setTimeout(()=> {            fn(...args); // fn即为事件执行的函数        }, delay)    }}
节流

当持续触发事件时,保证肯定时间段内只调用一次事件解决函数
实现:

let throttle = (fn, delay) => {    let flag = true;    return function (...args) {        if (!flag) return;        flag = false;        setTimeout(() => {            fn(...args);            flag = true;        }, delay)    }}

总结

总的来说,函数式编程是一种编程思维方式,需要在不断的实践里融会贯通,像这里的柯里化,也许我们在工作中很少用到,通过这些简单的例子也很体会它的作用,但重要的是学习一种思想,在遇到复杂问题时,可以给我们多一种处理思路,也多提供一种方式帮助我们去优化自己的程序。

前台路漫漫,每天学一点~~

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Javascript进阶——函数式编程(2)

发表回复