JS高级-函数一等公民

一、为什么函数是一等公民

1.1. 什么是一等公民

在编程语言中,当说到“一等公民”(First-class Citizen)这个术语时,它指的是语言中的基本抽象单位,它们被赋予了与其他基本数据类型(如数字、字符串、布尔值等)相同的地位和能力

1.2. 为什么称呼为“一等公民”

  • 在1967年的论文《Fundamental Concepts in Programming Languages》中,斯特雷奇提出了一等公民的概念,用以描述在编程语言中具有特定地位的实体

  • 其实一等这个词汇,就有一种优越性的表达含义,但在这里,其实是表示地位及重要性。简单的理解的话,就是编程语言之中一旦有内容被称为一等公民,该内容就是这门语言的核心内容

1.2.1. 实体定义
  • 我们对一等公民的定义是:具备特定地位的实体。但这定义要怎么理解呢?毕竟就目前来看还是有点抽象

    1. 可以被存储在数据结构中(如数组、列表、哈希表等)

    2. 可以参数传递

    3. 可以有返回值

    4. 可以被赋值给其他标识符

  • 具备这些特性的内容,就被称为一等公民。在JavaScript中,是函数,而在Java中,则是对象

    • 不同语言的一等公民所指的其实都不尽相同

    • 但具备的特点是一样的

1.3. 一等公民的重要性

  • 将函数视为一等公民的语言通常支持高阶函数和闭包,这为函数式编程范式提供了支持,而这也是我们本章节的主体,为了凸显其中的重要性,我们会在本章节中讲解高阶函数,进而在下一章节中深入闭包

  • 一等公民的概念也强调了语言设计的灵活性和表达能力,允许我们以更抽象和模块化的方式编写代码,而这模块化思想也是目前主流的模式,在Vue和React等框架中,都有着强烈的运用风格

1.4. 定义之代码演示

  • 可以参数传递,返回值,被赋值给其他标识符,我们要如何理解?

    • 通过代码来学习是最快的方式之一,我们来看下JS中的函数是如何实现这些的

    • 在函数中可以继续嵌套定义函数,其实是比较特殊的。其他语言,例如C或者Java都是不允许的

  • 在该案例中,我们直接把函数返回出去的这种做法,其实是非常适合用在做工具库的情况,这有点像Class中的new实例化的效果。Class后续我们会学到的

    • 无论是通过foo()获取的函数引用赋值给fn,还是通过new关键字实例化一个类获取的对象,最终都得到了一个可以调用或使用的东西

//作为另一个函数的参数,js语法允许函数内部再定义函数
function foo(){
    function bar(){
        console.log("小余的bar");
    }
  //返回值,我们直接把函数返回出去了,而非把函数结果返回出去。返回出去的是一个函数,还需调用
    return bar
}

//赋值给其他标识符
var fn = foo()
fn()

//小余的bar
  • 将函数直接作为参数传达进去的情况其实还是比较少见的

    • 通常情况下,我们会使用一个变量来接收住bar的内容,再将变量传入foo函数内。但JS中确实是可以直接传达进去的

    • 另一种情况则是传入函数本身,通常是为了实现某种特定的效果,借传入函数本身来实现

//也可以作为另外一个函数的返回值来使用
function foo (aaaa){
    console.log(aaaa);
}

foo(123)
//也可以参数传递
function bar(bbbb){
    return bbbb + "刚吃完午饭"
}

//将函数作为参数传达进去调用
foo(bar("小余"))
// 123
// 小余刚吃完午饭
1.4.1. 封装函数小案例
  • 然后我们将其进行结合,看函数如何通过这三点定义进行表达。在这个案例中,我们主要使用calc函数

    1. calc函数可以传达num1等参数(实参、形参)

    2. add、sub、mul通过return实现返回值的定义效果

    3. add、sub、mul被calc函数所使用,产生不同效果

  • 该案例,通过对calc计算函数的高度抽象,实现了模块化的效果。只需要传入add、sub、mul等函数,即可实现所对应的效果

    • 传入的三个函数,如同三个模块。需要则加载,不用则卸下

    • 从这点也可以看出,函数是非常的灵活的

