# 作用域与变量提升

  • 变量声明的关键词
    • var
    • let
    • const
    • function
  • 变量提升是指 声明式函数及变量的声明都将被提升到作用域 scope (全局域或者当前函数作用域) 顶部的行为

# 作用域

  • 案例一

    var a = 1;
    
    1
    • a 是变量 相当于盒子 用来代表一个值的符号
    • 1 是值
    • = 是赋值 将变量和值绑定在一起
    • 作用域是指变量和值绑定的有效范围
  • 案例二

    let a = 'melon';
    
    function f1() {
      return a;
    }
    
    function f2() {
      let a = 'ricky';
      return f1();
    }
    
    console.log(f1());
    console.log(f2());
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    • 函数 f1 返回变量 a 是外层定义的 a

    • 函数 f1 和函数 f2 没有直接的关系 所以没办法读取 f2 定义变量 a

# 变量声明和变量赋值

  • 案例一

    var a = 1;
    var b = 2;
    
    1
    2
    • 当我们进行变量声明且赋值时 实际上是分成两步 先声明好所有的变量 然后赋值
    var a;
    var b;
    a = 1;
    b = 2;
    
    1
    2
    3
    4
  • 案例二

    var a = 1;
    console.log(a);
    console.log(b);
    var b = 2;
    
    1
    2
    3
    4
    • 当有多个变量同时声明赋值时 实际上是声明赋值一个变量 再声明赋值一个变量
    var a;
    var b;
    a = 1;
    console.log(a);
    console.log(b);
    b = 2;
    
    1
    2
    3
    4
    5
    6

# 函数声明优先级高于变量声明

  • 案例一

    console.log(a);
    function a() { }
    console.log(a);
    var a = 1;
    console.log(a);
    
    1
    2
    3
    4
    5
    • 不管变量和函数写在什么位置 所有变量声明会被整体提升到作用域顶部 但是函数声明在变量声明的后面
    var a;
    function a() { }
    console.log(a);
    console.log(a);
    a = 1;
    console.log(a);
    
    1
    2
    3
    4
    5
    6
  • 案例二

    function f1() {
      f2();
    
      function f2() {
        console.log('我是f2的打印');
      }
    }
    
    f1();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    • 声明式函数会提前到函数最前面 而表达式函数则只会 提前声明不会赋值
    function f1() {
      f2();
    
      let f2 = function () {
        console.log('我是f2的打印');
      }
    }
    
    f1();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

# letconst 变量声明 (opens new window)

  • 块级作用域

    function fn() {
      let b = 'melon';
    
      if (true) {
        var a = 'ricky';
        let b = 'ricky';
      }
    
      console.log(a);   
      console.log(b);   
    }
    
    fn();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    • let b = 'melon' 声明变量 b 作用域是整个函数 fn
    • if 语句
      • 使用 var 声明变量是 函数作用域 所以函数 fn 范围内都可以访问变量 a
      • 使用 let 声明变量是 块级作用域 所以 if 范围内访问变量 bricky
  • 不允许重复声明

    var a = 1;
    let a = 2;	// Uncaught SyntaxError: Identifier 'a' has already been declared
    
    1
    2
  • 暂时性死区

    var x = 1;
    if(true) {
      console.log(x);	//Uncaught ReferenceError: Cannot access 'x' before initialization
      let x = 2;
    }
    
    1
    2
    3
    4
    5

# 总结

  • 代码在正式执行之前先进行一次预编译 首先将变量声明及函数声明提升至当前作用域的顶端 然后代码的正式运行
  • 如果当前作用域中存在此变量声明 无论它在什么地方声明 引用此变量时就会在当前作用域中查找
  • 作用域其实就是一个 变量绑定的有效范围
  • 如果在同一个作用域中存在多个同名函数声明 后面出现的将会覆盖前面的函数声明
  • 声明式函数 优先权是最高的 然后是表达式函数和变量声明 按顺序执行
  • letconst 声明变量时 因为暂时性死区 所以不能变量提升
  • try...catchcatch 会延长作用域链
  • with 语句可以手动往作用域链最前面添加一个对象 但是 严格模式下不可用

# 面试题

  • 题目一

    var a = 1;
    function fn() {
      if (!a) {
        var a = 10;
      }
      console.log(a);
    }
    fn();
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 题目二

    var a = 1;
    function fn() {
      a = 10;
      return;
      function a() { }
    }
    fn();
    console.log(a);
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 题目三

    var name = 'Ricky';
    (function () {
      if (typeof name === 'undefined') {
        var name = 'Melon';
        console.log('Goodbye ' + name);
      } else {
        console.log('Hello ' + name);
      }
    })();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 题目四

    (function() {
      var a = b = 1;
    })();
    console.log(a);
    console.log(b);
    
    1
    2
    3
    4
    5
  • 题目五

    function f1(a) {
      var a;
      return a;
    }
    
    function f2(a) {
      var a = "Ricky";
      return a;
    }
    
    let arr = [f1("Melon"), f2("Melon")];
    console.log(arr);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12