作用域和作用域连是JavaScript和其他编程语言的基本概念。然而,这些概念使许多新的JavaScript开发人员感到困惑。掌握JavaScript时,了解这些概念至关重要。
什么是作用域?
JavaScript中的作用域是指变量的可访问性。也就是说,程序的哪些部分可以访问变量。
为什么作用域如此重要呢?
- 作用域的主要好处是安全性。也就是说,可以仅从程序的某个区域访问变量。使用作用域,我们可以避免从程序的其他部分无意中修改变量。
- 作用域还减少了命名空间冲突。也就是说,我们可以在不同的作用域内使用相同的变量名。
作用域的种类
JavaScript中有三种类型的作用域 - 1)全局作用域,2)功能作用域,以及3)块作用域。
- 全局作用域
任何不在任何函数或块(一对花括号)内的变量都在全局范围内。可以从程序中的任何位置访问全局范围中的变量。例如:
var greeting = 'Hello World!';
function greet() {
console.log(greeting);
}
// Prints 'Hello World!'
greet();
- 函数作用域
在函数内声明的变量在函数作用域内。它们只能在该函数内访问,这意味着无法从外部代码访问它们。例如:
function greet() {
var greeting = 'Hello World!';
console.log(greeting);
}
// Prints 'Hello World!'
greet();
// Uncaught ReferenceError: greeting is not defined
console.log(greeting);
- 块级作用域
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引擎尝试在函数的词法环境中查找greeting
和name
变量。它在当前词法环境中找到name
变量,但它无法在当前词法环境中找到greeting变量。
因此它查看外部词汇环境(由外部属性定义,即全局词汇环境)并找到greeting
变量。
接下来,JavaScript引擎在块内的代码处执行。因此它为该块创建了一个新的词法环境。例如:
blockLexicalEnvironment = {
greeting: 'Hello World',
outer: <globalLexicalEnvironment>
}
接下来,执行console.log(greeting)
语句,JavaScript引擎在当前词法环境中查找变量并使用该变量。因此,它不会查看变量的外部词汇环境(全局词汇环境)。
注 - 只为let和const声明创建一个新的词法环境,而不是var声明。 var声明被添加到当前词法环境(全局或函数词法环境)。
因此,当在程序中使用变量时,JavaScript引擎将尝试在当前词法环境中查找变量,如果它在那里找不到变量,它会在外部词汇环境中查找变量。这就是JavaScript引擎执行变量查找的方式。
总结
简而言之,范围是变量可见且可访问的区域。就像函数一样,JavaScript中的作用域可以嵌套,JavaScript引擎遍历作用域链以查找程序中使用的变量。 JavaScript使用词法范围,这意味着变量的范围是在编译时确定的。 JavaScript引擎使用词法环境在程序执行期间存储变量。
翻译不易,领红包,小赞赏一下吧