# 函数的概念
- 什么是函数?
- 首先明确一点,和我们数学中的函数是两个概念
- 在 JS 中,函数可以理解为将一段在程序中多次出现的代码封装起来的盒子,以便在多个地方调用执行
- 换句话说:函数就是一个内部封装了部分代码的盒子,可以在多个位置被调用
- 函数的使用
- 创建函数(定义函数)
- 调用函数
# 函数的定义
- 声明式定义
```
function fn() {
}
/**
* 分析:
* function:声明函数的关键字,代表接下来这段代码是一个函数
* fn:函数名,调用函数时需要使用,函数名自定义,符合命名规范和见名知意即可(!** 匿名函数时可以不写)
* ():小括号内存放函数的参数(后续讲解)
* {}:存放函数代码,调用函数时,想执行的代码都写在内部
*/
```
- 赋值式定义
```
var fn = function () {
}
```
# 函数的调用
```
function fn1() {
}
var fn2 = function () {
}
fn1()
fn2()
```
## 声明式与赋值式的区别
- 书写区别
- 调用区别
```
// 声明式
fn1() // 可以执行
function fn1(){
// 函数代码。。。。。。
}
fn1() // 可以执行
fn2() // 不可以执行(!** 声明时编程,其实就是相当于将一个函数赋值给一个变量,会有变量的声明提升,所以在变量声明前调用时,根据变量声明提升的规则,此时变量为 undefined ,所以不能被调用)
var fn2 = function () {
// 函数代码。。。。。。。。。
}
fn2() // 可以执行
```
# 函数的参数
- 参数是什么?
- 如果没有参数,那么函数的执行功能是固定的,写好函数后内部内容将不会变
- 比如:函数内部的代码为 1 + 1,那么始终执行时始终都是 1 + 1,如果此时想要计算 1 + 2 的值,需要重新封装一个 1+2 的函数
- 参数在哪里?如何使用
- 书写函数时有一个 () 内部就是书写参数的,函数分为两种,形参---实参
- 形参和实参的区别
- 形参:在函数声明时 function 后边的()内书写,每写一个参数,就相当于在函数内部创建一个变量,其值为函数调用时传递的值,只能在函数内部使用,不能在外部使用
- 实参:顾名思义,实际的参数,也就是函数在调用时传递的参数
```
function num () {
console.log(1 + 1)
}
num() // 打印值为 1+1
function num (a, b) { // 此处 a b 为形参
console.log(a + b)
}
num(1, 1) // 此处为 实参,分别传递给 a 和 b
num(1, 2) // 此处打印值为 1 + 2
```
- 注意
- 函数的形参与实参是按照从左到右的顺序一一对应的
```
// 少传参数
function num1(a, b, c, d) {
console.log(a,b,c,d)
}
num1(1, 2, 3, 4) // 打印1,2,3,4
num1(1, 2, 4) // 打印1,2,4,undefined
num1(4) // 打印4,undefined,undefined,undefined
// 多传参数
function num2 (a) {
console.log(a)
}
num2(1, 2) // 打印 1
num2(1) // 打印 1
```
# 函数的返回值
- 返回值是什么?有什么作用
- 函数内部默认有一个 return 他的值就是函数的返回值,如果函数内部不写 return 那么函数默认在函数体内部最底部返回一个 undefined
- 如果不想返回 undefined 需要手动在函数内部写上 return 并且跟上需要返回的值
- 可以中断函数(后续通过代码演示)
```
function num (a, b) {
a+b
}
var ab = num(1,2)
console.log(ab)
```
```
function num (a, b) {
// return a + b
console.log('函数内部执行的 a + b =',a+b)
}
var ab = num(1,2)
console.log('函数外部执行的 a + b =',ab)
```
# 函数的优点
- 函数其实就是将一段需要多次调用的代码抽离出来封装到一个盒子内部,方便在多个地方调用时简单化代码
- 抽离公共代码,项目代码整体更加简洁
- 方便(复用),在需要的地方直接 函数名 + 小括号 调用即可
# 函数的预解析
- 什么是预解析
- 在代码运行前,先全部分析一遍代码,这个行为叫做预解析(预解释)
- 预解析的内容
- 声明式函数定义
- var 声明变量
```
// 正常书写代码
fn()
console.log(a)
function fn() {
console.log(100)
}
var a = 100
// 预解析后可以理解为
function fn() {
console.log(100)
}
var a
fn()
console.log(a)
a = 100
```
# 函数的预解析
- 什么是预解析
- 在代码运行前,先全部分析一遍代码,这个行为叫做预解析(预解释)
- 预解析的内容
- 声明式函数定义
- var 声明变量
```
// 正常书写代码
fn()
console.log(a)
function fn() {
console.log(100)
}
var a = 100
// 预解析后可以理解为
function fn() {
console.log(100)
}
var a
fn()
console.log(a)
a = 100
```
# 作用域
- 什么是作用域?
- 简答一句话概述:变量生效的范围
- 变量不是在所有地方都是可以使用的,而这个变量的可使用范围 就是作用域
### 全局作用域 && 局部作用域
- 全局作用域
- 最大的作用域
- 在全局作用域中定义的变量可以在任何地方使用
- 页面打开时浏览器给我们生成了一个全局作用域 `window` 这个作用域一直存在,直到浏览器页面关闭时才会销毁
- `var num = 100; var num2 = 200` 这两行代码的变量就是存储在全局作用域下面,可以在任意地方去使用
- 局部作用域
- 局部作用域就是在全局作用域内的某一个地方,相对比较小的一些作用域
- 在局部作用域中定义的变量只能在当前的这个作用域内部使用,在全局作用域或者其他的局部作用域中不可以使用
- 在 JS 中,只有函数能生成一个局部作用域,别的都不行
- 简单来说,每一个函数内部都是一个局部作用域
```
var num = 100; // 全局作用域下声明的变量,在任何地方都可以使用
function fn() {
var num2 = 200; // 在当前位置声明的变量都是 fn 函数局部作用域内的变量, 也就是说只能在当前 fn 函数内部使用
}
```
### 作用域链
- 作用域链是一个纯概念性的东西
- 当我们在某一个作用域内获取某一个变量时
- 会先在当前作用域查找,找到直接拿来用,没找到会向上层查找
- 如果上层作用域也没找到,那么会继续向上层作用域的上层作用域查找,一直到查找到全局作用域
- 这样一层一层向上查找构成一个链条(假设有,不是真的有链条),我们叫做作用域链
```
var num = 100;
function fn() {
var num2 = 200;
function fun() {
var num3 = 300;
console.log(num3); // 自己当前作用域就有(fun函数内部),拿过来直接用
console.log(num2); // 自己当前作用域没有(fun函数内部),去上一层查找,发现有(fn函数内部),拿过来用
console.log(num); // 自己当前作用域没有(fun函数内部),去上一层查找,发现没有(fn函数内部),继续向上一层作用域查找,发现有(全局作用域),直接用
console.log(a); // 自己没有,一级一级向上查找,到全局作用域还是没有,报错
}
}
```
## 变量使用规则
### 访问规则
- 变量的访问规则 也叫做 作用域的查找机制
- 作用域的查找机制只能向上查找,不能向下查找
```
function fn() {
var num = 100;
}
fn();
console.log(num); // 全局作用域没有,相当于已经查找到顶层,所以报错找不到,不会向 fn的局部作用域查找
```
### 赋值规则
- 当我们想要给某一个变量赋值的时候,就要先找到这个变量,然后再给它赋值
- 赋值规则
- 先在自己作用域内部查找,有就直接赋值
- 没有就去上一级作用域内部查找,有就直接赋值,如果没有继续向上一级作用域内部查找
- 如果一直找到全局作用域都没有,那么就把这个变量定义为全局变量,再给他赋值
```
function fn() {
num = 100;
}
fn();
/**
* fn 调用以后,要给 num 赋值
* 查找自己作用域没有后,向上层查找
* 上层就是全局作用域,发现还是没有
* 那么会把 num 定义为全局的变量,并给它赋值
* 所以 fn 函数调用后,全局就有了一个变量叫做 num 并且值是 100
*/
```
# 递归函数
- 什么是递归?
- 在编程世界中,递归就是一个自己调用自己的手段
- 递归函数:在一个函数内部,调用了自己,循环往复
```
/**
* 这就是一段简单的递归,在函数内部调用了自己,函数一执行,就调用自己一次,在调用在执行,循环往复没有尽头
*/
function fn() {
fn();
}
fn();
```
- 其实递归函数和循环很类似
- 需要有初始化,自增,执行代码,条件判断
- 如果没有就会是一个没有尽头的递归函数,我们通常叫这种为 死递归
### 写一个简单的递归
```
/**
* 求 1 至 5 的和
*
* 1 + 2 = 3
* 3 + 3 = 6
* 6 + 4 = 10
* 10 + 5 = 15
*/
// 写递归的第一步,先写结束条件(为了避免出现死递归)
function add(n) {
if (n == 5) {
return 5;
}
}
add(1);
// 第二步,写不满足结束条件时的递归操作
function add(n) {
if (n == 5) {
return 5;
} else {
return n + add(n + 1);
}
}
add(1);
```