深入理解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时,这些变量和函数才会被创建。


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


相关文章
|
JavaScript 前端开发
js的作用域作用域链
【10月更文挑战第29天】理解JavaScript的作用域和作用域链对于正确理解变量的访问和生命周期、避免变量命名冲突以及编写高质量的JavaScript代码都具有重要意义。在实际开发中,需要合理地利用作用域和作用域链来组织代码结构,提高代码的可读性和可维护性。
|
10月前
|
前端开发 JavaScript Java
JavaScript闭包深入剖析:性能剖析与优化技巧
JavaScript 闭包是强大而灵活的特性,广泛应用于数据封装、函数柯里化和事件处理等场景。闭包通过保存外部作用域的变量,实现了私有变量和方法的创建,提升了代码的安全性和可维护性。然而,闭包也可能带来性能问题,如内存泄漏和执行效率下降。为优化闭包性能,建议采取以下策略:及时解除对不再使用的闭包变量的引用,减少闭包的创建次数,使用 WeakMap 管理弱引用,以及优化闭包结构以减少作用域链查找的开销。在实际开发中,无论是 Web 前端还是 Node.js 后端,这些优化措施都能显著提升程序的性能和稳定性。
249 70
|
8月前
|
存储 JavaScript 前端开发
|
10月前
|
自然语言处理 JavaScript 前端开发
当面试官再问我JS闭包时,我能答出来的都在这里了。
闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
426 16
当面试官再问我JS闭包时,我能答出来的都在这里了。
|
9月前
|
缓存 自然语言处理 JavaScript
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
JavaScript 前端开发
如何在 JavaScript 中实现块级作用域?
【10月更文挑战第29天】通过使用 `let`、`const` 关键字、立即执行函数表达式以及模块模式等方法,可以在JavaScript中有效地实现块级作用域,更好地控制变量的生命周期和访问权限,提高代码的可维护性和可读性。
|
自然语言处理 JavaScript 前端开发
如何在 JavaScript 中创建执行上下文
在JavaScript中,每当执行一段代码时,都会创建一个执行上下文。它首先进行变量、函数声明的创建和内存分配(即变量环境和词法环境的建立),接着进入代码执行阶段,处理具体逻辑。
|
存储 缓存 自然语言处理
掌握JavaScript闭包,提升代码质量与性能
掌握JavaScript闭包,提升代码质量与性能
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包(Closures)
深入理解JavaScript中的闭包(Closures)
|
存储 自然语言处理 JavaScript
深入理解JavaScript的闭包(Closures)
深入理解JavaScript的闭包(Closures)
186 0