深入理解JS的执行上下文、词法作用域和闭包(上)

简介: 深入理解JS的执行上下文、词法作用域和闭包(上)

摘要


本文将深入探讨执行上下文、词法作用域和闭包的概念📝,帮助读者更好地理解这些关键的编程概念。通过具体示例和详细讲解,我们将揭示它们在 JavaScript 中的工作原理和应用场景。


引言


在 JavaScript 编程中,执行上下文、词法作用域和闭包是非常重要的概念🎯。理解它们对于编写高效、可靠的代码至关重要。在本文中,我们将一起探索这些概念的本质,以及它们如何影响代码的执行和行为。


一、执行上下文 🌳


执行上下文的定义和作用


在JavaScript中,执行上下文(Execution Context)是ECMAScript规范中的一个概念,它定义了代码执行时的环境。JavaScript中的执行上下文分为两种:全局执行上下文和函数执行上下文。


  1. 全局执行上下文:

全局执行上下文是JavaScript程序的根上下文,当一个程序开始执行时,它会首先进入全局执行上下文。在全局执行上下文中,会创建全局对象(global object),它是全局变量的宿主,同时还会创建一个名为"this"的变量,指向全局对象。在全局执行上下文中,JavaScript会执行全局代码,直到遇到一个函数调用。


作用:


  • 创建全局对象:在全局执行上下文中,会创建全局对象,它是全局变量的宿主。
  • 创建"this"变量:在全局执行上下文中,会创建一个名为"this"的变量,指向全局对象。
  • 执行全局代码:在全局执行上下文中,JavaScript会执行全局代码,直到遇到一个函数调用。


  1. 函数执行上下文:

当在全局执行上下文中遇到一个函数调用时,会进入函数执行上下文。在函数执行上下文中,会创建一个活动对象(activation object),它是函数局部变量的宿主,同时还会创建一个名为"this"的变量,指向函数的上下文(即调用该函数的对象)。在函数执行上下文中,JavaScript会执行函数体中的代码,直到函数执行完毕。


作用:


  • 创建活动对象:在函数执行上下文中,会创建一个活动对象,它是函数局部变量的宿主。
  • 创建"this"变量:在函数执行上下文中,会创建一个名为"this"的变量,指向函数的上下文(即调用该函数的对象)。
  • 执行函数体中的代码:在函数执行上下文中,JavaScript会执行函数体中的代码,直到函数执行完毕。


总结:执行上下文是JavaScript代码执行时的环境,它定义了代码执行时的上下文状态和规则。通过理解执行上下文,可以更好地理解JavaScript中的变量作用域、函数调用和"this"指向等问题。


栈结构与执行上下文的关系 💻


栈结构(Stack)是一种后进先出(LIFO)的数据结构,用于存储和管理执行上下文。在JavaScript中,栈结构用于存储和管理函数执行上下文。


当JavaScript遇到一个函数调用时,它会将该函数的执行上下文压入栈中,成为当前执行上下文。然后,在该函数体中执行代码,直到函数执行完毕。当函数执行完毕后,它会将该函数的执行上下文弹出栈,恢复之前的执行上下文。


例如,以下代码:

function foo() {
  console.log('foo');
  bar();
}

function bar() {
  console.log('bar');
}

foo();

执行过程如下:


  1. 执行全局代码,遇到函数调用foo()。
  2. 将foo函数的执行上下文压入栈中,成为当前执行上下文。
  3. 在foo函数体中执行代码,遇到函数调用bar()。
  4. 将bar函数的执行上下文压入栈中,成为当前执行上下文。
  5. 在bar函数体中执行代码,执行完毕。
  6. 将bar函数的执行上下文弹出栈,恢复之前的执行上下文(即foo函数的执行上下文)。
  7. foo函数继续执行,执行完毕。
  8. 将foo函数的执行上下文弹出栈,恢复全局执行上下文。


总结:栈结构用于存储和管理函数执行上下文,它与执行上下文之间的关系是:栈结构中的每个元素都是一个函数的执行上下文,而执行上下文是指代码执行时的环境。通过理解栈结构与执行上下文之间的关系,可以更好地理解JavaScript中的函数调用和执行顺序等问题。


二、词法作用域 🌏


词法作用域的基本原理


