【函数式编程】基于JS进行函数式编程(二)高阶函数 | 函数代替数据传递 | 函数是一等公民 | 闭包 | 使用高阶函数实现抽象 | 数组的高阶函数

简介: 【函数式编程】基于JS进行函数式编程(二)高阶函数 | 函数代替数据传递 | 函数是一等公民 | 闭包 | 使用高阶函数实现抽象 | 数组的高阶函数

相关文章

【函数式编程】基于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]



相关文章
|
Web App开发 数据采集 JavaScript
动态网页爬取:Python如何获取JS加载的数据?
动态网页爬取:Python如何获取JS加载的数据?
1904 58
|
机器学习/深度学习 JavaScript 前端开发
JS进阶教程:递归函数原理与篇例解析
通过对这些代码示例的学习,我们已经了解了递归的原理以及递归在JS中的应用方法。递归虽然有着理论升华,但弄清它的核心思想并不难。举个随手可见的例子,火影鸣人做的影分身,你看到的都是同一个鸣人,但他们的行为却能在全局产生影响,这不就是递归吗?雾里看花,透过其间你或许已经深入了递归的魅力之中。
461 19
|
前端开发 JavaScript Java
JavaScript闭包深入剖析:性能剖析与优化技巧
JavaScript 闭包是强大而灵活的特性,广泛应用于数据封装、函数柯里化和事件处理等场景。闭包通过保存外部作用域的变量,实现了私有变量和方法的创建,提升了代码的安全性和可维护性。然而,闭包也可能带来性能问题,如内存泄漏和执行效率下降。为优化闭包性能,建议采取以下策略:及时解除对不再使用的闭包变量的引用,减少闭包的创建次数,使用 WeakMap 管理弱引用,以及优化闭包结构以减少作用域链查找的开销。在实际开发中,无论是 Web 前端还是 Node.js 后端,这些优化措施都能显著提升程序的性能和稳定性。
403 70
|
自然语言处理 JavaScript 前端开发
当面试官再问我JS闭包时,我能答出来的都在这里了。
闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
619 16
当面试官再问我JS闭包时,我能答出来的都在这里了。
|
存储 JavaScript 前端开发
|
JavaScript
JS实现多条件搜索函数
JS封装的多条件搜索
|
JavaScript 前端开发
JavaWeb JavaScript ③ JS的流程控制和函数
通过本文的详细介绍,您可以深入理解JavaScript的流程控制和函数的使用,进而编写出高效、可维护的代码。
339 32
|
缓存 自然语言处理 JavaScript
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
JavaScript 前端开发 Java
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
数据采集 JavaScript 前端开发
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
用array.filter()来实现数据筛选、数据清洗和链式调用,相对于for循环更加清晰,语义化强,能显著提升代码的可读性和可维护性。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~