JS的作用域以及作用域链和递归函数/规律的数列

176 阅读6分钟

* 作用域

     *      
     *      1. 变量使用使用区间的, 变量不是说声明之后在哪里都可以用, 他有一个使用的范围, 我们把这个范围叫做 作用域
     * 
     *  作用域分为两种
     *      1. 全局作用域
     *              JS 给我们提供了一个叫做 window 的全局作用域, 可以理解为 整个 script 标签内的作用域, 就是全局作用域
     *              全局变量都会挂载到 window 对象上
     *      2. 局部作用域       小预告: 后续会学习新的作用域 (块级作用域)
     *              在 JS 中, 有 且只有 函数能够创建 局部作用域(函数作用域), 局部作用域开始和结束位置, 就是函数代码段的开始和结束位置
     *              在 局部作用域(函数作用域)内 声明的变量叫做局部变量
     *              局部变量 不会挂载到 window 对象上
     * 
     *      以局部变量为荣, 以全局变量为耻
    */
    // var num = 100;
    // var aaa = 10000;
    /**
     * 根据规则, 这里是 script 标签内部, 所以是全局作用域, 我这个 num 也可以叫做 全局变量
    */
    // console.log(num)    // 100
    // console.log(window.num)    // 100
    // console.log(aaa)    // 10000


    // var name = '哈哈'
    // 因为全局作用域 window 上有一个属性叫做 name , 所以我们的全局变量 要避免起名的时候叫做 name , 尤其是开发的时候
    // console.log(window) // ?

    // function fn() {
    //     var num = 100
    //     console.log(num)    // 100
    //     console.log(window) // ?
    // }
    // fn()

    // var num = 100   // 李四
    // var num = 10000   // 张三
    // console.log(num)    // 李四以为还是100呢, 不知道张三重新声明变量了

    var num = 100;  // 李四

    function fn() {
        var num = 10000   // 张三
        console.log(num)    // 10000
    }
    console.log(num)    // 李四又来调用变量了
    
    
    

