19、函数的概念
首先一定要明确,和数学中的函数完全是两回事
*
* 在JS 中,函数可以理解为一段在程序(页面)中多次出现的代码段 封装起来的盒子
*
* 简单来说,JS 的函数就是一个盒子,盒子里边装的是﹑在当前页面中多次出现的较为复杂的代码段
20、函数的使用
* 如果函数只定义不调用,那么没有任何意义
1. 函数的定义(创建一个盒子)
分为两种方式定义
* 1.1 声明式定义
* 语法:function fn (){}
* function: 关键字 -> 表明后续的是一段函数
* fn: 函数的名字 -> 将来函数调用的的候需要用到,函数名自定义
* (): 内部填写参数 -> 欠着,后续详细讲解
* {}:内部填写函数调用时要执行的代码段
```js
function fn1(){
console.log('我是fn1函数')
}
fn1()
```
* 1.2 赋值式定义
* 语法:var fn = function () {}
```js
var fn2 = function () {
console.log('我是fn1函数')
}
fn2()
```
2.函数的使用(使用盒子内的代码)
* 不管是声明式还是赋值式定义的函数,调用方式都是一样的
* 调用语法:函数名() / 变量名()
21、声明式与赋值式的区别
* 1.写法不同
* 2.调用上略有不同
* 声明式定义的函数,可以在 函数定义前 去调用
```js
fn1()
console.log('我是fn1函数定义前的 输出~~~')
function fn1(){
console.log('我是fn1函数')
}
fn1()
```
* 赋值式定义函数,不能在函数定义前 去调用
```js
//fn2()
console.log(fn2) //undefined
console.log('我是fn2函数定义前的 输出~~~')
var fn2 = function () {
console.log('我是fn2函数')
}
// fn2()
console.log(fn2) //打印了fn2这个变量内部存储的值
console.log(fn2()) //打印了fn2这个函数的执行结果,一般默认是undefined
```
*
* 赋值式定义函数不能在函数定义前调用的原因
* 赋值式定义,其实就是声明一个变量,然后给他赋值为一个函数
*
* 再JS中,如果再定义变量之前使用变量的话。那么变量的值为undefined (变量提升 面试可能会问)
*
* 函数的执行结果,一般默认都是undefined,除非手动更改
22、函数的参数
①.函数的参数
* 函数的参数如何书写?
* 书写在function后的小括号内
* 参数的作用
* 如果一个函数没有书写参数,那么这个函数的功能相对来说比较单一
* 如果书写了参数,能够使我们这个函数的使用更加灵活
* 参数的书写,分为两个
* 1.function 后的小括号内 书写的参数我们叫做"形参"
!!! 形参的作用:书写之后,相当于在函数内部创建了一个变量,变量实际的值由"实参”传递
* 2.函数名后的小括号内 书写的参数我们叫做“实参”
!!! 实参的作用:将自身的值,按照一一对应的关系,传递给形参
```js
// 一个需求,需要封装一个函数,这个函数内需要计算出1+2的值,并输出在页面上
function fn() {
var sum = 1 + 2
console.log(sum)
}
fn()
// 新需求:封装一个函数,这个函数内需要计算5 + 8 的值,并输出在页面上
function fn1() {
var sum = 5 + 8
console.log(sum)
}
fn1()
// 新需求:封装一个函数,这个函数内需要计算100 + 200的值,并输出在页面上
function fn2(a, b) {
var sum = a + b
console.log(sum) //300
}
fn2(100,200)
// fn2('我是第一个实参','我是第二个实参')
// 新需求:计算300 + 400的值
fn2(300,400)
// 新需求:计算365+ 1的值
fn2(365,1)
```
②.函数参数的注意事项
函数的参数
* 形参 和 实参 两个的数量,要一一对应
* 1. 形参的数量如果大于实参
* 如果形参的数量大于实参的数量,那么会将实参按照顺序一一传递给 对应的形参 多出来的形参,相当于变量只定义没赋值,所以他们的值﹑是undefined
* 2. 实参的数量如果大于形参
* 如果实参的数量大于形参的数量,那么会将实参按照顺序一一传递给 对应的形参多出来的实参,无法在函数内部通过参数的方式调用
```js
function fn(a , b , c , d) {
console.log(a , b , c , d)//1 2 undefined undefined
}
fn(1,2)
function fn(a , b) {
console.log(a , b) //100 200
}
fn(100,200,300,400)
/*
*函数参数的默认值
*
* *函数再创建形参的时候,默认给一个值,将来在调用函数的时候,
* *如果没有传递那么这个形参的值也不会是 undefined 而是给的默认值
*
* *如果传递了对应的值,那么形参的值是实参传递进来的值,否则按照默认值来运行
* */
function fn(a = 100 , b = '我是形参b' , c) {
console.log(a , b , c) //100 '我是形参b' undefined
}
fn()
```
23、函数的返回值
* 函数的返回值(函数的执行结果)
*
* 在函数内部创建(定义)的变量,只能在函数内部使用,后续学习作用域的时候会详细讲解
*
* 我们如果想在函数外部得到函数内部的某一个值,或者运算结果,我们可以通过return这个关键字来帮我们完成
```js
// 需求:封装一个函数,这个函数内需要计算100 + 200的值
function fn(a , b) {
// 书写返回值
return a + b
}
// 创建变量 接收函数的返回值
var num = fn(100,200)
console.log('num的值',num) //300
// 如何书写返回值 如何接收返回值
function test() {
return 50 - 10
}
var sum = test()
console.log(sum) //40
```
24、课堂练习
```js
// 1.需求: 封装一个函数,这个函数能够计算一个区间内所有数字相加的和
/**
* 1.封装一个函数
*
* 2.需要参数吗?
*
* 3.需要几个参数?(需要两个参数)
*
* 4.需要返回值吗? 可写可不写,出于学习的目的,我们写上返回值
* */
function fn(start, end) {
// 计算从start 到 end 之间的所有数字的和
var sum = 0
for(var i = start; i <= end; i++) {
sum += i
}
return sum
}
var num = fn(1, 100)
console.log('num的值',num) //目前猜测应该为5050
```
```js
// 2.需求:封装一个函数 判断 一个数字!!! 是否为水仙花数;是水仙花数,返回一个true,否则返回false
/***
* 1.封装一个函数
* 2.需要参数吗?
* 3.需要几个参数?
* 4.需要返回值吗?
**/
/**
* 153 是一个水仙花数·
* 370 是一个水仙花数'
* 371 是一个水仙花数·
* 407 是个水仙花数
* */
// var num = prompt('请输入一个三位数') - 0
function fn(i) {
var baiW = parseInt(i / 100)
var shiW = parseInt(i / 10 % 10)
var geW = i % 10
var sum = Math.pow(baiW,3)+Math.pow(shiW,3)+Math.pow(geW,3);
if(sum === i){
// console.log(i,'是一个水仙花数')
return true
}else {
// console.log(i,'不是一个水仙花数')
return false
}
}
var boo = fn(153)
console.log(boo)
```
25、return的注意事项
return 的注意事项
* return 具有中断函数的能力
* 所以一般来说我们将它放在函数的尾部
26、函数的课后练习
```js
// 1. 封装一个函数, 判断一个数字是否为 水仙花数
// 什么是水仙花数, 一个四位数字, 各个位置的四次方和!!! 四次方和!!! 四次方和 如果等于自身, 那么就是水仙花数1234
/**
* *核心
* 1.封装一个函数*
* 2.需要参数吗?需要
* 3.需要几个?1个(默认是4位数字1000~9999)
* 4.需要返回值吗?
* 出于这个学习的目的我们这里写上返回值 同时我们约定,如果这个数字是水仙花数,返回一个 true否则_返回一个false
*/
function fn(i) {
// 计算 参数i 接收到四位数字,是否为水仙花数
var qianW = parseInt(i/1000)
var baiW = parseInt(i/100)%10
var shiW = parseInt(i/10)%10
var geW = i%10
// console.log(i)
// ** ES6 以后新推出的一个语法
// var sum = qianW ** 4
var sum = Math.pow(qianW,4)+Math.pow(baiW,4)+Math.pow(shiW,4)+Math.pow(geW,4)
if(sum === i){
// console.log(i,'是一个水仙花数')
return (i+'是一个水仙花数')
}else {
// console.log(i,'不是一个水仙花数')
return (i+'不是一个水仙花数')
}
}
var bool = fn(8569)
console.log(bool)
/**
* 2. 封装一个函数, 对一个四位数字加密
加密规则:
1. 每一位上的数字 +5 然后使用 10的余数替代
2. 一三交换位置, 二四交换位置
举例:
输入 1234
1. 每一位上的数字 +5 ===> 6789
2. 使用 10 的余数代替 ===> 6789
3. 一三 二四 交换位置 ===> 8967
输入 5655
1. 每一位上的数字 +5 ===> 0100
2. 使用 10 的余数代替 ===> 0100
3. 一三 二四 交换位置 ===> 0001 (这里需要打印0001, 不能打印1)
* */
function fn2(i) {
// 新需求:判断参数是否为4位数
if(i >= 1000 && i <= 9999){
var qianW = parseInt(i/1000)
var baiW = parseInt(i/100)%10
var shiW = parseInt(i/10)%10
var geW = i%10
// var sum = qianW * 1000 + baiW * 100 + shiW * 10 + geW
// console.log(sum)
// 置换
qianW = (qianW+5)%10
baiW = (baiW+5)%10
shiW = (shiW+5)%10
geW = (geW+5)%10
// return shiW * 1000 + geW * 100 + qianW * 10 + baiW
// var sum = shiW * 1000 + geW * 100 + qianW * 10 + baiW
// console.log(sum)
// return String(shiW) + String(geW) + String(qianW) + String(baiW)
return ''+ shiW + geW + qianW + baiW
}else {
console.log('传入的数字,不是四位数字')
}
}
var sum = fn2(5655)
console.log('加密后的数字为:',sum)
// 3. 封装一个函数, 求两个数字的最大公约数
/**
* 核心
* 1.封装一个函数
* 2.需要参数吗?需要
* 3.需要几个参数? 需要两个参数
* 4.需要返回值吗? 需要 将最大的公约数,返回 出去
*
* 思考:
* 如何计算 最大公约数
* 约数:整数X 除以 y(y!==0) ,余数%为0, 此时我们说y是x的约数
* */
function fn3(a,b) {
// for (var i = a; i >= 1; i--){//假设i是a和b的最大公约数
// if(a%i === 0 && b%i === 0){//如果能被a和b同时整除,那么i就是a和b的最大公约数
// return i
// }
// }
// 寻找两个数中较小的值
var min = a > b ? b : a //如果a>b条件成立,说b的值小,所以返回b,否则,说明a的值小。那么返回a
// 2.找约数
for (var i = min; i >= 1; i--) {
// 假设min的值为8,那么i的值可能是8 7 6 5 4 3 2 1
if(a % i === 0 && b % i === 0){
return i
}
}
}
var sum = fn3(8,12) //声明一个变量等于这个函数的结果
console.log('a和b的最大公约数是:',sum)//输出最大公约数
// 4.封装一个函数, 求两个数字的最小公倍数
function fn4(a,b){
/**
* 一个数学等式:
* 两数的乘积===两数的最大公约数*两数的最小公倍数
* a * b === a和b的最大公约数 * α和b的最小公倍数
*根据数学等式,做一个变换
* a* b / α和的最大公约数=== a和动b的最小公倍数
* */
/*var i = fn3(a, b) //这里的α和b其实就是fn2函数的两个形参,得到的值,就是α和b的最大公约数
var num2 = a * b / i //计算最小公倍数
return num2;//将最小公倍数返回*/
return a * b / fn3(a,b)
}
var zx = fn4(8,12)
console.log('a和b的最小公倍数是:',zx)
```
27、函数的预解析
预解析的一个表现就是 声明式函数再定义前可以被调用
*
* 预解析是什么?
* JS 在执行代码的时候, 会有一个 所谓的 解析阶段
* 解析阶段, 做了一件事, 就是 函数提升, 就是将 声明式 函数的定义, 提升到当前 作用域的最顶端
*
* 作用域的最顶端:
* 暂时理解为 当前页面的最开始的位置
```js
fn()
function fn() {
console.log('我是 fn 函数, 我被调用了')
}
```
一道面试题: 函数的预解析是什么?
正常书写的 代码
* fn()
* function fn() {
* console.log('我是 fn 函数, 我被调用了')
* }
*
* 浏览器会对我们的 JS 代码, 做一个 预解析, 预解析的时候, 会将函数提升到 当前作用域的最顶端, (暂时理解为 当前页面最开始的位置)
*
* fn() -> 这行代码是函数调用, 所以不需要提升
* function fn() { -> 这是一个声明式定义的函数, 所以需要提升
* console.log('我是 fn 函数, 我被调用了')
* }
*
* 预解析之后的代码长什么样(执行顺序)?
* function fn() {
* console.log('我是 fn 函数, 我被调用了')
* }
*
* fn() // 所以此时调用的时候, 因为 fn 函数已经定义完成了, 所以这里能够正常执行函数
28、作用域
什么是 作用域? (这是一道面试题)
* 就是变量可以起作用的范围
*
* 作用域分为两个 (这是一道面试题)
* 1. 全局作用域(直接在 script 内书写的代码)
* 再此作用域创建的变量, 我们叫做全局变量, 在当前 script 标签内的哪里都能使用
* 在 JS 中, 全局作用域中有一个 提前给我们准备好的 对象(一种数据格式, 后续会详细的讲解)
* 这个 对象叫做 window
* 我们创建的全局变量, 会被自动添加到 window 对象中
*
* 2. 局部作用域(在 JS 中, 只有函数能够创建局部作用域)
* 在此作用域创建的变量, 只能在当前作用域使用, 超出这个作用域(也就是在函数外边)去使用, 就会找不到变量
```js
var num = 100
// 假设 间隔 500 行
console.log(num)
function fn() {
var sum = '我是在函数 fn 内部创建的变量, 我是局部变量, 所以我只能在当前函数内使用'
var abc123 = '我是在 fn 函数内部创建的局部变量'
console.log(sum)
}
fn()
// console.log(sum) // 这里因为超出了这个变量的使用区间, 所以会 报错
var abc = '我是一个全局变量 abc' // 创建一个全局变量 abc
console.log(window)
```
29、作用域链
* 作用域链 (这是一个纯概念性的东西, 面试也可能会问)
* 作用域链就是在访问一个变量的时候, 如果当前作用域内没有
*
* 会去自己的父级作用域, 也就是上一层作用域内查找, 如果找到就直接使用, 如果没有找到继续向上层查找
*
* 直到查找到 最顶层的全局作用域, 如果找到了直接使用, 如果没找到 报错提示变量不存在(未定义)
*
* 我们将这个一层一层向上查找的规律, 叫做作用域链
```js
var num = 999
function fn1() {
var num = 100
function fn2() {
console.log(num)
/**
* 打印的值 为 100
* 1. 先在当前作用域内, 也就是 fn2 函数内部开始查找变量 num, 然后发现 当前作用域内 没有这个变量
* 所以会去自己的父级的作用域内查找(也就是 fn1 这个函数内部)
* 2. 来到了自己父级内部查找, 此时找到了一个变量 num 他的值 为 100, 然后直接使用这个变量 并停止查找
*/
}
fn2()
}
fn1()
var num = 999
function fn1() {
function fn2() {
console.log(num)
/**
* 打印的值 为 999
* 1. 先在当前作用域内, 也就是 fn2 函数内部开始查找变量 num, 然后发现 当前作用域内 没有这个变量
* 所以会去自己的父级的作用域内查找(也就是 fn1 这个函数内部)
* 2. 来到了自己父级内部查找, 发现并没有一个叫做 num 的变量, 然后继续向上层查找, 也就是 全局作用域 内
*
* 3. 来到全局作用域内查找的时候 发现了一个叫做 num 的变量, 值为 999, 然后停止查找, 直接使用该变量
*/
}
fn2()
}
fn1()
function fn1() {
function fn2() {
console.log(num)
/**
* num 找不到, 所以会报错
*
* 1. 先在当前作用域内查找, 也就是 fn2 内部, 发现没有, 去自己的父级查找, 也就是 fn1 内部
* 2. 来到了 fn1 内部查找, 发现没有, 去自己的父级查找, 也就是 全局作用域
* 3. 来到了全局作用域内查找, 发现还是没有, 然后停止查找, 返回一个 num 未定义的报错
*
* 4. 虽然 fn2 作用域内的子级作用域内(fn3函数内部) 有一个变量叫做 num 但是根据 作用域链的访问规则
* 我们并不会去这个 作用域内查找, 因为 作用域只会逐层向上查找, 并不会向下查找
*/
function fn3() {
var num = 666
}
fn3()
}
fn2()
}
fn1()
```
* 作用域链的赋值规则
* 在给变量赋值的时候, 首先会去当前作用域查找, 如果有直接赋值, 并停止查找
*
* 如果没有, 会去自己的父级查找, 在父级找到直接修改值然后停止查找, 如果没有继续向自己的父级查找, 直到找到全局作用域
*
* 在全局作用域内, 找到直接赋值修改他的值, 如果没有找到, 那么会在全局作用域创建一个变量, 并赋值
```js
function fn1() {
function fn2() {
num = 100
}
fn2()
}
fn1()
console.log(num) // 100
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() {
var num = 999
function fn2() {
num = 100
/**
* 在当前作用域内查找 num 发现没有, 会去自己的父级作用域内查找, 也就是 fn1 函数内部
*
* 在 fn1 函数内部发现一个变量 num 然后值为 999 我们会对这个变量做一个重新赋值的操作
*
* 也就是将他的值 重新修改为 100
*/
}
fn2()
}
fn1()
console.log(num) // 666
var num = 666
function fn1() {
function fn2() {
num = 100
/**
* 在当前作用域内查找 num 发现没有, 会去自己的父级作用域内查找, 也就是 fn1 函数内部
*
* 在 fn1 函数内部, 发现没有 这个变量, 继续去自己的父级作用域查找, 也就是 全局作用域
*
* 在全局作用域发现了一个变量 叫做 num, 他的值是 666, 我们将这个变量重新赋值为 100
*/
}
fn2()
}
fn1()
console.log(num) // 100
```
30、递归函数
递归函数
*
* 本质上还是一个函数
*
* 当一个函数在函数的内部, 调用了自身, 那么就算是一个 所谓的 递归函数(只不过有点小缺陷)
```js
function fn(n) {
/**
* 计算 4 的阶乘
* 4 的阶乘: 4 * 3的阶乘
*/
// return 4 * fn(3)
return n * fn(n - 1)
}
var sum = fn(4)
console.log(sum) // 此时打印的值 为 4 的阶乘
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) //3608800
```