js中的函数与作用域

65 阅读8分钟

续 : 上次练习作业

  1. 用户输入一个整数 n,计算 n 的阶乘。即n*(n-1)*(n-2)*……*3*2*1
  2. 苹果 3 元一个, 鸭梨 2 元一个, 桃子 1 元一个。现在想用 200 元正好买 100 个水果, 用 JS 列出所有购买方案
  3. 在控制台输出 100~200 之间所有的质数
    • 7 不是质数
      • 7/1 7
      • 7/7 7
    • 9 是质数
      • 9/1 9
      • 9/3 9
      • 9/9 9
// 1. 用户输入一个整数 n,计算 n 的阶乘。即`n*(n-1)*(n-2)*……*3*2*1`

// 假设 用户输入的 7
        // var num = prompt('请您输入一个数字') - 0
        /**
         *  逻辑:
         *      假设用户输入的是 数字 7
         *          1. 先拿到 7~1 的所有数字
         *          2. 计算阶乘
        */
        // var sum = 1
        // for (var i = num; i > 0; i--) {
        //     // sum = sum * i
        //     sum *= i
        // }
        // console.log(sum)





// 2. 苹果 3 元一个, 鸭梨 2 元一个, 桃子 1 元一个。现在想用 200 元正好买 100 个水果, 用 JS 列出所有购买方案

        /**
         *  1. 需要利用分支语句, 确保总价为 200 元, 数量为 100 个
         *
         *  2. 假设: 苹果购买了 a 个
         *           鸭梨购买了 b 个
         *           桃子购买了 c 个
         *
         *      a * 3 + b * 2 + c * 1 === 200
         *      a + b + c === 100
        */

        // for (var a = 0; a <= 100; a++) {
        //     for (var b = 0; b <= 100; b++) {
        //         for (var c = 0; c <= 100; c++) {
        //             if (a * 3 + b * 2 + c * 1 === 200 && a + b + c === 100) {
        //                 console.log(a, b, c)
        //             }
        //         }
        //     }
        // }






 // 3. 在控制台输出 100~200 之间所有的质数

        /**
         *  1. 判断数字 7 是不是质数
         *      7 % 2 !== 0
         *      7 % 3 !== 0
         *      7 % 4 !== 0
         *      7 % 5 !== 0
         *      7 % 6 !== 0
         * 
         * 
         *  2. 判断数字 9 是不是质数
         *      9 % 2 !== 0
         *      9 % 3 === 0
         *      9 % 4 !== 0
         *      9 % 5 !== 0
         *      9 % 6 !== 0
         *      9 % 7 !== 0
         *      9 % 8 !== 0
         * 
         *  2. 判断数字 8 是不是质数
         *      8 % 1 === 0
         *      8 % 2 === 0
         *      8 % 4 === 0
         *      8 % 8 === 0
        */


// 验证:
// var n = 7   // 判断 数字 7 是不是 质数
        // var sum = 0 // 当前变量初始值给一个 0,  但不是固定的, 你可以随意给值        相当于 自己和自己的约定
        // for (var i = 2; i < n; i++) {
        //     // if (7 % 2 !== 0) {
        //     if (n % i === 0) {
        //         // console.log('如果我输出了, 说明当前数字不是质数')
        //         sum++
        //     }
        // }
        // if (sum !== 0) {
        //     console.log('不是质数')
        // } else {
        //     console.log('是质数')
        // }


for (var n = 100; n <= 200; n++) {
            var sum = 0 // 当前变量初始值给一个 0,  但不是固定的, 你可以随意给值        相当于 自己和自己的约定
            for (var i = 2; i < n; i++) {
                if (n % i === 0) {
                    sum++
                }
            }
            if (sum === 0) {
                console.log(n, '是质数')
            }
        }

函数

  • 在 JS 中, 函数可以理解为一段 在程序中 (在当前页面) 多次出现的一段代码(多行)
  • 我们可以将这一段代码 放在一个盒子中, 这个盒子就是 函数

函数的使用必须要先定义, 然后调用

  1. 定义:
    • 分为两种方式:
      • 1.1 声明式定义 function 函数名 (参数) {函数要执行的一段代码} function: 关键字, 不要拼写错了 函数名: 将来调用函数的时候需要使用, 参考变量的命名规范 (参数): 参数可写可不写, 详细的讲解, 先欠着 {}: 大括号内书写, 函数在调用的时候, 需要执行的代码
      • 1.2 赋值式定义 var 变量名(函数名) = function (参数) {函数要执行的一段代码}
  2. 调用
    • 注意:如果函数只有定义, 没有调用, 那么这个函数永远不会执行
    • 语法:函数名()
    • 假设函数名为 fn -> 调用语法: fn()