//封装小案例
function calc(num1,num2,calcFn){
    console.log(calcFn(num1,num2));
}

function add(num1,num2){
    return num1 + num2
}

function sub(num1,num2){
    return num1 - num2
}

function mul(num1,num2){
    return num1 * num2
}

calc(10,10,add)
calc(10,10,sub)
calc(10,10,mul)
//20
//0
//100

二、高阶函数

2.1. 什么是高阶函数

在JavaScript中,高阶函数是指至少满足以下一个条件的函数:

  1. 接收一个或多个函数作为参数

  2. 返回一个函数

2.2. 挑选偶数

  • 从高阶函数的定义来看,我们的封装函数小案例,满足第一点条件,也是属于一种高阶函数

    • 我们通过正常使用和高阶函数使用,来分别实现原生JS的挑选偶数案例

2.2.1. 普通方式实现
  • 我们一般的实现思路是在拥有JS基础知识情况下,正常的实现方式:

    1. 创建空数组用来接收偶数

    2. 遍历需要筛选偶数的数组

    3. 对每个数字进行判断是否为偶数

    4. 为偶数则填入创建的空数组

  • 正常进行实现,其实是需要四个步骤的,所有的步骤都由我们手动的进行实现

//普通使用
var nums = [2,4,5,8,12,45,23]

var newNums = []
for(var i = 0;i<nums.length;i++){
    var num = nums[i]
    if(num % 2 === 0){
        newNums.push(num)
    }
}
console.log(newNums)
//[ 2, 4, 8, 12 ]
2.2.2. filter过滤器实现
  • filter过滤器是数组之中的方法

    • element: 当前正在处理的数组元素

    • index(可选): 当前元素的索引

    • array(可选): 调用 filter 的数组

    • 语法:array.filter(callback(element[, index[, array]])[, thisArg])

    • callback: 函数,测试数组的每个元素。返回 true 表示保留元素,返回 false 表示丢弃元素

      thisArg(可选): 执行 callback 时使用的 this 值(暂时跳过,我们后面章节会讲this)

  • 我们传入的其实是一个函数,而这个函数在内部会被调用。此时就会被称为回调函数callback,回调就是回头会调用的意思

    • 所以实际上我们filter的参数只有两个,一个是回调函数,一个是绑定this。后者我们暂时不考虑

    • 前者回调函数可接收三个参数

  • 该高阶函数是过滤的意思,这说明了,我们可以直接对遍历的参数本身进行判断,产生的判断结果为布尔值,则会决定了当前数据是否返回,而不是返回布尔值本身。这就是该高阶函数过滤本身所具备的含义

//filter,对数组进行过滤,是数组中的一个方法,传入三个参数(第一个是数组中遍历的值,第二个是数组的对应下标,第三个是我们当前数组的引用=>就是整个数组传进来),返回值是另外一个新的数组
var nums = [2,4,5,8,12,45,23]
//方式1:明显的函数特征
var evenNumbers = numbers.filter(function(number) {
  return number % 2 === 0; // 返回条件:元素是偶数
});

//方式2:箭头函数省略掉function
var newNums = nums.filter((item,index,array)=>{
    return item % 2 === 0
})
console.log(newNums);
//[ 2, 4, 8, 12 ]
  • 所以在了解我们的需求之后,只需要用到回调函数的第一个参数的情况下,我们连小括号都能省略掉

    • 这个回调函数,我们nums数组内的每一个数字都会调用一次

    • 同时可以注意到,我们没有了括号之后,不需要return。这是因为{}其实是函数的作用域,而一旦出现了作用域就会隔绝变量的提升,此时想要传递出去就需要return

    • 不使用{}的条件是,我们的逻辑非常简短,简短到就只有一行内容就可以使用

    • 并且由于filter是使用在数组的方法,回调函数至少对一个元素返回true,否则返回一个空数组