词法作用域(Lexical Scope)是指由代码文本中变量声明的位置决定的变量可访问的范围。在JavaScript中,词法作用域主要受到函数和块级作用域(如let、const声明的变量)的影响。


  1. 词法作用域的基本原理可以概括为以下几点:


函数内的变量声明对外部不可见:在函数内部声明的变量只能在函数内部访问,无法被外部访问。


例如:

function foo() {
  let a = 1;
  console.log(a); // 输出 1
}

console.log(a); // 报错:a is not defined


  1. 块级作用域内的变量声明对嵌套函数不可见:在letconst声明的变量所在的块级作用域内,嵌套的函数无法访问这些变量。


例如:

function foo() {
  let a = 1;
  if (true) {
    let b = 2;
    console.log(a); // 输出 1
    console.log(b); // 输出 2
  }
  console.log(b); // 报错:b is not defined
}


  1. 函数内的变量声明不会覆盖外部的变量:在函数内部声明的变量与同名的外部变量不会互相影响。


例如:

let a = 1;

function foo() {
  let a = 2;
  console.log(a); // 输出 2
}

console.log(a); // 输出 1


  1. 函数内的变量声明不会影响全局变量:在函数内部声明的变量不会影响到全局变量。


例如:

let a = 1;

function foo() {
  let a = 2;
  console.log(a); // 输出 2
}

console.log(a); // 输出 1


总之,词法作用域是由代码文本中变量声明的位置决定的变量可访问的范围。在JavaScript中,词法作用域主要受到函数和块级作用域的影响。理解词法作用域的基本原理有助于更好地理解JavaScript中的变量作用域和函数调用等问题。


变量的可见性和作用域链 🔍


在JavaScript中,变量的可见性和作用域链是理解变量可访问范围和作用域问题的关键。下面是对这两个概念的解释:


  1. 变量的可见性:

变量的可见性是指在代码中可以访问到某个变量的范围。在JavaScript中,变量的可见性受到词法作用域和动态作用域的影响。


词法作用域是由代码文本中变量声明的位置决定的,它决定了哪些变量可以在哪些代码块中访问。动态作用域是由函数调用和变量赋值等操作形成的,它决定了函数内部的变量可以被外部访问。


例如:

function foo() {
  let a = 1;
  console.log(a); // 输出 1
}

console.log(a); // 报错:a is not defined

在这个例子中,变量a在函数foo内部声明,它的可见性受到词法作用域的影响,因此只能在foo函数内部访问,在外部访问会报错。


  1. 作用域链:

作用域链是指当一个变量在当前作用域中找不到定义时,JavaScript会依次向上级作用域查找,直到找到该变量或到达全局作用域。作用域链的查找顺序是由变量声明的位置决定的。


例如:

let a = 1;

function foo() {
  let b = a;
  console.log(b); // 输出 1
}

console.log(b); // 报错:b is not defined

在这个例子中,变量b在函数foo内部声明,它的作用域链包括函数foo和全局作用域。由于b在函数foo内部找不到定义,所以会向全局作用域查找,找到变量a,因此b的值为1。


总结:变量的可见性和作用域链是理解JavaScript中变量作用域和函数调用等问题的重要概念。实际项目中,可以根据变量的声明位置和作用域链的规则,灵活地管理和访问变量。


如何利用词法作用域进行代码封装 📦


词法作用域是JavaScript中一种重要的作用域类型,它由代码文本中变量声明的位置决定。利用词法作用域进行代码封装可以有效地提高代码的可维护性和可读性。下面介绍几种利用词法作用域进行代码封装的方法:


  1. 使用函数封装:


将一组相关的变量和函数封装在一个函数内部,可以避免全局作用域的污染,提高代码的可维护性。

例如,将一个购物车相关的数据和操作封装在一个函数内部:

function createCart() {
  let products = [];

  function addProduct(product) {
    products.push(product);
  }

  function getProducts() {
    return products;
  }

  return {
    addProduct,
    getProducts
  };
}

const cart = createCart();
cart.addProduct({ id: 1, name: 'Product 1' });
console.log(cart.getProducts()); // 输出:[{ id: 1, name: 'Product 1' }]

在这个例子中,products和addProduct、getProducts函数被封装在createCart函数内部,外部代码无法直接访问这些变量和函数,只能通过createCart函数返回的接口进行访问。


  1. 使用块级作用域封装:

利用let、const声明的变量具有块级作用域的特性,可以将一组相关的变量封装在一个代码块内部。