// 1. 声明式定义
    // 定义解析
        function fn1() {
            console.log('函数调用的时候, 我会输出, 如果没有调用, 我永远不执行')
        }
        // 2. 调用
        fn1()

        // 3. 赋值式定义
        var fn2 = function () {
            console.log('我是 FN2 函数')
        }
        fn2()

函数中的参数

 * 因为如果一个函数没有参数的话, 这个函数的功能十分固定
 * 如果想要让一个函数更加灵活, 那么一定需要利用函数的参数
  • 函数的参数其实分两个, 书写的位置不同
    1. 形参: function 后的小括号内书写, 书写的时候, 直接写一个 "变量名", 然后在这个函数内部, 可以使用 形参起名的时候, 参考变量的命名规范
    2. 实参: 函数调用时的小括号内书写的, 作用是传递给函数的形参, 给形参赋值
  • 注意: 形参和实参, 都可以传递多个! 但是数量要一致

重要

  • 参数的默认值
    • 就在需要书写 形参的后边利用 赋值号 给这个形参一个默认值
      • 在你没有传递对应的实参的时候, 形参会利用默认值
      • 注意, 如果形参有对应的实参, 那么默认值不会生效
        注意: 需要默认值的形参, 一般会放在函数的最后
// 错误写法
       function fn(a = '我是一个默认值', b) {
           console.log(a, b)
       }

       fn(undefined, '当前字符串应该给到形参b')

       // 正确的书写默认值
       // function fn(a, b = '我是一个默认值') {
       //     console.log(a, b)
       // }

       // fn(100, 200)

当实参与形参数量不一致时

// 实参的数量 > 形参的数量;     传递的时候 实参会按照顺序依次传递给对应的形参, 多余的实参, 无法再当前函数以形参的方式去使用
       // function fn(a, b) {
       //     console.log(a, b)
       // }
       // fn(100, 200, 300, 400, 500)****


       // 形参的数量 > 实参的数量, 传递的时候 实参会按照顺序依次传递给对应的形参, 多余的形参, 没有值, 是 undefined
       // function fn(a, b) {
       //     console.log(a, b)
       // }
       // fn(100)

       // 正常书写多个参数
       // function fn(a, b) {
       //     // var num = 1 + 1
       //     // console.log(num)

       //     console.log(a, b)
       // }
       // fn(100, 'str')

小练习
封装一个函数, 判断一个数字,是不是质数

function fn(num) {
            // 判断一个数字是否为质数
            /**
             *  逻辑:
             *      假设要判断的数字为 7        是一个质数
             *          7 % 2 !== 0
             *          7 % 3 !== 0
             *          7 % 4 !== 0
             *          7 % 5 !== 0
             *          7 % 6 !== 0
             * 
             *      假设要判断的数字为 8        不是质数
             *          8 % 2 === 0
             *          8 % 3 !== 0
             *          8 % 4 === 0
             *          8 % 5 !== 0
             *          8 % 6 !== 0
             *          8 % 7 !== 0
            */
            var sum = 0
            for (var i = 2; i < num; i++) {
                if (num % i === 0) {
                    sum++
                }
            }

            if (sum === 0) {
                console.log(num, '是质数')
            } else {
                console.log(num, '不是质数')
            }
        }

        fn(5)
        fn(7)
        fn(8)
        fn(20)

函数返回值

  • 每一个函数内部都有一个 return, 这个能够决定当前函数的返回值是什么,如果没有书写, 那么默认返回一个 undefined
  • 函数内部如果没有书写 return, 那么函数的执行(调用)结果, 是一个 undefined
  • 如果我们需要更改函数的返回值, 需要将 return 书写在函数内代码的最后
  • return 具有中断函数的能力, 所以必须放最后
// function fn(a, b) {
        //     var sum = a + b

        //     return sum
        // }

        // var res = fn(500, 200)  // 讲 fn 函数的调用结果, 赋值给 变量 res
        // console.log(res)

        // console.log(sum) // 函数内部创建的变量, 无法在函数外使用, 详细原因后续解释

        // return
        function fn() {
            console.log(1)
            console.log(2)
            console.log(3)
            console.log(4)

            return 123
        }

        var res = fn()

        console.log(res) 
封装一个函数, 判断一个数字是不是质数, 根据结果返回不同的布尔值, 如果是质数, 返回一个 true, 否则返回false

小案例练习

// 封装一个函数, 判断一个数字是不是质数, 根据结果返回不同的布尔值, 如果是质数, 返回一个 true, 否则返回false

        /**
         *  1. 需要书写一个函数
         *  2. 需要 一个 参数
         *  3. 需要书写返回值
        */

        function fn(num) {

            var sum = 0
            for (var i = 2; i < num; i++) {
                if (num % i === 0) {
                    sum++
                }
            }

            // if (sum === 0) {
            //     // console.log('是质数')
            //     return true
            // } else {
            //     // console.log('不是质数')
            //     return false
            // }

            return sum === 0
        }

        var res = fn(7)
        console.log(res)    // true

        var res1 = fn(9)
        console.log(res1)    // false