var nums = [1, 2, 3, 4, 5, 6];
//方式3:省略掉小括号
var newNums = nums.filter(n => n % 2 === 0);
console.log(newNums); // 输出: [2, 4, 6]
2.2.3. map映射实现
  • map 映射:返回一个新数组,其元素是原数组元素经过指定函数处理后的结果

  • 语法:array.map(callback(element[, index[, array]])[, thisArg])

    • callback:回调函数,接收三个参数 elementindexarray

      thisArg:可选,指定执行回调函数时的 this

  • 我们能够看到,很多的高阶函数,都是传达进一个回调函数的

    • 而回调函数有三四种写法,由复杂到简单,这在所有使用回调函数的高阶函数都是一样的

    • 在目前的情况,我们这里直接用最简单的方式,3+4的情况。但遇到其他情况则可以自由组合

    1. 使用function,也被称为匿名函数或者匿名函数表达式,回调函数是最常见的场景,没有名称标识符

    2. 使用箭头函数

    3. 参数只有一个,箭头函数省略小括号

    4. 函数体简短到只有一行,可以省略{}return

//高阶函数map映射的使用
//map:映射
var nums = [1, 2, 3, 4, 5, 6];
var newNums2 = nums.map(i => i % 2 === 0 ? '偶数是女生' : '基数是男生')
console.log(newNums2);
//[ '偶数是女生', '偶数是女生', '基数是男生', '偶数是女生', '偶数是女生', '基数是男生', '基数是男生' ]
2.2.4. forEach迭代实现
  • forEach 迭代:仅遍历数组的每个元素,无返回值,通常用于执行副作用操作

  • 语法:array.forEach(callback(element[, index[, array]])[, thisArg])

    • 参数和前面讲解的高阶函数一致

//forEech:迭代,没有返回值,通常就用来打印一些东西
var nums = [2,4,5,8,12,45,23]
nums.forEach(i => console.log(i))
// 2
// 4
// 5
// 8
// 12
// 45
// 23
2.2.5. find查找
  • find 查找:返回数组中第一个满足指定测试函数的元素,否则返回 undefined

  • 语法:array.find(callback(element[, index[, array]])[, thisArg])

    • 参数一致,不重复

//find:查找的意思,有返回值
var nums = [2,4,5,8,"小余",12,45,23]
//找到内容则返回内容
var item = nums.find(i => i === '小余')
console.log(item);
//小余

//找不到则返回JS引擎处理的默认值undefined
var item2 = nums.find(i => i === 'coderwhy')
console.log(item2);
//undefined
  • 通常用来查询一系列相关的内容也是非常好用的,这对于我们想要实现检索表格内容、个人信息等操作是常用的做法

//模拟数据
var friend = [
  {name:"小余",age:18},
  {name:"coderwhy",age:35},
]

const findFriend = friend.find(i => i.name === '小余')
console.log(findFriend);
//{ name: '小余', age: 18 }

//findIndex,找到对象在数组在对象中对应的索引值
const findFriend2 = friend.findIndex(i => i.name === '小余')
console.log(findFriend2);
//0
2.2.6. reduce累加
  • reduce 累加:累加数组元素,返回单一的结果值,通过对每个元素应用一个合并函数实现

  • 语法:array.reduce(callback(accumulator, element[, index[, array]])[, initialValue])

    • callback:回调函数,接收四个参数 accumulator(累加器)、element(当前元素)、index(当前索引)和 array(原数组)

      initialValue:可选,累加器的初始值,如果不提供,则默认为数组的第一个元素

//普通实现方式
var nums = [2,4,5,8,12,45,23]
var total = 0
for(var i = 0;i<nums.length;i++){
    total += nums[i]
}
console.log(total);
//99
//reduce:对我们原来的数组进行一些累加或者统计的操作