作用域链

   * 什么是作用域链
     *      * 在查找变量时, 会先在当前作用域内查找, 找到就用, 没找到去上层查找, 一直会查找到顶层作用域(全局---window)
     *      * 然后在查找过程中, 我们把逐层向上的一层一层查找 所构成的一个链条 叫做作用域链(实际是没有作用域链的,这是一个纯概念性的东西)
     * 
     *      变量的访问规则
     *          变量访问会 先在当前作用域内查找, 找到拿来直接用, 如果没有找到, 会去上层作用域查找, 找到直接用
     *              如果上层作用域没找到, 会继续去上层作用域  的 上层作用域内查找, 找到直接用, 没找到会继续往上
     *              如果找到了全局作用域内 还是没有变量, 那么就会报错 num is not defined
     *      变量的赋值规则
     *          变量赋值会 现在当前作用域内查找, 找到直接拿来赋值, 如果没找到, 会去上层作用域查找, 找到直接赋值
     *          如果上层作用域也没找到, 会继续去上层作用域 的 上层作用域内查找, 找到直接赋值, 没找到继续往上
     *          如果找到了全局作用域内, 还是没有找到变量, 那么会直接将变量定义在当前作用域内(全局作用域)然后赋值
     * 
     * 重点:    作用域链 只会向上查找, 不会向下
    */

    // var num = 100
    // function fn() {
    //     console.log(num)
    // }
    // fn()

    // var num = 100;
    // function fn() {
    //     function fn1() {
    //         console.log(num)
    //     }
    //     fn1()
    // }
    // fn()
    /**
     * 首先 在 fn1 这个作用域内 访问变量 num
     *      会先在当前作用域内查找(fn1) 找到就用, 但是没找到, 所以会去上层作用域内查找(fn)
     *      在 fn 作用域内查找后, 发现还是没有, 去继续去上层作用域内查找(全局---window)
     *      然后在全局作用域内找到了 变量 num 值为 100
     * 
     *  所以此时会打印 100
    */

    // function fn() {
    //     function fn1() {
    //         num = 1000;
    //     }
    //     fn1()
    // }
    // fn()
    // console.log(num)
    /**
     * 首先 在 fn1 这个作用域内 给变量 num 赋值
     *      那么 会先在当前作用域(fn1)查找 num,  但是没找到, 所以会去上层作用域内查找(fn)
     *      在 fn 作用域内查找后, 发现还是没有, 去继续去上层作用域内查找(全局---window)
     *      然后发现, 在全局作用域内 仍没有找到一个叫做 num 的变量
     *      那么会直接将 num 定义在全局, 然后赋值
    */

    // var num = 100;

    function fn() {
        var num = 10000
        console.log(num)
    }

    console.log(num)
    /**
     * 函数内部
     *         在函数内部 打印了 num, 先在当前作用域内查找, 找到就使用
     *              所以此处打印 1000
     * 
     * 函数外部
     *          打印 变量 num, 会现在当前作用域查找, 找到就使用
     *              所以此处打印 100
    */


    function fn() {
        var num = 10000
    }
    fn()

    console.log(num)    // 'num' is not a defined
    /**
     * 在此处打印变量 num
     *      会先在当前作用域内查找, 找到就使用, 找不到去上层, 但! 此时已经是全局作用域, 所以不会去上层, 找不到直接报错
     * 
     *      同时注意: 不会向下 查找
     
    

递归函数

* 递归函数
     *      在函数内部, 调用自身, 此时就是写了一递归, 但 它是死递归
     * 
     *      要想写一个正确的递归函数, 需要在递归内部, 写上 返回点(到某个条件时, 停止递归)
    */
    function fn(n) {
        // 1. 先写折返点, 到某个条件停止递归
        if (n == 1) return 1

        // 2. 不符合结束递归时的代码
        return n * fn(n - 1)
    }

    // function fn() {  // 死循环版本
    //     fn()
    // }
    // fn()

    // function fn() {
    //     if (满足条件1) {
    //         fn()
    //     }
    //     if (不满条件1, 满足条件2) {
    //         // 结束递归函数
    //     }
    // }
    // fn()

    // function fn(n) {
    //     if (n == 1) {
    //         return 1
    //     }

    //     // return n * fn(n - 1)
    //     // 4 的阶乘 == 4 * 3的阶乘
    //     return n * fn(n - 1)
    //     // return 100 * 99 的阶乘
    //     // return 99 * 98 的阶乘
    //     // return 98 * 97 的阶乘
    //     // ......
    // }
    /**
     * fn 函数可以计算一个数字的阶乘
     *      使用的时候
     *          fn(100) --> 100的阶乘
     *          fn(50) --> 50的阶乘
     *          fn(4) --> 4的阶乘
    */
    var sum = fn(4)
    console.log(sum)    // 24

    /**
     * fn(4)
     *      运行 if 判断 n == 1 吗, 不等于, 所以运行 --> 4 * fn(3)  ---> 根据下一轮的执行, 知道 fn(3) == 6 所以此处运算为 24
     *      
     *          fn(3)   ---> 6
     *              运行 if 判断 n == 1 吗, 不等于, 所以运行 --> 3 * fn(2)  ---> 根据下一轮的执行, 知道 fn(2) == 2 所以此处运算为 6
     *          
     *                  fn(2)   --> 2
     *                  运行 if 判断 n == 1吗, 不等于, 所以运行 --> 2 * fn(1)   ---> 根据下一轮的执行, 知道 fn(1) == 1 所以此处运算为 2
     * 
     *                      fn(1)  --> 1
     *                      运行 if 判断 n == 1吗, 条件符合, 直接返回1, (递归  递的阶段结束, 开始归的阶段)
     
     

规律的数列

 * 有一个数列
     * 
     *  第一位和第二位 固定为 1
     *  从第三位开始, 每个位置的数字, 是前两位的和
     * 
     * 
     *  1  1  2  3  5  8  13   21   34
    */

    function fn(n) {
        if (n == 1 || n == 2) {
            return 1
        }
        // return 数列中第 4 位 的值   +   数列中 第 3 位的值
        return fn(n - 1) + fn(n - 2)
    }
    var sum = fn(5)
    console.log(sum)
    /**
     * 假定 fn 函数就是求 数列某一个位置上的数字是什么
     * 
     *      fn(10)
     * 
     *      fn(6)
     * 
     * 
     * 假设我要求 第五位上的数字是多少