作用域,作用域链,递归函数

107 阅读5分钟

作用域

变量使用使用区间的, 变量不是说声明之后在哪里都可以用, 他有一个使用的范围, 我们把这个范围叫做 作用域

作用域分为两种

  1. 全局作用域
  • JS 给我们提供了一个叫做 window 的全局作用域, 可以理解为 整个 script 标签内的作用域, 就是全局作用域
  • 局变量都会挂载到 window 对象上
  1. 局部作用域
  • 在 JS 中, 有 且只有 函数能够创建 局部作用域(函数作用域), 局部作用域开始和结束位置, 就是函数代码段的开始和结束位置

  • 在 局部作用域(函数作用域)内 声明的变量叫做局部变量

  • 局部变量 不会挂载到 window 对象上

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

作用域链

  • 在查找变量时, 会先在当前作用域内查找, 找到就用, 没找到去上层查找, 一直会查找到顶层作用域(全局---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)    //100
      fn()                //10000 
    
  • 函数内部

  • 在函数内部 打印了 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)
      }
      fn(4)             //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)