//高阶函数reduce的使用
//reduce接收参数,第一个参数:上一个函数的返回值(例如我们数组中有7个数字,那就调用7次函数,第一个参数每次都调用上一次的内容)
//那第一次调用的时候没有上一个函数怎么办?我们可以在回调函数后面定义初始化的值,例如0
//prevValue(上一次的值):0 , item:2  prevValue是previousValue的简写
//prevValue(上一次的值):2 , item:4
//不停的将上一次的值跟下一次的值做一个处理,直到全部处理结束带着结果进行返回
var num = nums.reduce((preValue, item) => preValue + item, 0)
console.log(num);
//99

2.3. 函数(Function)与方法(Method)的区别

  • 一般来说,其实是指同一个东西

  • 函数(Function):独立的Funtion,称之为一个函数

  • 方法(Method):当我们的一个函数属于某一个对象时,我们称这个函数是这个对象的方法

  • 方法更像是定义在一些特殊地方的函数,函数包含得更大

    • 这就是为什么我们的高阶函数被称为方法的原因,因为都隶属于某个对象,所以我们才能直接通过.xxx进行调用

    • 真想说的话,方法也可以叫做函数,但函数通常不会叫做方法

var obj = {
    
    foo:function(){
        
    }
}
//这个foo就是一个属于obj对象的方法
//方法调用的时候
obj.foo()

//函数调用,不属于某个对象
foo()

2.4. 高阶函数如何学习?

  • 高阶函数的真正使用地方,应该是实际场景之中,单纯学习API的话,是不能够真正掌握的

    • 我们目前只学习了语法,但具体能够运用在什么地方,是一个较为模糊的概念

    • 而运用JS高阶函数最多的框架是React,刚好最近它已经要出19版本了,React是很优秀的前端框架,并且对于JS的掌握提升帮助非常之大,JS和React是相互印证的两面

    1. 学习JS为学习React打下坚实的根基

    2. 学习React为学习JS提供了绽放光芒的平台

  • 所以,目前来看,在这里并不适合讲解这些高阶函数的运用场景。因为大家可以发现,过滤器能做的事情,映射也可以做。而映射能做的事情,迭代好像也可以做

    • 那在具体的场景中,我们应该要用哪个呢?

    • 我觉得这需要到具体的项目场景中去学习才不会虚无,我们目前学到这里就OK了,等到做项目的时候,我们才能体会其中带来的好处

    • 等到ES6-ES14部分,我们也会学习更多的高级函数或者语法

2.4.1. 高阶函数的好处
  • 在上面的五种高阶函数案例中,大家可以看到,我都是使用最精简的写法。这种精简的写法叫做箭头函数,在现在,我们只是为了凸起高阶函数的简洁而这么做的,但具体的学习箭头函数,我们会放在第接下来的第九篇文章当中,如果好奇的话,不妨先从MDN中进行了解

    • 这样可以对比得更加明显,高阶函数的代码通常一行能解决正常方式的许多行

    • 这是因为高阶函数的特性,能够接收函数或者返回函数。让高阶函数可以内置实现我们原本正常步骤中的其中几步,代码量的减少,其实是因为有一部分通用的步骤被高阶函数实现并且隐藏起来了,这些行为大多数都是从最小特权原则中引申出来的,也叫做最小授权或者最小暴露原则,在我们这样方法的API设计中是经常使用的软件设计模式

    • 而将其隐藏起来,可以避免同名标识符之间的冲突,会更省心一些。毕竟这些内容,我们往往只需要通过它们处理好的数据

    • 所以在一开始学习的时候,可能会稍微不好理解一点。但熟练之后,会为我们节约下不少时间

    • 像上面的各种案例中,基本上都内置了数据的遍历这个操作,让我们的代码可以不用进行for循环,减少了最主要的代码工作量。而其他的高阶函数,所内置的虽不一定的数据遍历操作

  • 但基本上都是某一部分通用的操作,目的是为了减少我们代码量和代码重复的痛点,而这也是高阶函数所想表达的意思

