当前位置: 首页 > news >正文

深入理解作用域【JavaScript】

一、作用域的内部原理

JavaScript 的作用域机制是理解变量如何被访问和存储的重要概念。下面详细介绍作用域的内部原理,包括编译、执行、查询、嵌套和异常处理这五个步骤。

1. 编译

在 JavaScript 的执行过程中,首要的步骤是编译。尽管JavaScript是解释性语言,但现代 JavaScript 引擎(如 V8)在执行代码前会先进行编译。编译阶段主要完成以下几项工作:

  • 词法分析:将源代码分解成语法单元(tokens)。
  • 语法分析:将 tokens 结构化为抽象语法树(AST)。
  • 作用域分析:在这一步,编译器会确定每个变量和函数的作用域位置,并建立作用域链。这里会生成一个环境记录(environment record),用于存储作用域内的变量信息。

编译阶段的结果为代码的执行做好准备,包括确定变量的可访问性和生命周期。

通过一个完整的实例来说明 JavaScript 的编译过程。假设我们有以下 JavaScript 代码:

let x = 5;
function add(a, b) {return a + b;
}let result = add(x, 3);
console.log(result);  // 输出结果:8

 1.1 编译阶段 

1.1.1 词法分析(Lexical Analysis)

词法分析
在这一步,源代码被分解成语法单元(tokens)。每个元素(关键字、标识符、常量等)被识别并分类。

示例代码的 tokens

  • let
  • x
  • =
  • 5
  • ;
  • function
  • add
  • (
  • a
  • ,
  • b
  • )
  • {
  • return
  • a
  • +
  • b
  • }
  • ;
  • let
  • result
  • =
  • add
  • (
  • x
  • ,
  • 3
  • )
  • ;
  • console
  • .
  • log
  • (
  • result
  • )
  • ;

1.1.2 语法分析(Syntax Analysis)

语法分析
在这一步,tokens 被组织成一个抽象语法树(AST)。AST 是代码的层次化表示,能更清晰地反映出程序的结构。

示例代码的 AST 简化表示

Program
├── VariableDeclaration (x)
│   └── Literal (5)
├── FunctionDeclaration (add)
│   ├── Identifier (a)
│   ├── Identifier (b)
│   └── BlockStatement
│       └── ReturnStatement
│           └── BinaryExpression (+)
│               ├── Identifier (a)
│               └── Identifier (b)
└── VariableDeclaration (result)└── CallExpression (add)├── Identifier (x)└── Literal (3)
└── ExpressionStatement (console.log)└── Identifier (result)

1.1.3 作用域分析(Scope Analysis)

作用域分析
在这一步,编译器会确定变量和函数的作用域,并建立作用域链。编译器会生成环境记录(environment record),记录每个变量的作用域信息。

示例代码的环境记录

Global Environment Record
├── x: 5
├── add: Function
│   ├── Parameters:  
│   │   ├── a
│   │   └── b
│   └── Scope: add's local scope
└── result: add(x, 3)

作用域链

  • 全局作用域包含 x 和 add
  • add 函数的作用域包含参数 a 和 b
  • result 在全局作用域中定义。

2. 执行

编译完成后,进入执行阶段。在此阶段,代码按顺序执行。JavaScript 引擎会根据生成的环境记录,处理变量的声明和赋值。在执行过程中会遵循以下原则:

  • 作用域链:当执行上下文被创建时,会创建一个作用域链,包含当前执行上下文的环境记录和其父级的环境记录,这样就可以实现层级结构,确定变量的作用范围。
  • 变量提升:在执行上下文被创建时,变量和函数的声明会被提升到作用域的顶部,而赋值在执行到对应声明的那一行时才会被应用。

let x = 5;
function add(a, b) {return a + b;
}let result = add(x, 3);
console.log(result);  // 输出结果:8

2.1 代码执行阶段

编译完成后,JavaScript 引擎进入执行阶段。以下是代码的执行过程:

2.1 变量声明

  • let x = 5;:全局作用域中定义变量 x,并赋值为 5
  • let result;:全局作用域中定义变量 result

2.2 函数定义

  • function add(a, b) { return a + b; }:定义函数 add,并在全局作用域中注册。

2.3 函数调用

  • result = add(x, 3);:调用 add 函数,传递参数 x 和 3
  • 在 add 函数内部,a 被赋值为 5b 被赋值为 3
  • 执行 return a + b;,结果为 8
  • result 被赋值为 8

2.4 输出结果

  • console.log(result);:在控制台输出 result,即 8

3. 查询

在执行某行代码时,JavaScript 引擎需要查找变量。查询过程如下:

  • 引擎首先检查当前执行上下文的环境记录是否包含该变量。
  • 如果找不到,查找上一级的环境记录,依此类推,直到找到该变量或到达全局上下文。
  • 如果最终仍未找到,则会抛出 ReferenceError

这种查找机制也解释了“光照作用域”的概念,即局部变量优先于全局变量

在 JavaScript 中,LHS(Left-Hand Side)查询和 RHS(Right-Hand Side)查询是两种不同的变量查找方式。

