javaScript作用域你真的了解吗?

373 阅读2分钟

作用域

作用域是可访问变量的集合或者说范围(例如全局的范围、函数的范围、语句块的范围),在作用域内,变量可访问,在作用域外变量不可访问。

js是词法作用域,变量的访问范围仅由声明时候的区域决定。

js的作用域可以分为3类

  • 全局作用域:在全局环境下声明的变量。在任意位置可以访问到。

  • 函数作用域:在函数内部声明的变量,函数内部和函数内部声明的函数中都可以访问到。访问变量时候先在函数内部找,找不到则在外层函数中找,直到全局作用域,形成“作用域链”。 函数作用域有“变量提升”和“函数声明提升”的特性。

  • 块级作用域:在语句块声明的变量,使用let和const声明的变量才作用于块级作用域,块级作用域没有变量提升。

案例一(初识)

    var a = 1;
    function f() {
      console.log(a);
    }
    function n() {
      var a = 2;
      f();
    }
    n();

分析:前面我们提到了js中的作用域是词法作用域,在声明的时候就已经决定了,所以这里a是会先在f函数中查找,查找不到会去全局查找,所以打印 1 。

案例二(感知)

    console.log(a); 
    a = 10;
    console.log(b);  
    var b = 20;
    var c = 30;
    let c = 40
    console.log(c);  

分析:第一个log会报错,因为a没有被声明,第二个log中的b用var声明的,存在变量提升,打印undefined。第三个log也会报错,因为let和const不允许重复声明。

案例三(不惑)

 var arr = ["第一次输出", "第二次输出", "第三次输出"];
    for (var i = 0; i < arr.length; i++) {
      setTimeout(function () {
        console.log(arr[i]);
      }, i * 2000)
    }

分析:考察的是setTimeout是异步执行的,此时的for循环里面使用的是var没有作用域,所以当for循环执行完毕的时候,i的值等于3 ,而arr[3]是undifined。

案例四(洞玄)

    for (var i = 0; i < 5; i++) {
      console.log(i);  
    }
    console.log(i); 

    for (let t = 0; t < 5; t++) {
      console.log(t);  
    }
    console.log(t); 

分析:第一个for循环由于js没有块级作用域,对于有块级作用域的语言来讲,初始化定义的变量,只存在于循环体中,所以第一个log打印的是 0 1 2 3 4 。而此时js循环体已经执行完毕,但是执行的结果仍然存储在执行体外部的执行环境中,所以第二个log打印的是5。第三个log中let存在作用域,所以打印 0 1 2 3 4 。而在for循环体外得不到i的值,所以第四个log是t is not defined。

案例五(知命)

改造下面的代码,使之输出0-9,写出你能想到的所有写法?

for (var i = 0; i < 10; i++) {
      setTimeout(() => {
        console.log(i)
      }, 1000)
    }

分析:for循环属于同步执行,for循环执行完毕才会执行setTimeout异步执行队列,当for循环执行完毕。此时的i是10,所以打印出来的i是10个10。

方法一

for (let i = 0; i < 10; i++) {
      setTimeout(() => {
        console.log(i)
      }, 1000)
    }

分析:将var变量改成let,特点是:使用let声明的变量会在当前作用域内。

方法二

for (var i = 0; i < 10; i++) {
      setTimeout(i => {
        console.log(i)
      }, 1000, i)
    }

分析:setTimeout回调函数的第三个参数可以作为第一个参数传入

方法三

for (var i = 0; i < 10; i++) {
      let _i = i
      setTimeout(() => {
        console.log(_i)
      }, 1000)
    }

分析:使用_i去存储i的值,let去创建独立的_i作用域

方法四

for (var i = 0; i < 10; i++) {
      (i => {
        setTimeout(() => {
          console.log(i)
        }, 1000)
      })(i)
    }

分析:利用函数自执行的方式,将i作为参数传递进去,构建块级作用域。

方法五

for (var i = 0; i < 10; i++) {
      setTimeout(console.log(i), 1000)
    }

分析:setTimeout的第一个参数一般只接收函数或者字符串,如果第一个参数是语句的话,js引擎会自动把该语句的外层包裹一层函数。