定义

作用域(scope)指的是变量存在的范围。在 ES5 的规范中,JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。ES6 又新增了块级作用域。

对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取

  1. var v = 1;
  2. function f() {
  3. console.log(v);
  4. }
  5. f()
  6. // 1

上面的代码表明,函数f内部可以读取全局变量v

在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)

  1. function f(){
  2. var v = 1;
  3. }
  4. v // ReferenceError: v is not defined

上面代码中,变量v在函数内部定义,所以是一个局部变量,函数之外就无法读取。

函数内部定义的变量,会在该作用域内覆盖同名全局变量。

  1. var v = 1;
  2. function f(){
  3. var v = 2;
  4. console.log(v);
  5. }
  6. f() // 2
  7. v // 1

上面代码中,变量v同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v覆盖了全局变量v

注意,对于**var**命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量

  1. if (true) {
  2. var x = 5;
  3. }
  4. console.log(x); // 5

上面代码中,变量x在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。

函数内部的变量提升

与全局作用域一样,函数作用域内部也会产生“变量提升”现象。**var**命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部

  1. function foo(x) {
  2. if (x > 100) {
  3. var tmp = x - 100;
  4. }
  5. }
  6. // 等同于
  7. function foo(x) {
  8. var tmp;
  9. if (x > 100) {
  10. tmp = x - 100;
  11. };
  12. }

函数本身的作用域

函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

  1. var a = 1;
  2. var x = function () {
  3. console.log(a);
  4. };
  5. function f() {
  6. var a = 2;
  7. x();
  8. }
  9. f() // 1

上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2

总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。

  1. var x = function () {
  2. console.log(a);
  3. };
  4. function y(f) {
  5. var a = 2;
  6. f();
  7. }
  8. y(x)
  9. // ReferenceError: a is not defined

上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。

同样的,函数体内部声明的函数,作用域绑定函数体内部。

  1. function foo() {
  2. var x = 1;
  3. function bar() {
  4. console.log(x);
  5. }
  6. return bar;
  7. }
  8. var x = 2;
  9. var f = foo();
  10. f() // 1

上面代码中,函数foo内部声明了一个函数barbar的作用域绑定foo。当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x。正是这种机制,构成了下文要讲解的“闭包”现象。