例如,将一个用户相关的数据和操作封装在一个if语句块内部:

let user = null;

if (isLoggedIn) {
  user = {
    id: 1,
    name: 'User 1'
  };

  function getUserName() {
    return user.name;
  }
}

console.log(getUserName()); // 输出:User 1

在这个例子中,user和getUserName函数被封装在if语句块内部,当isLoggedIn为true时,这些变量和函数才会被创建。


总之,利用词法作用域进行代码封装是一种有效的编程方法,可以提高代码的可维护性和可读性。实际项目中,可以根据具体需求和场景,灵活地使用函数封装和块级作用域封装等方法。


相关文章
|
11天前
|
JavaScript 前端开发
js的作用域作用域链
【10月更文挑战第29天】理解JavaScript的作用域和作用域链对于正确理解变量的访问和生命周期、避免变量命名冲突以及编写高质量的JavaScript代码都具有重要意义。在实际开发中,需要合理地利用作用域和作用域链来组织代码结构,提高代码的可读性和可维护性。
|
14天前
|
JavaScript 前端开发
js 闭包的优点和缺点
【10月更文挑战第27天】JavaScript闭包是一把双刃剑,在合理使用的情况下,它可以带来很多好处,如实现数据封装、记忆功能和模块化等;但如果不注意其缺点,如内存泄漏、变量共享和性能开销等问题,可能会导致代码出现难以调试的错误和性能问题。因此,在使用闭包时,需要谨慎权衡其优缺点,根据具体的应用场景合理地运用闭包。
104 58
|
11天前
|
自然语言处理 JavaScript 前端开发
[JS]作用域的“生产者”——词法作用域
本文介绍了JavaScript中的作用域模型与作用域,包括词法作用域和动态作用域的区别,以及全局作用域、函数作用域和块级作用域的特点。通过具体示例详细解析了变量提升、块级作用域中的暂时性死区等问题,并探讨了如何在循环中使用`var`和`let`的不同效果。最后,介绍了两种可以“欺骗”词法作用域的方法:`eval(str)`和`with(obj)`。文章结合了多位博主的总结,帮助读者更快速、便捷地掌握这些知识点。
22 2
[JS]作用域的“生产者”——词法作用域
|
12天前
|
前端开发 JavaScript 数据处理
CSS 变量的作用域和 JavaScript 变量的作用域有什么不同?
【10月更文挑战第28天】CSS变量和JavaScript变量虽然都有各自的作用域概念,但由于它们所属的语言和应用场景不同,其作用域的定义、范围、覆盖规则以及与其他语言特性的交互方式等方面都存在明显的差异。理解这些差异有助于更好地在Web开发中分别运用它们来实现预期的页面效果和功能逻辑。
|
11天前
|
JavaScript 前端开发
如何在 JavaScript 中实现块级作用域?
【10月更文挑战第29天】通过使用 `let`、`const` 关键字、立即执行函数表达式以及模块模式等方法,可以在JavaScript中有效地实现块级作用域,更好地控制变量的生命周期和访问权限,提高代码的可维护性和可读性。
|
19天前
|
JavaScript 前端开发
javascript的作用域
【10月更文挑战第19天javascript的作用域
|
14天前
|
缓存 JavaScript 前端开发
js 闭包
【10月更文挑战第27天】JavaScript闭包是一种强大的特性,它可以用于实现数据隐藏、记忆和缓存等功能,但在使用时也需要注意内存泄漏和变量共享等问题,以确保代码的质量和性能。
34 7
|
11天前
|
自然语言处理 JavaScript 前端开发
如何在 JavaScript 中创建执行上下文
在JavaScript中,每当执行一段代码时,都会创建一个执行上下文。它首先进行变量、函数声明的创建和内存分配(即变量环境和词法环境的建立),接着进入代码执行阶段,处理具体逻辑。
|
17天前
|
自然语言处理 JavaScript 前端开发
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
12 2
|
25天前
|
JavaScript 前端开发
JavaScript 作用域
JavaScript 作用域是指程序中可访问的变量、对象和函数的集合。它分为函数作用域和局部作用域。函数作用域内的变量仅在函数内部可见,而全局作用域的变量在整个网页中均可访问。局部变量在函数执行完毕后会被销毁,而全局变量则在整个脚本生命周期中都存在。未使用 `var` 关键字声明的变量默认为全局变量。