理解JavaScript中的作用域和作用域链

作用域和作用域连是JavaScript和其他编程语言的基本概念。然而,这些概念使许多新的JavaScript开发人员感到困惑。掌握JavaScript时,了解这些概念至关重要。

什么是作用域?

JavaScript中的作用域是指变量的可访问性。也就是说,程序的哪些部分可以访问变量。

为什么作用域如此重要呢?

  1. 作用域的主要好处是安全性。也就是说,可以仅从程序的某个区域访问变量。使用作用域,我们可以避免从程序的其他部分无意中修改变量。
  2. 作用域还减少了命名空间冲突。也就是说,我们可以在不同的作用域内使用相同的变量名。

作用域的种类

JavaScript中有三种类型的作用域 - 1)全局作用域,2)功能作用域,以及3)块作用域。

  1. 全局作用域
    任何不在任何函数或块(一对花括号)内的变量都在全局范围内。可以从程序中的任何位置访问全局范围中的变量。例如:
var greeting = 'Hello World!';
function greet() {
  console.log(greeting);
}
// Prints 'Hello World!'
greet();
  1. 函数作用域
    在函数内声明的变量在函数作用域内。它们只能在该函数内访问,这意味着无法从外部代码访问它们。例如:
function greet() {
  var greeting = 'Hello World!';
  console.log(greeting);
}
// Prints 'Hello World!'
greet();
// Uncaught ReferenceError: greeting is not defined
console.log(greeting);
  1. 块级作用域
    ES6引入了let和const变量,与var变量不同,它们可以作用于最近的花括号对。这意味着,不能从那对花括号外面访问它们。例如:
{
  let greeting = 'Hello World!';
  var lang = 'English';
  console.log(greeting); // Prints 'Hello World!'
}
// Prints 'English'
console.log(lang);
// Uncaught ReferenceError: greeting is not defined
console.log(greeting);

我们可以看到var变量可以在块外部使用,也就是说,var变量不受块作用域的限制。

嵌套作用域

就像JavaScript中的函数一样,作用域可以嵌套在另一个作用域内。例如:

var name = 'Peter';
function greet() {
  var greeting = 'Hello';
  {
    let lang = 'English';
    console.log(`${lang}: ${greeting} ${name}`);
  }
}
greet();

这里我们有3个范围嵌套在一起。首先,块作用域(由于let变量而创建)嵌套在本地或函数作用域内,而本地或函数作用域又嵌套在全局作用域内。

词法范围 (Lexical Scope)

词法范围(也称为Static Scope)字面意思是在编译时确定的(通常称为编译)而不是在运行时确定的。例如:

let number = 42;
function printNumber() {
  console.log(number);
}
function log() {
  let number = 54;
  printNumber();
}
// Prints 42
log();

这里,无论调用函数printNumber()的位置在哪里,console.log(number)都将始终打印42。这与具有动态范围(dynamic scope)的语言不同,其中console.log(number)将根据调用函数printNumber()的位置打印不同的值。

如果上面的代码是用支持动态作用域的语言编写的,则console.log(number)将打印54

使用词法范围,我们可以通过查看源代码来确定变量的范围。而在动态范围的情况下,在执行代码之前无法确定范围。

大多数编程语言都支持词法或静态范围,例如C,C ++,Java,JavaScript。

Perl可以同时支持静态和动态范围。

作用域链

当在JavaScript中使用变量时,JavaScript引擎将尝试在当前作用域中查找变量的值。如果它找不到变量,它将查看外部作用域,并将继续这样做,直到找到变量或达到全局作用域。

如果它仍然无法找到变量,它将隐式声明全局作用域内的变量(如果不是在严格模式下)或返回错误。

举例:

let foo = 'foo';
function bar() {
  let baz = 'baz';
  // Prints 'baz'
  console.log(baz);
  // Prints 'foo'
  console.log(foo);
  number = 42;
  console.log(number);  // Prints 42
}
bar();

当执行函数bar()时,JavaScript引擎会查找baz变量并在当前范围内找到它。接下来,它在当前范围中查找foo变量,并且在那里找不到它,因此它在外部范围中查找变量并在那里找到它(即全局范围)。

