关于 JavaScript 作用域

最近看《JavaScript 语言精粹》时看到这么一句话:

不像许多其它语言,JavaScript 中的代码块不会创建新的作用域,因此变量应该被定义在函数的头部,而不是在代码块中。

那么我们就先来看看其它语言 (Java) 的块级作用域是怎么样的

public class Test {
  public static void main(String[] args) {
    if(true) {
      int age = 3;
      System.out.println(age); // 3
    }
    
    System.out.println(age); // Error
  }
}

在这里,age 是 if 这个语句块中的定义的块级变量,其生存周期就是这个语句块,所以在语句块外部访问是无效的。

在 JS 中,对于作用域,又可以分为全局作用域和局部作用域。局部作用域又被称为函数作用域(Function Scope),所有的变量和函数只能在作用域内部使用。

JS 是不支持块级作用域的,在语句块内中定义的变量可以在语句块外访问:

// demo1
function test(){
  if(true) {
    var age = 3;
  }
  console.log(age); // 3
}

为什么会出现这样的情况?

提升(hoisting)

之所以在语句块外能访问,是因为var关键字声明变量时,有一个变量提升的过程:

Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it’s declared. This behavior is called “hoisting”, as it appears that the variable declaration is moved to the top of the function or global code.

而在 demo1 中,函数 test 中 if 语句块里面的 age 变量发生了变量提升,上述代码等价于:

function test(){
  var age;
  if(true) {
    age = 3;
  }
  console.log(age); // 3
}

这里有一点需要注意:所谓的变量提升只是其定义上升,而变量的赋值是不会上升的。

所以在 JS 中,我们总在作用域的最开始声明变量,这样可以使其作用域变得更加清晰。

同样的,在 JS 中,定义一个 function 时也会发生提升:

hoisted(); // logs "foo"

function hoisted() {
  console.log("foo");
}

而函数表达式(Function Expressions)是不会提升的:

notHoisted(); // TypeError: notHoisted is not a function

var notHoisted = function() {
   console.log("bar");
};

let in ES6

在ES6中,新增了let关键字,用let声明的变量是存在块级作用域的

function test () {
  if (true) {
    let age = 3; 
  } 
  console.log(age); // ReferenceError: age is not defined
}

当我们谈及 JS 的作用域时,肯定会想到闭包、作用域链等概念。而这些知识,且待我学习学习先……

参考:

Show Comments