【前端精进之路】JS篇:第13期 函数式编程_函数式编程的应用js

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

  • 接受一个或多个函数作为输入
  • 输出一个函数

JavaScript 语言中内置了一些高阶函数,比如 Array.prototype.map,Array.prototype.filter 和 Array.prototype.reduce,它们接受一个函数作为参数,并应用这个函数到列表的每一个元素。

Array.prototype.map

现在有一个数组 [1, 2, 3, 4],我们想要生成一个新数组,其每个元素皆是之前数组的两倍.

const arr1 = [1, 2, 3, 4];
const arr2 = arr1.map(item => item \* 2);

console.log( arr2 );
// [2, 4, 6, 8]
console.log( arr1 );
// [1, 2, 3, 4]

Array.prototype.filter

现在有一个数组 [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4],我们想要生成一个新数组,这个数组要求没有重复的内容,即为去重。

const arr1 = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
const arr2 = arr1.filter( (element, index, self) => {
    return self.indexOf( element ) === index;
});

console.log( arr2 );
// [1, 2, 3, 5, 4]
console.log( arr1 );
// [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]

Array.prototype.reduce

现在有一个数组 [0, 1, 2, 3, 4],需要计算数组元素的和.

// 使用高阶函数
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue,0);
console.log(sum)//25

三、函数柯里化

柯里化又称部分求值,柯里化函数会接收一些参数,然后不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值。

demo
// 普通函数
function add(x,y){
    return x + y;
}
add(1,2); // 3
// 函数柯里化
var add = function(x) {
  return function(y) {
    return x + y;
  };
};
var increment = add(1);
increment(2);// 3

作用:

主要有 3 个作用: 参数复用提前返回延迟执行

参数复用:我们可以传入部分参数,然后拿着返回的函数再加入新的参数,这样前面传入的参数就做到了参数复用。

提前返回 和 延迟执行 也很好理解,因为每次调用函数时,它只接受一部分参数,并返回一个函数(提前返回),直到(延迟执行)传递所有参数为止。

实现
function currying(fn, ...args) {
  const length = fn.length;
  let allArgs = [...args];
  const res = (...newArgs) => {
    allArgs = [...allArgs, ...newArgs];
    if (allArgs.length === length) {
      return fn(...allArgs);
    } else {
      return res;
    }
  };
  return res;
}

// 用法如下:
// const add = (a, b, c) => a + b + c;
// const a = currying(add, 1);
// console.log(a(2,3))

四、函数组合 (Composition)

假设有一个 compose 函数,它可以接受多个函数作为参数,然后返回一个新的函数。当我们为这个新函数传递参数时,该参数就会「流」过其中的函数,最后返回结果。

// 用法如下:
function fn1(x) {
  return x + 1;
}
function fn2(x) {
  return x + 2;
}
function fn3(x) {
  return x + 3;
}
function fn4(x) {
  return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11

我们compose的作用就是将嵌套执行的方法作为参数平铺,嵌套执行的时候,里面的方法就是右边的方法最开始执行,然后往左边返回。

function compose(...fns){
  return (arg) => 
    fns.reduceRight((prev,fn) => {
      return fn(prev)
    },arg)
}

解释

首先我们要传入一些函数,即...fns。并且通过compose函数我们要return一个函数。

return的这个函数传入的参数就是我们要计算的初始值。

注意:如果箭头函数的返回值只有一个式子,那么可以不使用{}和return。

在函数体的操作中,我们需要从右往左遍历所有的函数,prev是上一次返回的值,fn是当前处理的函数,并且要把arg当做初始值传入。

在函数体中,我们要返回当前fn函数的执行结果作为prev的值。

pipe函数

compose执行是从右到左的。而管道函数,执行顺序是从左到右执行的

function compose(...fns){
  return (arg) => 
    fns.reduce((prev,fn) => {
      return fn(prev)
    },arg)
}

五、偏函数

柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。

局部应用则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。

  • 柯里化:f(a,b,c)=f(a)(b)©
  • 偏函数:f(a,b,c)=f(a,b)©
/\*
 用bind函数实现偏函数,bind的另一个用法使一个函数拥有预设的初始参数,将这些参数写在bind的第一个参数后,
 当绑定函数调用时,会插入目标函数的初始位置,调用函数传入的参数会跟在bind传入的后面
 \*/
let add = (x, y) => x + y
let rst = add.bind(null, 1)
rst(2) //3

六、防抖与节流

防抖

你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行。

function debounce(fn,delay){
      //1.定义一个定时器,保存上一次的定时器
      let timer = null
      
      //2.真正执行的函数
      return function(...args){
        //取消上一次的定时器
        if(timer){
          clearTimeout(timer)
        }
        //延迟执行
        timer = setTimeout(() => {
          //执行外部传入的函数
          fn.apply(this,args)
        }, delay);
      }
    }

  • this绑定:真正执行的函数是要绑定我们返回的函数,而箭头函数没有this,它的this指向上层作用域,正好是我们返回的函数
  • 参数传递:因为真正执行的函数是我们返回的函数,所以参数是放在返回的那个函数中,直接解构

验证

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <input type="text">


  <script>
 function debounce(fn,delay){
 //1.定义一个定时器,保存上一次的定时器
 let timer = null
 
 //2.真正执行的函数
 return function(...args){
 //取消上一次的定时器
 if(timer){
 clearTimeout(timer)
 }
 //延迟执行
 timer = setTimeout(() => {
 //执行外部传入的函数
 fn.apply(this,args)
 }, delay);
 }
 }


 const inputEl = document.querySelector('input');
 let counter = 0

 const inputChange = function(){
 console.log(`发送了第${++counter}次网络请求`)
 }

 inputEl.oninput = debounce(inputChange, 1000)
 </script>
</body>
</html>

节流

当事件持续触发时,节流操作可以稀释事件处理函数执行频率,假设在1sonmousemove事件触发了100次,通过节流就可以使得onmousemove事件的事件处理函数每100ms触发一次,也就是在1sonmousemove事件的事件处理函数只执行10次。

方法1: 定时器方式实现

缺点:第一次触发事件不会立即执行fn,需要等delay间隔过后才会执行

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

方法2:时间戳方式实现

缺点:最后一次触发回调与前一次的触发回调的时间差小于delay,则最后一次触发事件不会执行回调

const throttle2 = (fn, wait = 50) => {
    // 上一次执行 fn 的时间
    let previous = 0
    // 将 throttle 处理结果当作函数返回
    return function(...args) {
        // 获取当前时间,转换成时间戳,单位毫秒
        let now = +new Date()
        // 将当前时间和上一次执行函数的时间进行对比
        // 大于等待时间就把 previous 设置为当前时间并执行函数 fn
        if (now - previous > wait) {
            previous = now
            fn.apply(this, args)
        }
    }
}

解释这里的 +new Date()

js在某个数据类型前使用‘+’,这个操作目的是为了将该数据类型转换为Number类型,如果转换失败,则返回NaN;

+'2'+1 // 3
+[1]   // NaN

+new Date() 会调用Date.prototype 上面的 valueOf方法

下面的例子返回效果等同:

+new Date();

new Date().getTime();

new Date().valueOf();

new Date()\*1

验证

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/ae9b22c463572f9277bce616e7e146fd.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

tml lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-y6ODARyZ-1713356466873)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值