预解析

1.变量
    变量提升, 其实就是在变量定义前去使用这个变量
    使用 var 声明的变量, 具有变量提升的能力, 换句话说, 再定义变量前是用变量
    不会报错, 但是值, 变成了 undefined
2.函数 (声明式函数)
    浏览器再解析代码的时候, 会将函数提升到当前作用域(当前页面)的最顶端
// 变量
        // // 1. 先使用变量
        // console.log(a)  // undefined
        // // 2. 再定义变量
        // var a = 100

        /**
         *  正常书写代码:
         *      console.log(a)
         *      var a = 100
         * 
         *  浏览器预解析后的代码
         *      var a
         *      console.log(a)
         *      a = 100
        */
  • 声明式与赋值式的区别
    1. 写法不同
    2. 赋值式不能在函数定义前去调用, 而声明式可以
      • 原因:
        • 赋值式是利用 变量提升
        • 声明式是利用 函数提升
fn1()
        // fn2()        // undefined()

        // 声明式
        function fn1() {
            console.log('fn1 函数')
        }

        // 赋值式
        var fn2 = function () {
            console.log('fn2 函数')
        }

        fn1()
        fn2()

作用域

  变量可以使用的范围
  因为变量不是在任何地方都可以使用, 而这个能够使用的范围, 我们称之为 作用域
  1. 全局
    • 是 JS 中 最大的一个作用域
    • 在 全局作用域中, 创建的变量, 可以在任意的地方使用
    • 直接写在 script 标签内的 变量, 我们称之为全局变量, 这个作用域也叫做 全局作用域
    • 我们的全局作用域中有一个 浏览器帮我们生成的 对象 (一种数据类型), 叫做 window
    • 我们在全局作用域声明的变量会自动添加到 window 对象中
  2. 局部 (函数作用域)
    • 在 JS 中, 只有函数能够创建局部作用域
    • 在局部作用域内创建的变量, 只能在当前作用域内使用, 超过这个作用域则不能使用
  3. 尽可能的多写局部变量, 不写全局变量
// var a = 100
        // var a1 = '我是一个全局变量'

        // console.log(window)

        // alert('我是一个弹出框')


        function fn() {
            var str = '我是定义在 fn 函数的局部作用域内的变量, 超过这个作用域就不能使用了'

            console.log(str)
        }

        fn()

        function fn1() {
            var str = '我是 fn1 函数内部的 str 我的变量名和 fn 函数内的 相同, 但是因为 作用域不同, 所以互不影响'

            console.log(str)
        }

        fn1()

        // console.log(str)

作用域链

  • 是一个 纯概念性的东西
  • 当我们在某一个作用域内获取一个变量的时候
    1. 首先在自己(当前)的作用域内查找, 找到就使用, 如果没有找到, 会去自己(当前)作用域的上层作用域(父级作用域)

    2. 如果在 上层作用域 找到, 直接使用, 并停止查找, 如果没有找到, 那么继续向上层查找

    3. 如果上层作用域也没有找到, 那么会继续去上层作用域找, 直到找到全局作用域, 如果还没有找到, 此时报错

    • 上述的也叫做 访问规则; 重点, 变量在访问的时候, 会顺着作用域 逐层向上查找, 不会逐层向下查找

赋值规则:

  • 如果再给一个变量赋值的时候, 首先会在当前作用域内查找这个变量
  • 如果没有找到, 那么回去上层作用域查找, 如果还没有, 继续向上
  • 直到找到了全局作用域也没有找到, 在全局创建一个变量, 并赋值
var a = 1
        
        function fn () {
            var a = 2

            function fn1 () {
                var a = 3

                console.log('fn1', a)
            }
            fn1()

            console.log('fn', a)
        }

        fn()

        // 赋值规则
        // function fn() {
            
        //     a = 100
        //     console.log(a)  // 100
        
        // }
        // fn()
        // console.log(a)  // 100




        // function fn1 () {
        //     a = 1
        // }
        // fn1()

        // console.log(a) // 1

        // 访问规则
        // var a = 'QF666'
        // function fn() {
        //     function fn1() {
        //         var a = 100
        //     }
        //     fn1()
        //     console.log(a)
        // }
        // fn()



        // 作用域链
        // var a = 10
        // function fn () {
        //     function fn1 () {
        //         console.log(a)
        //     }
        //     fn1()
        // }
        // fn()