然后,我们将42分配给number变量,因此JavaScript引擎在当前作用域中查找number变量,然后在外部作用域中查找。

如果脚本不是严格模式,引擎将创建一个名为number的新变量并为其分配42或返回错误(如果是严格模式)。

作用域和作用域链如何工作?

到目前为止,我们已经讨论了范围和范围的类型。现在让我们了解JavaScript引擎如何确定变量的范围并在底层执行变量查找。

为了理解JavaScript引擎如何执行变量查找,我们必须理解JavaScript中词法环境的概念。

什么是词法环境(Lexical Environment)?

词汇环境是一种包含标识符变量映射的结构。 (此处标识符是指变量/函数的名称,变量是对实际对象[包括函数对象和数组对象]或原始值的引用)。

简而言之,词汇环境是存储变量和对象引用的地方。

注 - 不要将词法范围与词法环境混淆,词汇范围是在编译时确定的范围,词汇环境是在程序执行期间存储变量的位置。

从概念上讲,词汇环境如下所示:

lexicalEnvironment = {
  a: 25,
  obj: <ref. to the object>
}

为每个词法范围创建一个新的词法环境,但仅在执行该范围内的代码时。词汇环境也提到了它的外部词汇环境(即外部范围)。例如:

lexicalEnvironment = {
  a: 25,
  obj: <ref. to the object>
  outer: <outer lexical environemt>
}

JavaScript引擎如何执行变量查找?

现在我们知道范围,范围链和词汇环境是什么,让我们现在了解JavaScript引擎如何使用词法环境来确定范围和范围链。

让我们看一下下面的代码片段来理解上面的概念。

let greeting = 'Hello';
function greet() {
  let name = 'Peter';
  console.log(`${greeting} ${name}`);
}
greet();
{
  let greeting = 'Hello World!'
  console.log(greeting);
}

加载上述脚本时,会创建一个全局词法环境,其中包含全局范围内定义的变量和函数。例如:

globalLexicalEnvironment = {
  greeting: 'Hello'
  greet: <ref. to greet function>
  outer: <null>
}

这里外部词法环境设置为null,因为在全局范围之后没有外部范围。

之后,遇到对greet()函数的调用。因此,为greet()函数创建了一个新的词法环境。例如:

functionLexicalEnvironment = {
  name: 'Peter'
  outer: <globalLexicalEnvironment>
}

里外部词法环境设置为globalLexicalEnvironment,因为它的外部范围是全局范围。 之后,JavaScript引擎执行console.log($ {greeting} $ {name}语句。

JavaScript引擎尝试在函数的词法环境中查找greetingname变量。它在当前词法环境中找到name变量,但它无法在当前词法环境中找到greeting变量。

因此它查看外部词汇环境(由外部属性定义,即全局词汇环境)并找到greeting变量。

接下来,JavaScript引擎在块内的代码处执行。因此它为该块创建了一个新的词法环境。例如:

blockLexicalEnvironment = {
  greeting: 'Hello World',
  outer: <globalLexicalEnvironment>
}

接下来,执行console.log(greeting)语句,JavaScript引擎在当前词法环境中查找变量并使用该变量。因此,它不会查看变量的外部词汇环境(全局词汇环境)。

注 - 只为let和const声明创建一个新的词法环境,而不是var声明。 var声明被添加到当前词法环境(全局或函数词法环境)。

因此,当在程序中使用变量时,JavaScript引擎将尝试在当前词法环境中查找变量,如果它在那里找不到变量,它会在外部词汇环境中查找变量。这就是JavaScript引擎执行变量查找的方式。

总结

简而言之,范围是变量可见且可访问的区域。就像函数一样,JavaScript中的作用域可以嵌套,JavaScript引擎遍历作用域链以查找程序中使用的变量。 JavaScript使用词法范围,这意味着变量的范围是在编译时确定的。 JavaScript引擎使用词法环境在程序执行期间存储变量。

翻译不易,领红包,小赞赏一下吧

原文

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值