• 作者:老汪软件技巧
  • 发表时间:2024-11-30 17:03
  • 浏览量:

引言

在JavaScript中,理解词法作用域以及作用域链执行上下文、变量提升是掌握这门语言的关键。这些代码深刻影响着代码的执行流程,还决定了变量的和函数的可见性与查找方式。通过深入探讨这些概念,我们可以更好地编写清晰、可维护且高效的代码。

词法作用域与作用域链

词法作用域是指变量的作用域在编写时就已经由其位置决定了,函数可以访问其定义时所在作用域及其外部作用域中的变量。

作用域链是通过一系列嵌套的词法环境形成的链式结构,用于在当前作用域中查找变量,如果找不到则沿着链向上一级作用域继续查找,直到全局作用域。

执行上下文1. 什么是执行上下文

执行上下文(Execution Context)是JavaScript中用于管理代码执行过程中的环境和状态的一个抽象概念。每个执行上下文都包含了一系列的信息,包括变量、函数声明、作用域链以及this的值等。

2. 如何存储执行上下文

具体的执行上下文实例会被放置在一个称为调用栈(Call Stack)的数据结构中。调用栈负责跟踪当前正在执行的函数及其调用顺序。每当一个函数被调用时,其对应的执行上下文就会被推入调用栈;当函数执行完毕后,相应的执行上下文会从调用栈中弹出。

3. 执行上下文的类型

执行上下文有三种类型:全局执行上下文、函数执行上下文、eval函数执行上下文。

全局执行上下文: 这是JavaScript程序启动时创建的第一个执行上下文,它代表了全局作用域。

函数执行上下文: 每当一个函数被调用时,就会为该函数创建一个新的执行上下文。这个上下文包含了函数内部的所有局部变量、参数及其它信息。

Eval执行上下文: 当使用eval()函数执行字符串形式的代码时,会创建一个eval执行上下文。不是很常用

4. 执行上下文的组成

每个执行上下文主要由以下三部分组成:

变量环境: 它是一个记录了在该执行上下文中声明的所有变量和函数的数据结构。

词法环境: 用于存储变量、函数声明及其对应值的数据结构,并维护着对外部环境的引用,从而形成作用域链,决定了代码在执行时如何查找和访问这些标识符。

this关键字: 指定了当前执行上下文中的this所指向的对象。

变量提升(hoisting): 一把隐形的双刃剑

在JavaScript中,变量提升是一种行为,无论你在何时声明一个变量和函数,他都会将它们移动到作用域的顶部,这一过程叫做变量提升

console.log(a);
console.log(func);
console.log(b); // 词法环境中的变量或常量,在申明之前不可访问
// 暂时性死区 TDZ
var a = 1;
function func() {
}
let b = 2;

var a声明会被提升到其作用域的顶部,但赋值不会被提升。因此,在执行console.log(a)时,a的值为undefined。

函数声明func也会被提升,这意味着在执行任何代码之前,func已经是一个可用的函数。

为了避免变量提升对代码可读性的影响,es6引入了let和const,使用let和const声明的变量也会被添加到变量环境中,但是它们不会立即初始化(即处于暂时性死区 TDZ, Temporal Dead Zone)。这意味着在声明之前尝试访问这些变量会导致一个引用错误(ReferenceError)。此外,let和const声明的变量具有块级作用域(block scope),只在声明它们的块内有效。

代码分析1. 词法作用域与作用域链

function foo() {
    var a = 1;
    let b = 2;
    {

作用域和执行上下文面试__作用域slot

let b = 3; var c = 4; let d = 5; console.log(a); console.log(b) } console.log(b) console.log(c) console.log(d) } foo();

变量分析

d是用let声明的,所以它的作用域为这个块级作用域,并将它添加到这个内部块的词法环境中。

根据以上分析,我们可以将foo执行上下文画出来

{C32A5DC0-B40E-4BF4-9778-7FAE50ED366C}.png

根据分析,我们可以将foo执行上下文画出来

输出分析2. 外部作用域(outer scope)

function bar() {
    console.log(myname);
}
function foo() {
    var myname = 'john'
    bar()
    console.log(myname);
}
var myname = 'lisa'
foo();

执行上下文分析

如果根据直觉我们可能会认为两个输出值都为john,但在bar()中它输出的是lisa。

{FF460AD9-037B-4704-8027-B0F3653408AE}.png

仔细观察上面这张图,它有一个outer指针,简单作用域可以直接从内到外查找,但如果在函数中,还要考虑outer指向的外部作用域是是什么

由于bar()是在全局作用域中定义的,所以即使在foo()中被使用,但它的outer指向的还是全局作用域,所以它查找的会是全局作用域中定义的myname = lisa,而不会经过foo()。

总结

通过分析具体的代码示例,我们看到了变量提升(hoisting)的行为,以及var、let和const声明的不同特性。特别是ES6引入的let和const,它们具有块级作用域,并且在声明之前访问会导致引用错误,从而避免了var带来的变量提升问题。

最后,通过一个复杂的例子,我们讨论了外部作用域的概念,即函数可以访问其外部作用域中的变量。这个例子展示了即使在函数内部调用另一个函数,该函数的作用域链仍然指向其定义时的外部作用域,而不是调用时的作用域。

通过这些概念的理解和实践,开发者可以更好地控制变量的作用域,避免常见的编程错误,并编写出更加健壮和易于维护的代码。


上一条查看详情 +VSCode 架构分析:启动和初始化
下一条 查看详情 +没有了