3.1 LHS 查询

LHS 查询发生在变量被赋值的时候。换句话说,当引擎在执行代码时需要查找一个变量的位置以便对其进行赋值操作,这时的查找就是 LHS 查询。

let a = 2;

在这个例子中,引擎需要找到变量 a 以便对其赋值 2。这个查找过程就是 LHS 查询。

LHS 查询的目的是找到变量容器本身,以便对其进行赋值

3.2 RHS 查询

RHS 查询发生在引擎需要获取变量的值时。换句话说,当引擎在执行代码时需要获取变量的值,这时的查找就是 RHS 查询。

console.log(a);

在这个例子中,引擎需要找到变量 a 的值以便将其传递给 console.log 函数。这个查找过程就是 RHS 查询。

RHS 查询的目的是获取变量的当前值

3.3 区别总结

  • LHS 查询:目的是找到变量容器本身,以便对其进行赋值
  • RHS 查询:目的是获取变量的当前值

3.4 示例说明

考虑以下代码:

function foo(a) {console.log(a);
}foo(2);

在这个代码片段中,发生了以下操作:

  1. 函数调用 foo(2)

    • 这里发生了 RHS 查询,查找 foo 函数的定义。
    • 然后是 LHS 查询,查找 a 以便对 a 赋值 2
  2. console.log(a)

    • 这里发生了 RHS 查询,查找 console.log 函数的定义。
    • 然后再次发生 RHS 查询,查找 a 的值,以便将其传递给 console.log 函数。

4. 嵌套(重要!)

JavaScript 支持嵌套的函数定义。每当一个函数被调用时,都会创建一个新的执行上下文。嵌套函数会形成新的作用域链,从而影响变量的可访问性。

  • 闭包:嵌套函数可以访问外部函数的变量,这形成了“闭包”。闭包允许内部函数保持对其外部环境的引用,即使外部函数已经结束执行,内部函数仍然有权限访问外部函数的变量。

这种嵌套和闭包的机制使得 JavaScript 函数可以有效地封装状态,形成私有变量。

 4.1 作用域变量的查找机制:

在 JavaScript 中,作用域变量的查找机制遵循一种递归式的查找过程,称为“作用域链查找”。当在当前作用域中无法找到某个变量时,引擎会在外层嵌套的作用域中继续查找,直到找到该变量,或者是抵达最外层的作用域(全局作用域)为止。如果在全局作用域中仍然无法找到该变量,引擎会抛出一个 ReferenceError(引用错误)

4.1.1 详细过程

1、当前作用域(Local Scope)

  • 当引擎遇到一个变量时,首先会在当前的执行上下文中查找该变量。
  • 如果找到,则会停止查找并使用该变量的值。

2、外部作用域(Outer Scope)

  • 如果在当前作用域中未找到该变量,引擎会进入外层嵌套的作用域继续查找。
  • 这个过程会一直持续,直到找到该变量或者到达全局作用域。

3、全局作用域(Global Scope)

  • 如果在外层作用域中仍然未找到该变量,引擎最终会到达全局作用域进行查找。
  • 如果此时仍未找到,引擎会抛出一个 ReferenceError,表示该变量未定义。

4.1.2 作用域链

作用域链是一个链式结构,每个执行上下文都有一个指向外部作用域的引用。这个链式结构使得引擎能够按照特定的顺序查找变量。

  • 创建作用域链

    • 当一个函数被调用时,引擎会创建一个新的执行上下文,并将其推入执行栈中。
    • 这个新的执行上下文会包含一个指向外部作用域的指针,这个指针指向创建该函数的执行上下文(即父级作用域)。
  • 查找过程

    • 引擎会按照作用域链的顺序,从当前作用域开始,逐级向上查找变量。
    • 直到找到该变量或到达作用域链的顶部(全局作用域)。

4.2 示例说明

考虑以下代码:

var globalVar = '全局变量';function outerFunction() {var outerVar = '外部变量';function innerFunction() {var innerVar = '内部变量';console.log(innerVar); // 输出:内部变量console.log(outerVar); // 输出:外部变量console.log(globalVar); // 输出:全局变量console.log(nonExistentVar); // 抛出错误:ReferenceError: nonExistentVar is not defined}innerFunction();
}outerFunction();

在这个例子中:

  1. innerFunction 在当前作用域中查找 innerVar,并找到它。
  2. innerFunction 在当前作用域中找不到 outerVar,因此沿着作用域链查找,在外部作用域 outerFunction 中找到 outerVar
  3. innerFunction 在当前作用域和外部作用域中均找不到 globalVar,继续沿着作用域链查找,在全局作用域中找到 globalVar
  4. innerFunction 在所有作用域中均找不到 nonExistentVar,因此抛出 ReferenceError

4.3 总结

作用域变量的查找机制是一个递归式的过程,通过作用域链逐级向上查找变量,直到找到该变量或到达全局作用域。

5. 异常

JavaScript 的异常处理机制可以影响作用域的访问。使用 try...catch 语句块时,会创建一个新的执行上下文。在这个过程中,异常会影响变量的可访问性和作用范围:

  • 在 try 块中,如果发生异常,控制权转移到 catch 块。
  • 如果错误发生在一个嵌套作用域内,catch 块仍然可以访问外部作用域的变量。
  • 对于未捕获的异常,将会将所有当前的上下文信息清理,并转移到全局作用域。

5.1 示例代码

// 全局作用域
var globalVar = '全局变量';function outerFunction() {// 外部函数作用域var outerVar = '外部变量';try {// try 块作用域var tryVar = 'try 块变量';function innerFunction() {// 内部函数作用域var innerVar = '内部变量';throw new Error('抛出一个错误'); // 抛出一个异常}innerFunction();} catch (error) {// catch 块作用域console.log('捕获的错误:', error.message);console.log('try 块变量:', tryVar); // 访问 try 块中的变量console.log('外部变量:', outerVar); // 访问外部作用域中的变量console.log('全局变量:', globalVar); // 访问全局作用域中的变量console.log('内部变量:', innerVar); // 尝试访问内部作用域中的变量,将抛出 ReferenceError}
}outerFunction();

5.2 代码分析

全局作用域

  • 定义了一个全局变量 globalVar

外部函数 outerFunction

  • 定义了一个局部变量 outerVar
  • 使用 try...catch 语句块来处理可能发生的异常。

try 块作用域

  • 在 try 块中定义了一个局部变量 tryVar
  • 定义了一个内部函数 innerFunction

内部函数 innerFunction

  • 定义了一个局部变量 innerVar
  • 抛出一个错误 throw new Error('抛出一个错误')

catch 块作用域

  • 捕获 try 块中抛出的异常,并输出错误信息。
  • 访问 try 块中的变量 tryVar,输出 “try 块变量:”。
  • 访问外部作用域中的变量 outerVar,输出 “外部变量:”。
  • 访问全局作用域中的变量 globalVar,输出 “全局变量:”。
  • 尝试访问 innerFunction 中的变量 innerVar,由于 innerVar 在 catch 块的作用域链之外,抛出 ReferenceError

二、遮蔽效应

1. 遮蔽效应

(或者称为“变量遮蔽”)是指在 JavaScript 中,当一个变量在内层作用域中被声明时,它会遮蔽(或隐藏)同名的外层作用域中的变量。这意味着内层作用域中的同名变量会覆盖外层作用域中的变量,外层变量在该内层作用域中将不可见。

1.1 举例说明

下面是一个示例,展示了遮蔽效应的行为。

// 外层作用域
var name = 'LuQian';function greet() {// 内层作用域var name = 'Bob'; // 这个声明将遮蔽外层的 name 变量console.log('Hello, ' + name); // 输出: "Hello, Bob"
}greet(); // 调用 greet 函数
console.log('Global name: ' + name); // 输出: "Global name: LuQian"
1.2 代码分析

外层作用域

  • 在全局范围(外层作用域)中,声明了一个变量 name,值为 'Alice'

内层作用域

  • 在 greet 函数内,声明了一个同名变量 name,值为 'Bob'。这个变量遮蔽了外层作用域中的 name

输出

  • 当调用 greet 函数时,console.log('Hello, ' + name) 访问的是 greet 函数内的 name 变量,输出为 Hello, Bob
  • 当控制流返回到全局作用域,console.log('Global name: ' + name) 显示的是外层作用域的 name 变量,输出为 Global name: Alice

2. 注意事项

2.1 遮蔽只在当前作用域有效
  • 变量遮蔽只是在声明变量的那一层作用域有效。如果内层作用域不声明同名变量,则会查找外层作用域的变量。
2.2 遮蔽的层级
  • 如果在内层作用域再次声明一个同名变量,则会遮蔽它的外层同名变量。例如,如果在 greet 函数内部再有一个嵌套函数,并且在嵌套函数内再次声明 name,则嵌套函数将覆盖 greet 函数内的 name 变量。
2.3 示例代码(更复杂的遮蔽例子)
var name = 'LuQian';function outerFunction() {var name = 'Bob';function innerFunction() {var name = 'Charlie'; // 遮蔽了 outerFunction 的 nameconsole.log('Inner name: ' + name); // 输出: "Inner name: Charlie"}innerFunction();console.log('Outer name: ' + name); // 输出: "Outer name: Bob"
}outerFunction();
console.log('Global name: ' + name); // 输出: "Global name: LuQian"

3. 总结

遮蔽效应是 JavaScript 中作用域链和变量查找的一部分。理解这一概念可以帮助开发者更清晰地控制变量的访问以及避免不必要的错误。在编写函数和作用域相关代码时,务必注意同名变量的作用域,以确保代码的可读性和逻辑的准确性。

三、变量声明提升

1. 变量声明提升

在 JavaScript 中,变量声明提升(Hoisting)是指在代码执行之前,JavaScript 引擎会将所有变量声明和函数声明提升到其所在作用域的顶部。这意味着无论变量或函数在何处声明,它们都会被移动到作用域的开始处,但变量的初始化并不会被提升。

2. 变量声明提升的详细说明

2.1 变量声明提升

  • 使用 var 声明的变量会被提升到其所在函数的顶部,但其初始化值不会被提升。
  • 使用 let 和 const 声明的变量也会被提升,但它们会被初始化为 undefined 状态,直到它们被实际声明为止。这个状态称为“暂时性死区”(Temporal Dead Zone, TDZ)。

2.2 函数声明提升

  • 函数声明也会被提升到其所在作用域的顶部。这意味着可以在函数声明之前调用该函数。

2.3 示例代码

以下是几个示例,展示了不同类型的变量声明提升行为。

// 示例 1: 变量声明提升 (var)
console.log(x); // 输出: undefined
var x = 10;
console.log(x); // 输出: 10// 示例 2: 变量声明提升 (let)
// console.log(y); // 抛出 ReferenceError,因为 y 处于暂时性死区
let y = 20;
console.log(y); // 输出: 20// 示例 3: 变量声明提升 (const)
// console.log(z); // 抛出 ReferenceError,因为 z 处于暂时性死区
const z = 30;
console.log(z); // 输出: 30// 示例 4: 函数声明提升
foo(); // 输出: "Hello, I am foo!"
function foo() {console.log("Hello, I am foo!");
}// 示例 5: 函数表达式不提升
// bar(); // 抛出 TypeError,因为 bar 是 undefined
var bar = function() {console.log("Hello, I am bar!");
};
bar(); // 输出: "Hello, I am bar!"

2.4 代码分析

示例 1: 变量声明提升 (var)

  • var x = 10; 被解释为 var x; 和 x = 10;
  • var x; 被提升到作用域顶部,因此 console.log(x) 输出 undefined
  • 赋值操作 x = 10; 在原位置执行,因此 console.log(x) 输出 10

示例 2: 变量声明提升 (let)

  • let y = 20; 被提升到作用域顶部,但在实际声明之前访问 y 会导致 ReferenceError,因为 let 和 const 存在暂时性死区。

示例 3: 变量声明提升 (const)

  • const z = 30; 同样被提升到作用域顶部,但在实际声明之前访问 z 会导致 ReferenceError,因为 const 存在暂时性死区。

示例 4: 函数声明提升

  • 函数声明 function foo() {...} 被提升到作用域顶部,因此可以在声明之前调用 foo()

示例 5: 函数表达式不提升

  • 函数表达式 var bar = function() {...}; 的 var bar 被提升,但 bar 初始化为 undefined,因此在赋值之前调用 bar() 会抛出 TypeError。赋值操作完成后,bar() 可以正常调用。

3. 总结

变量声明提升是 JavaScript 中一个重要的概念,理解它有助于更好地组织代码,避免常见的错误。变量声明提升使得变量和函数声明在作用域内提前可用,但需要注意 let 和 const 的暂时性死区,以避免在实际声明之前访问变量导致的错误。

四、作用域链和自由变量

在 JavaScript 中,作用域链和自由变量是理解变量查找和函数执行上下文的重要概念。它们共同决定了变量在不同作用域中的可见性和访问方式。

1.  作用域链(Scope Chain)

作用域链是在 JavaScript 引擎执行代码时,用于查找变量和函数的机制。作用域链由当前执行上下文的变量对象(Variable Object)和所有父级执行上下文的变量对象组成。

  • 当前执行上下文:在函数执行时,JavaScript 引擎会创建一个执行上下文(Execution Context),其中包括函数的参数、局部变量和函数的内部函数。
  • 变量对象:每个执行上下文都有一个与之关联的变量对象,其中存储了该上下文中的所有变量和函数声明。
  • 作用域链的构成:当前执行上下文的变量对象位于作用域链的顶部,其后是所有父级执行上下文的变量对象,依次排列,直到全局作用域的变量对象。

2. 自由变量(Free Variables)

自由变量是指在函数内部使用,但在函数内部未定义的变量。换句话说,自由变量是在函数外部定义但在函数内部引用的变量。

  • 查找自由变量:当函数内部引用一个自由变量时,JavaScript 引擎会沿着作用域链向上查找,直到找到该变量为止。如果在全局作用域中仍未找到该变量,则会抛出 ReferenceError

3. 示例代码

下面的示例代码展示了作用域链和自由变量的使用:

// 全局作用域
var globalVar = 'Global';function outerFunction() {// 外部函数作用域var outerVar = 'Outer';function innerFunction() {// 内部函数作用域var innerVar = 'Inner';// 访问当前作用域的变量console.log('innerVar:', innerVar); // 输出: "innerVar: Inner"// 访问外部作用域的变量(自由变量)console.log('outerVar:', outerVar); // 输出: "outerVar: Outer"// 访问全局作用域的变量(自由变量)console.log('globalVar:', globalVar); // 输出: "globalVar: Global"}innerFunction();
}outerFunction();

4. 代码分析

全局作用域

  • 定义了全局变量 globalVar,值为 'Global'

外部函数 outerFunction

  • 定义了局部变量 outerVar,值为 'Outer'
  • 定义了内部函数 innerFunction

内部函数 innerFunction

  • 定义了局部变量 innerVar,值为 'Inner'
  • 访问当前作用域的变量 innerVar,输出 "innerVar: Inner"
  • 访问外部作用域的变量 outerVar(自由变量),输出 "outerVar: Outer"
  • 访问全局作用域的变量 globalVar(自由变量),输出 "globalVar: Global"

5. 详细过程

5.1 作用域链

  • 当 innerFunction 执行时,其作用域链由当前执行上下文的变量对象和所有父级执行上下文的变量对象组成。具体顺序为:innerFunction 的变量对象 -> outerFunction 的变量对象 -> 全局变量对象。

5.2 自由变量查找

  • 当 innerFunction 内部访问 outerVar 时,JavaScript 引擎首先在 innerFunction 的变量对象中查找,未找到。接着在 outerFunction 的变量对象中查找,找到并输出 "outerVar: Outer"
  • 当 innerFunction 内部访问 globalVar 时,JavaScript 引擎首先在 innerFunction 的变量对象中查找,未找到。接着在 outerFunction 的变量对象中查找,未找到。最后在全局变量对象中查找,找到并输出 "globalVar: Global"

6. 总结

作用域链和自由变量是 JavaScript 中重要的概念,用于管理变量的可见性和访问方式。理解作用域链和自由变量有助于编写更清晰、更高效的代码,并避免常见的变量查找错误。

五、 执行的上下文环境、执行环境和执行流、执行环境栈

在 JavaScript 中,理解和掌握执行上下文环境、执行环境和执行流、执行环境栈是深入理解语言运行机制的关键。这些概念共同构成了 JavaScript 代码执行的核心机制。

1. 执行上下文环境(Execution Context)

执行上下文环境是 JavaScript 代码执行的上下文,定义了代码执行时的作用域、变量、对象、函数以及 this 的绑定。每个执行上下文环境包含以下三个组成部分:

  • 变量对象(Variable Object, VO):存储了当前上下文中的所有变量、函数声明和函数参数。
  • 作用域链(Scope Chain):由当前执行上下文的变量对象和所有父级执行上下文的变量对象组成,用于查找变量和函数。
  • this 绑定:确定当前执行上下文中的 this 值。

执行上下文可以分为以下几种类型:

  • 全局执行上下文:JavaScript 代码首次执行时创建的全局执行上下文。它是作用域链的最外层,包含全局变量和全局对象(如 window 或 global)。
  • 函数执行上下文:每当调用一个函数时,会创建一个新的函数执行上下文。函数执行上下文包含函数的局部变量、参数和内部函数。
  • eval 执行上下文:在 eval 函数内部执行的代码所创建的执行上下文。

2. 执行环境和执行流

  • 执行环境(Execution Environment):执行环境是指代码在特定上下文中的执行状态。它包括当前的执行上下文、作用域链、this 绑定等。
  • 执行流(Execution Flow):执行流是指代码在执行过程中的控制流程。它包括顺序执行、函数调用、条件分支、循环、异常处理等。

3. 执行环境栈(Execution Context Stack)

执行环境栈(也称为调用栈,Call Stack)是 JavaScript 引擎用于管理执行上下文的数据结构。执行环境栈遵循后进先出(LIFO)的原则。每当调用一个函数时,引擎会将该函数的执行上下文推入栈顶;当函数执行完毕后,引擎会将该执行上下文从栈顶移除。

  • 栈底:全局执行上下文始终位于栈底。
  • 栈顶:当前正在执行的执行上下文位于栈顶。

4. 示例代码

下面的示例代码展示了执行上下文环境、执行环境和执行流、执行环境栈的运行机制:

// 全局执行上下文
var globalVar = 'Global';function outerFunction(outerParam) {// 外部函数执行上下文var outerVar = 'Outer';function innerFunction(innerParam) {// 内部函数执行上下文var innerVar = 'Inner';console.log('innerVar:', innerVar); // 输出: "innerVar: Inner"console.log('outerVar:', outerVar); // 输出: "outerVar: Outer"console.log('globalVar:', globalVar); // 输出: "globalVar: Global"console.log('outerParam:', outerParam); // 输出: "outerParam: OuterParam"console.log('innerParam:', innerParam); // 输出: "innerParam: InnerParam"}innerFunction('InnerParam');
}outerFunction('OuterParam');

5. 代码分析

全局执行上下文

  • 定义了全局变量 globalVar,值为 'Global'
  • 定义了函数 outerFunction

调用 outerFunction

  • 创建 outerFunction 的执行上下文,推入执行环境栈。
  • 定义局部变量 outerVar,值为 'Outer'
  • 定义函数 innerFunction

调用 innerFunction

  • 创建 innerFunction 的执行上下文,推入执行环境栈。
  • 定义局部变量 innerVar,值为 'Inner'
  • 访问当前作用域的变量 innerVar,输出 "innerVar: Inner"
  • 访问外部作用域的变量 outerVar(自由变量),输出 "outerVar: Outer"
  • 访问全局作用域的变量 globalVar(自由变量),输出 "globalVar: Global"
  • 访问外部函数参数 outerParam(自由变量),输出 "outerParam: OuterParam"
  • 访问当前函数参数 innerParam,输出 "innerParam: InnerParam"

innerFunction 执行完毕

  • innerFunction 的执行上下文从执行环境栈中移除。
  • 返回到 outerFunction 的执行上下文。

outerFunction 执行完毕

  • outerFunction 的执行上下文从执行环境栈中移除。
  • 返回到全局执行上下文。

6. 执行环境栈的变化

初始状态

  • 栈底:全局执行上下文。

调用 outerFunction

  • 栈顶:outerFunction 的执行上下文。
  • 栈底:全局执行上下文。

调用 innerFunction

  • 栈顶:innerFunction 的执行上下文。
  • 中间:outerFunction 的执行上下文。
  • 栈底:全局执行上下文。

innerFunction 执行完毕

  • 栈顶:outerFunction 的执行上下文。
  • 栈底:全局执行上下文。

outerFunction 执行完毕

  • 栈顶:全局执行上下文。

7. 总结

执行上下文环境、执行环境和执行流、执行环境栈是 JavaScript 引擎在执行代码时的核心机制。理解这些概念有助于更好地理解代码的执行顺序、作用域链、变量查找和函数调用过程。掌握这些知识可以帮助开发者编写更高效、更清晰的代码,并避免常见的执行上下文相关错误。

六、 作用域的类型

在 JavaScript 中,作用域主要分为以下几种类型:

  • 全局作用域
  • 函数作用域
  • 块作用域
  • 隐式作用域(或称为闭包)

1. 全局作用域

全局作用域是最高的作用域,它里的变量和函数可以在代码的任何位置访问。全局变量是由任何函数、对象、或者块中都可以访问的变量。

var globalVar = 'I am global';function testGlobal() {console.log(globalVar); // 输出: "I am global"
}testGlobal();
console.log(globalVar); // 输出: "I am global"

在上面的示例中,globalVar 是一个全局变量,可以在函数和全局代码中访问。

2. 函数作用域

函数作用域是指在函数内部定义的变量,只能在该函数内部访问。每当创建一个函数时,JavaScript 会为该函数创建一个新的作用域。

function testFunctionScope() {var functionVar = 'I am local to this function';console.log(functionVar); // 输出: "I am local to this function"
}testFunctionScope();
console.log(functionVar); // 抛出 ReferenceError: functionVar is not defined

在这个例子中,functionVar 是被定义在 testFunctionScope 函数内部的,外部无法访问。

3. 块作用域

块作用域是在 ES6 引入的一个新特性,使用 let 和 const 关键字声明的变量具有块级作用域。这意味着在诸如 ifforwhile 等控制结构或代码块中声明的变量只在该块内部有效。

if (true) {let blockVar = 'I am block scoped';console.log(blockVar); // 输出: "I am block scoped"
}console.log(blockVar); // 抛出 ReferenceError: blockVar is not defined

在这个例子中,blockVar 只在 if 语句的块内部有效,外部无法访问。

4. 作用域链

作用域链是 JavaScript 用于查找变量的一种机制。它是一系列的作用域,从最内层到最外层的作用域形成一个链条。当 JavaScript 引擎要查找一个变量时,它首先在当前作用域中查找,如果未找到,则查找外层作用域,直到全局作用域。

var globalVar = 'Global';function outer() {var outerVar = 'Outer';function inner() {var innerVar = 'Inner';console.log(outerVar); // 输出: "Outer"console.log(globalVar); // 输出: "Global"}inner();
}outer();

在上述代码中,inner 函数可以访问其外部函数 outer 的变量 outerVar,以及全局变量 globalVar。这就是作用域链的工作原理。

5. 闭包

闭包是 JavaScript 中的一个重要概念,它是指一个函数可以“记住”并访问其外部作用域的变量,即使该函数是在其外部执行的。闭包允许你将数据封装在一个特定范围中,防止外部直接访问。

function makeCounter() {let count = 0; // count 变量在这里被创建return function() {count++; // 可以访问外部变量 countreturn count;};
}const counter = makeCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2
console.log(counter()); // 输出: 3

在这个示例中,返回的函数在其调用时仍然能够访问 makeCounter 函数中的 count 变量,这就是闭包的作用。

6. 作用域的重要性与应用

掌握作用域非常重要,因为它直接影响到变量的生命周期、内存管理和代码的可读性与可维护性。

  1. 避免变量冲突:通过使用局部作用域和块作用域,可以减少全局变量的使用,从而降低变量命名冲突的风险。

  2. 封装和信息隐藏:通过闭包技术,可以封装变量,使外界无法直接访问,从而增强数据的隐私性。

  3. 增加可维护性:好的作用域管理可以使代码逻辑更清晰,从而增加可维护性。

7. 总结

JavaScript 的作用域是一个非常重要且复杂的主题,它由全局作用域、函数作用域和块作用域组成。通过作用域链和闭包的概念,能够有效管理变量的可访问性和生命周期。掌握作用域的机制对于编写高效、可维护的 JavaScript 代码至关重要。

相关文章:

深入理解作用域【JavaScript】

一、作用域的内部原理 JavaScript 的作用域机制是理解变量如何被访问和存储的重要概念。下面详细介绍作用域的内部原理,包括编译、执行、查询、嵌套和异常处理这五个步骤。 1. 编译 在 JavaScript 的执行过程中,首要的步骤是编译。尽管JavaScript是解…...

微信小程序实战教程:如何使用map组件实现地图功能

在微信小程序中,map组件是一个非常实用的功能,它可以帮助我们快速实现地图展示、定位、标注等操作。本文将详细介绍如何在微信小程序中使用map组件,带你轻松掌握地图开发技能。 一、map组件概述 map组件是微信小程序官方提供的一个地图组件…...

张雪峰谈人工智能技术应用专业的就业前景!

一、张雪峰谈人工智能技术应用专业 在教育咨询领域,张雪峰老师以其深入浅出的讲解和前瞻性的视角,为广大学子提供了宝贵的专业选择建议。对于人工智能技术应用专业,张雪峰老师通常给予高度评价,认为这是一个充满无限可能且就业前…...

机器学习课程学习周报十五

机器学习课程学习周报十五 文章目录 机器学习课程学习周报十五摘要Abstract一、机器学习部分1. 统计推断与贝叶斯推断2. GMM和EM算法补充3. 马尔可夫链蒙特卡罗法3.1 蒙特卡罗法3.2 马尔可夫链3.3 Diffusion模型中的马尔可夫链 总结 摘要 本周的学习涵盖了统计推断和贝叶斯推断…...

rabbitMq------客户端模块

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言消费者模块信道管理模块管理的字段提供的接口 信道内存管理连接管理类 前言 在RabbitMQ中,提供服务的是信道,因此在客⼾端的实现中&…...

地理定位营销与开源AI智能名片O2O商城小程序的融合与发展

摘要:本文阐述地理定位营销的概念、手段及其在商业中的应用,探讨开源AI智能名片O2O商城小程序如何与地理定位营销相结合,为企业营销带来新的机遇与挑战。 一、引言 在当今数字化营销的时代,地理定位营销已成为一种重要的营销手段…...

解决Vue应用中遇到路由刷新后出现 404 错误

解释: Vue 应用中遇到路由刷新后出现 404 错误,通常是因为 Vue 应用是个单页应用(SPA),它通过 Vue Router 管理路由,通过 HTML5 History Mode 实现页面导航无需重新加载页面。当直接访问非首页的路由或者刷…...

在window10下使用directml加速phi-3模型的一些记录

1.安装anaconda,安装python 安装torch等参考网上资料非常多 不细描述 2.参考微软官网【在windows上通过DirectML启用Pytorch文档,检查系统版本 检查gpu版本 3.参考微软官网【在windows上通过DirectML启用Pytorch】文档,安装torch_directml模…...

通信工程学习:什么是OSPF开放式最短路径优先

OSPF:开放式最短路径优先 OSPF(Open Shortest Path First,开放式最短路径优先)是一种内部网关协议(IGP),被广泛应用于计算机网络中,特别是在构建大型和复杂的网络时。以下是对OSPF的…...

《中国电子报》报道: 安宝特AR为产线作业者的“秘密武器

近日,中国电子报在其文章《下一代工业智能终端重新定义制造业》中对安宝特的增强现实(AR)解决方案给予了高度评价,称其为产线作业者的“秘密武器”。这一创新技术改变了传统制造业的作业方式,使得操作人员能够在生产过…...

【Android】Handler消息机制

文章目录 前言概述核心组件概述Android消息机制概述 Android消息机制分析ThreadLocal的工作原理ThreadLocal基础ThreadLocal实现原理 MessageQueueLooperHandler的工作原理总结 前言 本文用于记录Android的消息机制,主要是指Handler的运行机制。部分内容参考自《An…...

大数据必懂知识点:Parquet、ORC还是Avro作为数据存储格式,哪种在性能和压缩率上更优

目录 第一章 相关理论 1.1 Parquet格式介绍 1.1.1 起源与发展 1.1.2 特点与优势 1.2 ORC格式介绍 1.3 Avro格式介绍 1.3.1 跨语言支持 1.3.2 动态映射 1.3.3 丰富的数据模式 1.3.4 数据模式灵活性 第二章 种格式性能比较 2.1 读写性能对比 2.2 查询性能对比 2.3 压…...

P1387 最大正方形

题目描述 在一个nm 的只包含 0 和 1 的矩阵里找出一个不包含 0 的最大正方形,输出边长。 输入格式 输入文件第一行为两个整数n,m(1≤n,m≤100),接下来 n 行,每行 m 个数字,用空格隔开,0 或 1。 输出格式 一个整数…...

Python知识点:如何使用Multiprocessing进行并行任务管理

开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! 如何在Python中使用Multiprocessing进行并行任务管理 在现代编程中,…...

React常见优化问题

在React开发中,性能优化是一个重要且持续的过程,旨在提升应用的响应速度和用户体验。以下是一些常见的React优化问题详解,并附上相应的代码示例。 1. 避免不必要的组件渲染 React组件的渲染是由其props或state的变化触发的。但是,…...

css 简单网页布局——浮动(一)

1. 三种布局方式 1.1 标准流 1.2 浮动的使用 1.3 简述浮动 1.3.1 浮动三大特性 <style>.out {border: 1px red solid;width: 1000px;height: 500px;}.one {background-color: aquamarine;width: 200px;height: 100px;}.two {background-color: blueviolet;width: 200px;h…...

设计模式(3)builder

需求&#xff1a; 对于复杂的对象&#xff0c;我们只需要 通过 设置一些参数&#xff0c;就可以得到相对应的 实例。 简单来说&#xff0c; 需求就是用一个类 通过方法返回一个 新建的对象&#xff0c;而且可以通过方法去设置这个对象 public interface CarBuilder {void se…...

Day01-MySQL数据库介绍及部署

Day01-MySQL数据库介绍及部署 1、数据库服务概述介绍1.1 企业中为什么需要数据库&#xff1f;1.2 数据库服务作用1.3 数据库服务分类 2、数据库服务安装部署2.1 数据库版本应用2.2 数据库服务程序下载2.3 数据库软件安装方式2.3.1 二进制安装步骤 3、数据库服务初始化介绍3.1 安…...

分享一个餐饮连锁店点餐系统 餐馆食材采购系统Java、python、php三个版本(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…...

解决跨域问题

第一种 让后端解决 第二种 通过代理来解决 首先可以先搭建后端接口 解决则参照vue-cli官网 首先新建一个vue.config.js文件 然后在项目的根目录新建两个文件夹 开发环境和生产环境 然后可以使用环境变量 系统会自动识别你是生产环境还是开发环境 然后在封装的axios中配…...

面试知识储备-多线程

1.线程的概念 线程使得在一个程序中可以同时执行多个任务。在 Java 应用程序中&#xff0c;多个线程可以同时运行&#xff0c;例如一个线程可以处理用户输入&#xff0c;另一个线程可以进行后台数据处理。 2.创建线程的方式 &#xff08;1&#xff09;重写thread类中的run方法…...

边缘计算插上AI的翅膀会咋样?

人工智能&#xff08;Artificial Intelligence,AI&#xff09;是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学&#xff0c;是新一轮产业革命的重要驱动力量。2022年底发布的ChatGPT将人工智能技术上升到了一个新的高度。如今&#x…...

脉冲神经网络(SNN)论文阅读(六)-----ECCV-2024 脉冲驱动的SNN目标检测框架:SpikeYOLO

原文链接&#xff1a;CSDN-脉冲神经网络&#xff08;SNN&#xff09;论文阅读&#xff08;六&#xff09;-----ECCV-2024 脉冲驱动的SNN目标检测框架&#xff1a;SpikeYOLO Integer-Valued Training and Spike-Driven Inference Spiking Neural Network for High-performance …...

周报_2024/10/6

周报 时间 2024/9/30——2024/10/6 科研进展 写项目标书 实验了不同比例的标签加噪&#xff0c;模型效果随着标签加噪比例增加下降明显 下周计划 构造概念漂移数据集 借鉴其他文章中应对标签加噪的做法...

[深度学习][python]yolov11+bytetrack+pyqt5实现目标追踪

【算法介绍】 YOLOv11、ByteTrack和PyQt5的组合为实现高效目标追踪提供了一个强大的解决方案。 YOLOv11是YOLO系列的最新版本&#xff0c;它在保持高检测速度的同时&#xff0c;通过改进网络结构、优化损失函数等方式&#xff0c;提高了检测精度&#xff0c;能够同时处理多个…...

如何使用ssm实现基于Web的穿戴搭配系统的设计与实现+vue

TOC ssm784基于Web的穿戴搭配系统的设计与实现vue 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在这个新的时代&…...

JavaScript的设计模式

JavaScript设计模式是指在面向对象编程中&#xff0c;通过对类和对象进行抽象和泛化&#xff0c;提取出一些通用的设计思路和解决方案&#xff0c;以解决常见的软件设计问题。这些设计模式可以分为以下几类进行详细介绍&#xff1a; 一、创建型模式 1. 工厂模式&#xff08;F…...

CIKM 2024 | 时空数据(Spatial-temporal)论文总结

CIKM 2024于10月21号-10月25号在美国爱达荷州博伊西举行&#xff08;Boise, Idaho, USA&#xff09; 本文总结了CIKM 2024有关时空数据&#xff08;spatial-temporal data&#xff09;的相关论文&#xff0c;主要包含交通预测&#xff0c;插补&#xff0c;事故预测&#xff0c…...

计算机毕业设计 网上体育商城系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…...

【数据结构】什么是哈希表(散列表)?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f4cc;哈希表的概念 &#x1f4cc;哈希函数的构造方法 &#x1f38f;直接定址法 &#x1f38f;除留余数法 &#x1f38f;平方取中法 &#x1f38f;折叠法 &#x…...