2.4.2. 记忆高阶函数
  • 高阶函数有非常多,我们在学习的时候最好进行总结归纳

    • 死记硬背是最不可取的方式

    • 不管怎么样的函数,所进行的事情基本上无法脱离对数据的处理

    • 而对数据的处理,基本上都来源于增删改查这四个字,依次套用就行了。对一段主体是处理开头还是结尾亦或者中间?

      现学套用:内容增加在开头、增加在中间、增加在结尾、增加在具体索引位置

      其他的删改查都是一样的逻辑,单对数据处理位置的四种方式乘增删改查的四种情况。就有16种方法了,在稍微拓展一下其他有关于this的,然后对原数据有能改动、有返回新数据的两种情况,再乘2,差不多就四十多种方法了。所以通过简单的推导就能够去掌握大多数常用的部分

    • 而且高阶函数的回调函数传递进参数,基本上都差不多

  • 既然是对数据的处理,那还分为两种大类:

    1. 使用函数方法会改变原数据

    2. ...不会改变原数据

  • 大多数情况下,我们会使用第一种方式,在React中,我们会学到类似数据的不可变性的思想,这有利于我们理解这些做法

2.5. 语法的学习

  • 在看各种文档的时候,我们能够看到类似如下的语法示例:

    array.find(callback(element[, index[, array]])[, thisArg])

  • 好像很少有人说这种需要怎么去看,怎么去理解,而这些在我们进行自学的道路上,是不可避免需要接触到的内容

    • 在这里核心注意点有两个,方括号[]以及逗号,

2.5.1. 括号
  • 括号分为单层方括号 []和 嵌套方括号 [[]]

    单层方括号 []

    可选参数:当我们看到用方括号括起来的参数,如 [element][index],这表示这些参数是可选的。可以在调用函数时包括这些参数,也可以省略它们。方括号内的参数不是必须提供的,但提供这些参数可能会改变函数的行为或返回的结果

    嵌套方括号 [[]]

    嵌套可选参数:嵌套的方括号,如 [[index]][[array]],通常用来表示参数的依赖性。当外层的参数被包括时,内层的参数才有可能被包括。这种结构在定义中还是比较少见的,通常用来进一步强调参数之间的依赖关系

    • 我们需要如何区分他们呢?

  • element 是必需的,因为它没有被方括号括起来

    • 而不管是单层方括号还是嵌套方括号,都代表了可选的意思

2.5.2. 逗号
  • 在文档中的这个语法,我们能够看到,逗号在方括号的里面...

    • 这又是什么意思?为什么要在方括号的里面呢?

    • 逗号则是必须要满足前面的内容,才能够轮到逗号之后的内容。是逗号前面是必须满足的前置条件

2.5.3. 语法拆解示例
  • 在理解了逗号和方括号的含义之后,我们再来看下这个语法到底是什么意思

    • array.find(callback(element[, index[, array]])[, thisArg])

    • 首先进行拆分

    • 我们首先将其分为两个参数,第一个回调函数因为由小括号(),所以是必须的,不能够空参数。而第二个参数是方括号[],表示可选,并且在方括号内部参数的前面有逗号,说明了必须有第一个回调函数参数才能有第二个this剩余参数

    • 为什么我知道得这么清楚?这是因为callback本身就是回调函数的意思,而thisArg则是this+Argument的含义,我们在之后会学到什么是argument的

    • 回调函数内部则就是一样的套路了,回调函数里面必须有元素element才能有index索引,有索引才能有数组array本身

    1. callback(element[占位符])

    2. [,thisArg]

  • 现在,让我们打开MDN,来看下这样的理解是否正确!

    • 是正确的,但现在MDN没有采用这种精简的语法了,而是对参数进行解释,并分为必填和可选两个部分,以缩进来表示更加细分的参数,这会降低一些理解的难度

    • 而在vscode的提示中,则是转为用TS来进行提示,本质上是差不多的,但TS所能表达的含义会更加丰富。等学完JS再去学TS,就对于各种现在的代码提示能够有很好的理解力了

图片

 
 
图6-1 API语法参数分类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_35430208

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值