JS的函数

164 阅读13分钟

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
     ```