函数的概念
- 什么是函数?
- 首先明确一点,和我们数学中的函数是两个概念
- 在 JS 中,函数可以理解为将一段在程序中多次出现的代码封装起来的盒子,以便在多个地方调用执行
- 通俗来讲:函数相当于一个盒子,里面装的是在页面中重复出现的多段代码
-
函数的使用
- 创建函数(定义函数)
- 调用函数
函数的定义
1. 声明式定义
function fn() {
函数体
}
/* function: 是关键字 表明后续的是一段函数
* fn: 是函数的名字,将来调用的时候会用到,函数名是自定义的
* (): 内部填写参数
*/ {}: 内部填写函数调用时要执行的代码段
2. 赋值式定义
var fn = function(){
函数体
}
函数的调用
function fn1() {
}
var fn2 = function() {
}
fn1();
fn2(); //这里注意,调用的时候必须有()
(补充) 声明式与赋值式的区别:
- 写法不同
- 调用上不同
//声明式
fn1() //可以在函数定义前去调用
function fn1() {
}
fn() //这里调用也是会正常执行
//赋值式
fn2() //这个先调用,后赋值,是不能正常执行的,相当于声明一个变量,然后赋值为一个函数
//如果在定义变量之前,使用变量的话,那么变量的值为undefined (变量提升)
var fn2 = function() {
}
fn2() //在这里可以正常执行
函数的参数
-
参数是什么?
- 如果没有参数,那么函数的执行功能是固定的,写好函数后内部内容将不会变
- 比如:函数内部的代码为 1 + 1,那么始终执行时始终都是 1 + 1,如果此时想要计算 1 + 2 的值,需要重新封装一个 1+2 的函数
-
形参和实参的区别
形参:形参是function后面小括号内的参数,用来接收实参传来的值实参:实参是调用函数括号里的值,起决定作用
function fn(num1,num2){
console.log(num1 + num2)
}
fn(1,2) //这里的实参为1,2 那么就把1传递给了num1,把2传递给了num2,输出的就是1+2
fn(2,3) //这里的实参为2,3 那么就把2传递给了num1,把3传递给了num2,输出的就是2+3
//以后如果想执行相同的操作只需要在调用函数时传递不同的实参就可以
形参和实参的注意事项:
- 形参和实参的数量要一一对应
- 如果实参多于形参,那么一对一接受以后,剩下的没有接收,调用不了
- 如果形参多于实参,那么一对一接受以后,剩下的显示undefined
- 形参可以设置默认值,如果实参没有设置的时候,显示默认值
函数的返回值
-
返回值是什么?有什么作用
函数如果有return,则返回的是return后面的值,如果没有return, 则返回undefined如果不想返回 undefined 需要手动在函数内部写上 return 并且跟上需要返回的值return有中断函数执行的能力,所以放在函数的最后return只能返回一个值,如果有多个值,以最后一个为准
function fn(a,b){
return a + b
}
var sum = fn(2,3) //这里设置了一个变量来接收返回值
console.log(sum)
函数的优点
-
函数其实就是将一段需要多次调用的代码抽离出来封装到一个盒子内部,方便在多个地方调用时简单化代码
- 抽离公共代码,项目代码整体更加简洁
- 方便(复用),在需要的地方直接 函数名 + 小括号 调用即可
函数的预解析
JS 引擎运行的时候分为两步 : 1 预解析 2: 代码执行
-
什么是预解析
- 在代码运行前,先全部分析一遍代码,这个行为叫做预解析
-
预解析的内容
- 变量预解析 (变量提升) :就是把所有声明的变量声明提升到当前作用域的最顶端,不提升赋值操作
- 函数预解析 (函数提升)
函数预解析,只会对声明式函数进行提升,赋值式函数不会被提升
//如果是声明式函数,可以先调用载声明,原因就是因为函数预解析,如下:
fn() //浏览器到这行的时候,发现是函数调用,所以不需要提升
function fn() { //这是一个声明式的函数,所以需要提升
console.log('我是一个函数,我被调用了')
}
//模拟预解析后的代码如下:
function fn() {
console.log('我是一个函数,我被调用了')
}
fn()
什么是作用域?
- 作用域 就是变量可以起作用的范围区域
作用域分为两个:
- 全局作用域(直接书写在script 内书写的代码)
- 局部作用域(在JS中,只有函数能够创建局部作用域)
function fn() {
var sum = '我是一个局部变量,只能在函数内部使用'
console.log(sum)
}
fn()
console.log(sum) //这里会报错,因为找不到这个变量,sum是一个局部变量
var num = '我是一个全局变量'
console.log(num) //这里正常输出
作用域链
-
什么是作用域链?
- 作用域链就是在访问一个变量的时候, 如果当前作用域内没有会去自己的父级作用域, 也就是上一层作用域内查找, 如果找到就直接使用, 如果没有找到继续向上层查找直到查找到 最顶层的全局作用域, 如果找到了直接使用, 如果没找到 报错提示变量不存在(未定义)我们将这个一层一层向上查找的规律, 叫做作用域链
var num = 999
function fn (){
var num = 666
function fn1 (){
function fn2 (){
console.log(num) //这里打印的值为666
}
fn2()
}
fn1()
}
fn()
//首先函数全部调用了,fn2()里面输出一个num,但是当前作用域没有这个变量,
//所以会向自己的父级,也就是fn1()里面查找,没有找到,继续往上,
//在fn()里面找到了var num = 666,然后就会把666输出
function fn1() {
function fn2() {
console.log(sum) //这里会报错
function fn3() {
var sum = 100
}
fn3()
}
fn2()
}
fn1()
//这里首先函数全部调用,然后在fn2()里面输出sum,当前作用域没有,向上级作用域也就是fn1()里面
//进行查找,也没有找到,然后来到了全局作用域,发现还是没有,所以会停止查找,出现报错
//虽然 fn2 作用域内的子级作用域内(fn3函数内部) 有一个变量叫做 sum
//但是根据 作用域链的访问规则,并不会去这个 作用域内查找, 因为 作用域只会逐层向上查找, 并不会向下查找
作用域链的赋值规则
- 在给变量赋值的时候, 首先会去当前作用域查找, 如果有直接赋值, 并停止查找如果没有, 会去自己的父级查找, 在父级找到直接修改值然后停止查找, 如果没有继续向自己的父级查找, 直到找到全局作用域 在全局作用域内, 找到直接赋值修改他的值, 如果没有找到, 那么会在全局作用域创建一个变量, 并赋值
var num = 999
function fn (){
var num = 666
function fn1 (){
function fn2 (){
num = 100
console.log(num) //100
}
fn2()
}
fn1()
}
fn()
console.log(num); //999
递归函数
- 本质上还是一个函数
- 当一个函数在函数的内部, 调用了 自身, 那么就算是一个 所谓的 递归函数(只不过有点小缺陷)
//比如用递归函数计算100的阶乘
//不能用循环的时候就可以用递归函数,就是往前传值,在一层一层返回来结果
//100的阶乘可以看作100 * 99的阶乘 99的阶乘可以看作99 * 98的阶乘,依次类推
function fn(n) {
if(n === 1){ //如果走了这个分支, 就会直接 return 1 中断函数的递归
return 1 //注意这里返回的是1的阶乘,而不是数字1
}
return n * fn(n - 1) //实参会依次减一,再传给形参再次计算
}
var sum = fn(100)
console.log(sum)