四、函数
1.函数的概念
-
什么是函数?
- 首先明确一点,和我们数学中的函数是两个概念
- 在 JS 中,函数可以理解为将一段在程序中多次出现的代码封装起来的盒子,以便在多个地方调用执行
- 换句话说:函数就是一个内部封装了部分代码的盒子,可以在多个位置被调用
-
函数的使用
- 创建函数(定义函数)
- 调用函数
2. 函数的定义
- 声明式定义
function fn() {
}
/**
* 分析:
* function:声明函数的关键字,代表接下来这段代码是一个函数
* fn:函数名,调用函数时需要使用,函数名自定义,符合命名规范和见名知意即可(!** 匿名函数时可以不写)
* ():小括号内存放函数的参数(后续讲解)
* {}:存放函数代码,调用函数时,想执行的代码都写在内部
*/
复制代码
复制代码
- 赋值式定义
var fn = function () {
}
复制代码
复制代码
3.函数的调用
调用语法: 函数名() / 变量名()
function fn1() {
}
var fn2 = function () {
}
fn1()
fn2()
复制代码
复制代码
4. 声明式与赋值式的区别
- 书写区别
- 调用区别
//1. 声明式
fn1() // 可以执行
function fn1(){
// 函数代码。。。。。。
}
fn1() // 可以执行
//2. 赋值式
fn2() // 不可以执行
//赋值式定义, 其实就是声明一个变量, 然后给他赋值为一个函数,
//再 JS 中, 如果再定义变量之前 使用变量的话, 那么变量的值为 undefined (变量提升 面试可能会问)
//函数的执行结果, 一般默认都是 undefined,除非手动更改)
var fn2 = function () {
// 函数代码。。。。。。。。。
}
fn2() // 可以执行
复制代码
复制代码
5. 函数的参数
-
参数是什么?
- 如果没有参数,那么函数的执行功能是固定的,写好函数后内部内容将不会变
- 比如:函数内部的代码为 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
复制代码
复制代码
-
函数参数的默认值
- 函数再创建形参的时候, 默认给一个值, 将来在调用函数的时候,
- 如果没有传递那么这个形参的值也不会是 undefined 而是给的默认值
- 如果传递了对应的值, 那么形参的值是实参传递进来的值, 否则按照默认值来运行
function fn(a = 100, b = '我是形参b', c) {
console.log(a, b, c)
}
fn()
复制代码
复制代码
6. 函数的返回值
-
返回值是什么?有什么作用
- 函数内部默认有一个 return 他的值就是函数的返回值,如果函数内部不写 return 那么函数默认在函数体内部最底部返回一个 undefined
- 如果不想返回 undefined 需要手动在函数内部写上 return 并且跟上需要返回的值
- 可以中断函数(后续通过代码演示)
function num (a, b) {
a+b
}
var ab = num(1,2)
console.log(ab)//打印undefined
复制代码
复制代码
function num (a, b) {
// return a + b
console.log('函数内部执行的 a + b =',a+b)
}
var ab = num(1,2)
console.log('函数外部执行的 a + b =',ab)
//打印:
//函数内部执行的 a + b = 3
//函数外部执行的 a + b = undefined
复制代码
复制代码
7. 函数的优点
-
函数其实就是将一段需要多次调用的代码抽离出来封装到一个盒子内部,方便在多个地方调用时简单化代码
- 抽离公共代码,项目代码整体更加简洁
- 方便(复用),在需要的地方直接 函数名 + 小括号 调用即可
8. 函数的预解析
-
预解析的一个表现就是
- 声明式函数再定义前可以被调用
-
什么是预解析(一道面试题)
- 在代码运行前,先全部分析一遍代码,这个行为叫做预解析(预解释)
- 解析阶段, 做了一件事, 就是 函数提升, 就是将 声明式 函数的定义, 提升到当前 作用域的最顶端
- 作用域的最顶端: 暂时理解为 当前页面的最开始的位置
-
预解析的内容
- 声明式函数定义
- 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
复制代码
复制代码
8. 函数的作用域
-
什么是 作用域? (这是一道面试题)
- 就是变量可以起作用的范围
-
作用域分为两个
-
全局作用域(直接在 script 内书写的代码)
-
在此作用域创建的变量, 我们叫做全局变量, 在当前 script 标签内的哪里都能使用
-
在 JS 中, 全局作用域中有一个 提前给我们准备好的 对象(一种数据格式, 后续会详细的讲解)
-
这个 对象叫做 window,我们创建的全局变量, 会被自动添加到 window 对象中
-
-
局部作用域(在 JS 中, 只有函数能够创建局部作用域)
- 在此作用域创建的变量, 只能在当前作用域使用, 超出这个作用域(也就是在函数外边)去使用, 就会找不到变量
-
function fn() {
var sum = '我是在函数 fn 内部创建的变量, 我是局部变量, 所以我只能在当前函数内使用'
var abc123 = '我是在 fn 函数内部创建的局部变量'
console.log(sum)
}
fn()
// console.log(sum) // 这里因为超出了这个变量的使用区间, 所以会 报错
var abc = '我是一个全局变量 abc' // 创建一个全局变量abc
console.log(window)
复制代码
复制代码
9. 作用域链 (这是一个纯概念性的东西, 面试也可能会问)
- 作用域链 就是在访问一个变量的时候, 如果当前作用域内没有
- 会去自己的父级作用域, 也就是上一层作用域内查找, 如果找到就直接使用, 如果没有找到继续向上层查找
- 直到查找到 最顶层的全局作用域, 如果找到了直接使用, 如果没找到 报错提示变量不存在(未定义)
- 我们将这个一层一层向上查找的规律, 叫做作用域链
function fn1() {
var num = 999
function fn2() {
num = 100
//在当前作用域内查找 num 发现没有, 会去自己的父级 作用域内查找, 也就是 fn1 函数内部
//在 fn1 函数内部发现一个变量 num 然后值为 999 //我们会对这个变量做一个重新赋值的操作
//也就是将他的值 重新修改为 100
}
fn2()
console.log(num) // 100
}
fn1()
console.log(num) // 未定义
复制代码
复制代码
var num = 666
function fn1() {
function fn2() {
num = 100
/**
* 在当前作用域内查找 num 发现没有, 会去自己的父级作用域内查找, 也就是 fn1 函数内部
*
* 在 fn1 函数内部, 发现没有 这个变量, 继续去自己的父级作用域查找, 也就是 全局作用域
*
* 在全局作用域发现了一个变量 叫做 num, 他的值是 666, 我们将这个变量重新赋值为 100
*/
}
fn2()
console.log(num) //100
}
fn1()
console.log(num) // 100
复制代码
复制代码
10. 递归函数
- 本质上还是一个函数
- 当一个函数在函数的内部, 调用了 自身, 那么就算是一个 所谓的 递归函数(只不过有点小缺陷)
//计算阶乘
function fn(n) {
if (n === 1) {
// 说明此时想要计算 1 的阶乘, 那么我直接将 1 的阶乘的结果 return 出去
return 1
}
return n * fn(n - 1)
}
var sum = fn(4)
console.log(sum) // 24
var sum1 = fn(10)
console.log(sum1)