什么是作用域

2021-06-08 22:05:34 Javascript 大约 5 分钟

简单来说作用域就是根据名称查找变量的一套规则,它决定了变量的可见范围和生命周期,正确使用作用域可以使代码更清晰、易懂。作用域可以减少命名冲突,而且是垃圾回收的基本单元。

# 作用域分类

  1. 静态作用域**(词法作用域)😗* static scope;
    • 如果函数中的变量,没有在该函数中定义,就去定义该函数的地方查找;
    • **静态作用域,也叫此法作用域,代码写完后,变量的作用域就已确定不变;**是由你写代码时将变量和块作用域写在哪里决定的。
    • js使用静态作用域;
  2. 动态作用域: dynamic scope;
    • 如果函数中的变量,没有在该函数中定义,就去调用该函数的地方查找;
    • 动态作用域,代码写完后,变量的作用域还无法确定,它和调用它所在的函数有关;

# 认识作用域

// 全局作用域
function foo(a) {
  // foo 的作用域
  var b = a * 2;
  function bar(c) {
    // bar 的作用域
    console.log(a, b, c);
  }
  // 在 foo的作用中调用
  bar(b * 3);
}
foo(2); // 2, 4, 12
1
2
3
4
5
6
7
8
9
10
11
12

以上代码中有三层作用域:

  1. 全局作用域:即foo所在的作用域;
  2. 函数作用域:即foo函数所包含的作用域,也就是bar所在作用域;
  3. 函数作用域:即bar函数所包含的作用域,也就是 console.log(a, b, c)代码执行的地方;

# 全局作用域

在 JavaScript 中有一种特殊的对象称为 全局对象。这个对象在Node.js 对应的是 global对象,在浏览器中对应的是 window 对象。由于全局对象的所有属性在任何地方都是可见的,所以这个对象又称为 全局作用域。全局作用域中的变量不论在什么函数中都可以被直接引用,而不必通过全局对象。

满足以下条件的变量属于全局作用域:

  • 在最外层定义的变量;
  • 全局对象的属性;
  • 任何地方隐式定义的变量(未定义直接赋值的变量) 。

注意

需要格外注意的是第三点,在任何地方隐式定义的变量都会定义在全局作用域中,即不通过 var 声明直接赋值的变量。这一点经常被人遗忘,而模块化编程的一个重要原则就是避免使用全局变量,所以我们在任何地方都不应该隐式定义变量。

# 函数作用域

JavaScript 的作用域是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见,我们称为函数作用域。

使用==var==声明的变量在声明它的函数体以及该函数体中的嵌套函数体内都是可以访问的,在函数体外不可访问。

例1:

var x = 5;
function f() {
  var y = 10;
  console.log(x, y); // 5 10
  function f_1() {
    var z = 15;
    console.log(x, y, z); // 5 10 15
  }
   f_1();
}
f();
console.log(x, y, z) // 报错 y is not defined
1
2
3
4
5
6
7
8
9
10
11
12

以上代码说明,JavaScript 的函数定义是可以嵌套的,每一层是一个作用域,变量搜索顺序是从内到外。在函数体外无法访问函数体内的变量,变量只对这个函数内部可见。

例2:

if (true) {
	var somevar = 'value';
}
console.log(somevar); // 输出 value
1
2
3
4

以上代码输出value,这是因为 JavaScript 的作用域完全是由函数来决定的, if、 for 语句中的花括号不是独立的作用域(let、const除外)。

例3

var v1 = 'v1'
var f1 = function() {
  console.log(v1) // 输出 v1
}
f1()
var f2 = function() {
  var v1 = 'local'
  console.log(v1) // 输出 local
}
f2()
1
2
3
4
5
6
7
8
9
10

在函数中引用一个变量时, JavaScript 会先搜索当前函数作用域,或者称为“局部作用域”,如果没有找到则搜索其上层作用域,一直到全局作用域。

例4

var f = function() {
  var scope = 'f0'
  ;(function() {
    var scope = 'f1'
    ;(function() {
      console.log(scope) // 输出 f1
    })()
  })()
}
f()
1
2
3
4
5
6
7
8
9
10

我们在最内层函数引用了 scope 变量,通过作用域搜索,找到了其父作用域中定义的 scope 变量。

例5

var scope = 'top'
var f1 = function() {
  console.log(scope)
}
f1() // 输出 top
var f2 = function() {
  var scope = 'f2'
  f1()
}
f2() // 输出 top
1
2
3
4
5
6
7
8
9
10

通过 f2 调用的 f1 在查找 scope 定义时,找到的是父作用域中定义的 scope 变量,而不是 f2 中定义的 scope 变量。这说明了作用域的嵌套关系不是在调用时确定的,而是在定义时确定的。

注意

函数作用域的嵌套关系是定义时决定的,而不是调用时决定的,也就是说, JavaScript 的作用域是静态作用域,又叫词法作用域,这是因为作用域的嵌套关系可以在语法分析时确定,而不必等到运行时确定。

# 作用域嵌套

当一个块或者函数嵌套在另一个块或函数中时,就发生了作用域嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层作用域中继续查找,知道找到该变量或抵达最外层的作用域(也就是全局作用域)为止;

例:

function foo(a) {
  console.log(a + b); // 4
}
var b = 2;
foo(2);
1
2
3
4
5

遍历嵌套作用域链的规则很简单,引擎首先从当前的执行作用域(foo函数体内)开始查找变量 b,如果找不到,就向上一级继续查找,当抵达最外层的全局作用域(全局作用域)时,发现有变量 b 。查找过程停止。

上次编辑于: 2023年7月4日 09:36