彻底理解JavaScript头等函数
一、函数的理解
🔥 什么是函数?
一般来说,一个函数是可以通过外部代码 调用 的一个“子程序”(或在递归的情况下由内部函数调用)。像程序本身一样,一个函数由称为函数体的一系列语句组成 。
在 JavaScript 中,函数是头等 (first-class)对象,因为它们可以像任何其他对象一样具有属性和方法。
💚 默认情况下,JavaScript引擎或者浏览器会内置一些已经实现好的函数。
比如:alert/prompt/console.log/String/Number/Boolean等。
💗 可以理解:函数其实就是某个代码片段的封装,这段代码帮助我们完成某一个特定的功能。
🔑 函数的概念理解
1. 声明函数—— 封装 独立的功能
- 声明函数,在JavaScript中也称为 定义函数 。
- 声明函数的过程是对某些功能的封装过程;
- 在之后的开发中,我们会根据自己的需求定义很多自己的函数;
2. 调用函数—— 享受 封装 的成果
- 调用函数,也可以称为 函数调用。
- 调用函数是让已存在的函数为我们所用;
- 这些函数可以是刚刚自己封装好的某个功能函数;
- 当然, 我们也可以去使用默认提供的或者其他三方库定义好的函数;
3. 函数的作用: 使用函数可以提高编写的效率,以及代码的重用。
❤️ 函数的使用
1. 定义函数
-
语法:
由 关键字
funciton
,并跟随函数名称、函数参数列表、定义函数的JS语句
function name([param,[, param,[..., param]]]) {
[statements]
}
name
: 函数名param
: 要传递给函数的参数的名称。不同引擎中的最大参数数量不同。statements
: 包含函数体的语句
案例:
假设你要编写一些反复“发出叫声”的代码:在小狗较大时发出较大的 叫声(显示大写的WOOF WOOF),而小狗较小时发出较小的叫声(显 示小写的woof woof)。 之后需要在代码中多次使用这种发出叫声的功能。
下面来编写一个可反复使用的bark函数。
function bark(name, weight) {
if (weight > 20) {
console.log(name + ' says WOOF WOOF')
} else {
console.log(name + ' says woof woof')
}
}
⚠️ 函数定义完后,里面的代码是不会执行的, 函数必须调用才会执行。
2. 调用函数
定义的函数并不会自动执行它。定义了函数仅仅是赋予函数以名称并明确函数被调用时该做些什么。
通过 函数名() 即可。
bark("旺旺",20) // 旺旺 says woof woof
在开发中,函数内部是可以调用另外一个函数的。
既然函数中可以调用另外一个函数,那么函数是否可以调用自己呢?
- 当然是可以的;
- 但是函数调用自己必须有结束条件,否则会产生无限调用,造成报错;
- 函数调用自己还有一个专业的名词,叫做递归(Recursion)
3. 函数的参数
-
🔥 函数,把 具有独立功能的代码块 组织为一个小模块,在需要的时候调用
-
函数的参数,增加函数的 通用性,针对 相同的数据处理逻辑,能够 适应更多的数据;
- 在函数 内部,把参数当做 变量 使用,进行需要的数据处理;
- 函数调用时,按照函数定义的参数顺序,把 希望在函数内部处理的数据,通过参数传递
-
🔥 形参和实参
- 形参(参数 parameter):定义 函数时,小括号中的参数,是用来接收参数用的,在函数内部 作为变量使用
- 实参(参数 argument):调用 函数时,小括号中的参数,是用来把数据传递到 函数内部 用的。
⚠️ 形参只需定义一次,但你可能会多次调用函数,而且每次提供的实参可能不同。
bark("旺旺", 20) // 旺旺 says woof woof
bark("旺旺2", 10) // 旺旺2 says woof woof
bark("旺旺3", 18) // 旺旺3 says woof woof
bark("旺旺4", 30) // 旺旺4 says WOOF WOOF
4. 函数的返回值
回想我们之前使用的prompt函数,函数需要接受参数,并且会返回用户的输入;
所以说, 🔥 函数不仅仅可以有参数,也可以有返回值:
- 默认情况下,函数是返回
undefined
的。 - 想要返回一个其他的值,函数必须通过一个
return
语句指定返回值。- 一旦在函数中执行return操作,那么当前函数会终止;
- 如果函数使用 return语句,但是 return后面没有任何值,那么函数的返回值也是:undefined;
function sum(num1, num2) {
if (num1 < 0 || num2 < 0) return '数字要大于0'
return num1 + num2
}
console.log(sum(-1, 0)) // 数字要大于0
console.log(sum(1, 2)) // 3
5. 局部变量和外部变量
🎀 在JavaScript(ES5之前)中没有块级作用域的概念,但是函数可以定义自己的作用域。
作用域(Scope) 表示一些标识符的作用有效范围(所以也有被翻译为有效范围的);
函数的作用域表示在函数内部定义的变量,只有在函数内部可以被访问到;
| 外部变量和局部变量的概念 :
❣️ 定义在函数内部的变量,被称之为局部变量(Local Variables)。
❣️ 定义在函数外部的变量,被称之为外部变量(Outer Variables)。
-
什么是全局变量?
- 在函数之外声明的变量(在script中声明的),称之为全局变量。
- 全局变量在任何函数中都是可见的。
-
在函数中,访问变量的顺序是什么呢?
- 优先访问自己函数中的变量,没有找到时,在外部中访问。
let a = '张三' function fn() { let b = '局部b' console.log('fn中访问:', a) console.log(b) } function fn2() { console.log('fn2中访问:', a) } fn() fn2() // fn中访问: 张三 // 局部b // fn2中访问: 张三
6. 函数表达式(Function Expressions)
在JavaScript中,函数并不是一种神奇的语法结构,而是一种特殊的值。
前面定义函数的方式,我们称之为 函数的声明(Function Declaration) ;
还有另外一种写法是 函数表达式(Function Expressions) :
var foo = function () {
console.log("函数表达式 foo")
}
◼ 注意,function关键字后面没有函数名
🔥 函数表达式允许省略函数名 。
⚠️ 无论函数是如何创建的,函数都是一个值!
7. 函数声明 vs 函数表达式
函数声明 | 函数表达式 | |
---|---|---|
语法 | 在主代码流中声明为单独的语句的函数 | 在一个表达式中或另一个语法结构中创建的函数 |
JavaScript创建函数的时机 | 在函数声明被定义之前,它就可以被调用。这是内部算法的原故; 当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数; | 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用 |
💗 开发中如何选择呢?
- 当我们需要 声明一个函数 时, 首先考虑函数声明语法。
- 它能够为组织代码提供 更多的灵活性 ,因为我们可以在声明这些函数之前调用这些函数。
二、什么是头等函数?
🔥 头等函数(first-class function;第一级函数)是指在程序设计语言中,函数被当作头等公民。
即,函数 可以作为 其他函数的参数、函数的返回值、赋值给变量(函数的表达式写法)、存储在数据结构中(存储在对象、数组中)或者也支持匿名函数 。
💡 通常我们对头等公民的编程方式,称之为 函数式编程 。
💗 JavaScript就是符合函数式编程的语言,这个也是JavaScript的一大特点!
1. 赋值给变量(函数表达式的写法)
const foo1 = function () {
console.log("foo1函数被执行了")
}
foo1() // foo1函数被执行了
2. 在变量之间来回传递
let foo2 = foo1
foo2() // foo1函数被执行了
3. 另一个函数的参数
function foo(fn){
fn()
}
function bar() {
console.log("我是bar函数被调用了~")
}
foo(bar) // 我是bar函数被调用了~
foo这种函数我们也可以称之为高阶函数(Higher-order function);
高阶函数必须至少满足两个条件之一:
- 接受 一个或多个函数作为输入
- 输出一个函数
4. 函数的返回值
function sayHello(name) {
function hi() {
console.log("hi:", name)
}
return hi
}
const fn = sayHello("kobe") //柯里化
// console.log("fn:", fn)
fn() // hi: kobe
5. 存储在数据结构中
// 函数存储在对象中
const obj = {
name: '张三',
eating: function () {
console.log("eating")
}
}
obj.eating()
// eating
// 函数存储在数组中
function bar1() {
console.log("bar1被执行~")
}
function bar2() {
console.log("bar2被执行~")
}
function bar3() {
console.log("bar3被执行~")
}
// 事件总线
const arrFns = [bar1, bar2, bar3]
arrFns.forEach((fn) => {
fn()
})
// bar1被执行~
// bar2被执行~
// bar3被执行~
6.匿名函数的理解
如果在传入一个函数时,我们没有指定这个函数的名词或者通过函数表达式指定函数对应的变量,那么这个函数称之为匿名 函数。
[参考]
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Functions
- coderwhy