js作用域链的理解

320 阅读5分钟

作用域

在JavaScript中,作用域(Scope)是指变量的可访问性范围。JavaScript中的作用域可以分为全局作用域函数作用域和块级作用域。

  1. 全局作用域

全局作用域是指在任何函数外部定义的变量,它们可以被整个程序访问。在浏览器中,全局作用域是指在全局对象window中定义的变量,可以通过window对象来访问它们。在Node.js中,全局作用域是指在模块顶层定义的变量,可以通过global对象来访问它们。

在全局作用域中定义的变量可以被任何函数访问,但是如果在函数中定义了同名的变量,函数中的变量会覆盖全局变量,因为JavaScript中的变量是通过作用域链来查找的,它会先在当前函数的作用域中查找变量,如果找不到,就会沿着作用域链往上一级作用域中查找,直到找到为止。如果在全局作用域中也找不到变量,就会抛出ReferenceError错误。

  1. 函数作用域

函数作用域是指在函数内部定义的变量,它们只能在函数内部被访问。在函数作用域中定义的变量也遵循作用域链的查找规则,它们可以访问上一级作用域中的变量,但是上一级作用域不能访问函数作用域中的变量。

  1. 块级作用域

在ES6之前,JavaScript中没有块级作用域,但是可以通过函数作用域来模拟块级作用域。例如,在一个if语句中定义的变量在语句执行完后仍然可以访问到,因为JavaScript中的if语句不会创建一个新的作用域,但是可以通过定义一个立即执行函数来创建一个新的函数作用域,从而实现块级作用域。

在ES6中,引入了let和const关键字来定义块级作用域中的变量。在块级作用域中定义的变量只能在当前块中被访问,不能在块外访问。

词法作用域

词法作用域(Lexical Scope),也叫静态作用域,是指变量的作用域是在代码编写阶段就确定好了的,而不是在代码运行阶段确定的。JavaScript就是一种基于词法作用域的语言。

在JavaScript中,函数的作用域由函数定义的位置决定,而不是函数调用的位置决定。例如:

var name = "Alice";

function sayHello() {
  console.log("Hello, " + name + "!");
}

function sayGoodbye() {
  var name = "Bob";
  sayHello();
  console.log("Goodbye, " + name + "!");
}

sayHello();     // 输出: Hello, Alice!
sayGoodbye();   // 输出: Hello, Alice! Goodbye, Bob!

在上面的例子中,sayGoodbye函数中定义了一个名为name的局部变量,并调用了sayHello函数。由于JavaScript采用词法作用域,sayHello函数中的name变量在定义时就已经指向全局变量name,而不是sayGoodbye函数中的局部变量name。因此,调用sayGoodbye函数时,会输出"Hello, Alice! Goodbye, Bob!"。

词法作用域的优点在于,它可以使变量的作用域更加清晰、可控。开发者可以通过在代码编写阶段确定变量的作用域,避免因为变量作用域的不确定性而引起的问题。此外,词法作用域也可以提高代码的可读性和可维护性。

需要注意的是,词法作用域只是JavaScript作用域规则的一部分。JavaScript中还有其他的作用域规则,例如with语句、eval函数等,它们会影响变量的作用域。因此,在使用这些语言特性时需要格外小心,避免引入不必要的风险。

作用域链

JavaScript中,每个函数都有一个作用域(Scope),函数内部声明的变量和函数外部声明的变量不会互相影响。作用域链(Scope Chain)指的是JavaScript中作用域的嵌套关系,由当前函数的变量对象和所有父级函数的变量对象构成。

当访问一个变量时,JavaScript引擎会首先搜索当前函数的变量对象,如果找不到,则继续向上查找父级函数的变量对象,直到找到该变量或查找到全局作用域为止。这个查找的过程就是作用域链的实现。

以下是一个例子:

var a = 1;

function outer() {
  var b = 2;

  function inner() {
    var c = 3;
    console.log(a + b + c);
  }

  inner();
}

outer(); // 输出: 6

在这个例子中,变量a定义在全局作用域中,函数outer和函数inner分别在outer的作用域和inner的作用域中定义了变量b和变量c。当执行inner函数时,由于变量c在当前函数的作用域中已经定义,因此直接查找变量c即可。变量b在inner函数的作用域链中找不到,于是继续向上查找outer函数的作用域,找到了变量b的定义,得到b的值为2。变量a在inner函数的作用域链中也找不到,于是继续向上查找全局作用域,找到了变量a的定义,得到a的值为1。最终,将a、b和c的值相加,得到6,并输出结果。

作用域链的实现使得JavaScript中可以使用闭包等高级特性,但也会带来性能问题。由于作用域链的查找过程需要遍历整个作用域链,因此访问局部变量的速度比访问全局变量的速度慢。因此,在编写JavaScript代码时,应该尽可能减少作用域链的查找次数,避免影响程序性能。

推荐文章

前端面试题的什么是作用域链?