菜单

函数作用域 vs. 块作用域

相关源文件

本 Wiki 页面解释了 JavaScript 中函数作用域和块作用域之间的区别,以及何时以及如何使用每种作用域。理解这些概念对于编写具有适当变量封装的可维护代码至关重要。本节重点比较使用 var 声明的函数级作用域与使用 letconst 声明的块级作用域,以及它们对程序设计的影响。

有关整个作用域链以及嵌套作用域如何交互的相关信息,请参阅 作用域链。有关不同变量声明的提升行为的详细信息,请参阅 变量(不那么)秘密的生命周期

理解函数作用域

在 JavaScript 中,函数传统上是作用域创建的主要单元。当我们使用 var 声明变量或创建函数声明时,这些声明会绑定到包含它的函数作用域,无论它们在函数内的何处出现。

函数作用域意味着使用 var 声明的变量在整个函数内都可访问,不受函数内任何块边界的限制。这种行为有时会导致意外的变量泄露。

来源: scope-closures/ch3.md1-149 scope-closures/ch3.md1-61

函数作用域可视化

来源: scope-closures/ch3.md1-61 scope-closures/ch2.md1-80

理解块作用域

随着 ES6 中 letconst 的引入,JavaScript 获得了真正的块作用域能力。块(用 { } 花括号表示)现在可以使用这些声明作为作用域边界,将变量的可见性限制在仅该块内。

一个关键的区别是,块作用域变量在运行时执行其声明之前不会被创建(与 var 变量不同,后者会被提升并用 undefined 初始化)。这创造了所谓的“暂时性死区”(TDZ)——一个变量存在但无法在其声明之前访问的时期。

来源: scope-closures/ch6.md216-306 scope-closures/ch5.md1-150

块作用域可视化

来源: scope-closures/ch6.md216-270 scope-closures/ch2.md30-70

函数作用域与块作用域的关键区别

理解何时使用每种作用域至关重要。以下是关键区别的比较:

功能函数作用域(var块作用域(let/const
作用域边界受函数边界限制受任何 { } 块边界限制
提升 (Hoisting)被提升并用 undefined 初始化被提升但未初始化(TDZ)
重新声明在同一作用域内允许在同一作用域内不允许
生命周期整个函数都存在仅在其声明的块内存在
全局绑定在全局对象上创建属性不在全局对象上创建属性
可见性在整个函数中可用仅在块内声明之后可用

来源: scope-closures/ch5.md30-250 scope-closures/ch3.md180-260 scope-closures/ch6.md216-306

作用域嵌套比较

来源: scope-closures/ch2.md1-80 scope-closures/ch3.md1-100 scope-closures/ch6.md216-306

混合方法和最佳实践

在实际代码中,混合方法通常是最有效的。“最小暴露原则”(POLE)表明,变量应在尽可能小的作用域中声明,以尽量减少意外的交互并提高代码的可维护性。

何时使用函数作用域与块作用域

  • 使用函数作用域var)用于
    • 需要在整个函数中访问的变量
    • 函数内多个块使用的变量
    • 在使用闭包需要访问最新值的循环时
  • 使用块作用域let/const)用于
    • 仅在特定块内需要的变量
    • 循环变量
    • 使用范围有限的临时变量
    • 创建私有实现细节
    • 避免变量名冲突

来源: scope-closures/ch6.md1-80 scope-closures/apA.md590-661

块内函数声明(FiB)

一个特殊的考虑因素是函数声明在块内的行为。块内的函数声明在 JavaScript 环境中的处理方式历来不一致。

在严格模式下(ES 模块中的默认设置),块内的函数声明作用于该块。但是,在非严格模式下,它们可能会被提升到包含的函数作用域甚至全局作用域,具体取决于 JS 引擎。

为了在所有环境中保持一致性,建议在块内使用带 letconst 的函数表达式

来源: scope-closures/ch6.md389-430 scope-closures/ch5.md41-60

IIFE 与块作用域创建对比

在 ES6 引入块作用域之前,立即调用函数表达式(IIFE)常被用来创建一个新作用域。

有了块作用域,我们现在可以更简洁地实现类似的封装。

主要区别在于函数会创建自己的作用域边界,这会影响 returnthisbreakcontinue 语句的行为。块没有这些相同的边界效应,这使得它们在某些情况下更具灵活性。

来源: scope-closures/ch6.md180-215 scope-closures/ch6.md307-380

IIFE 与块作用域对比

来源: scope-closures/ch6.md180-215 scope-closures/ch6.md307-380

函数作用域与块作用域之间的遮蔽

遮蔽(Shadowing)发生在内层作用域的变量与外层作用域的变量同名时。内层变量会“遮蔽”外层变量,阻止从内层作用域访问它。

遮蔽行为因声明类型而异

  • let 可以遮蔽 var
  • var 不能遮蔽 let(会导致 SyntaxError),如果它们之间没有函数边界。

来源: scope-closures/ch3.md206-260

结论与最佳实践

遵循“最小暴露原则”(POLE),您应该

  1. 在代码中恰当使用 varlet/const,为每个任务选择合适的工具
  2. var 用于函数范围的变量
  3. let/const 用于块作用域的变量
  4. 将变量声明在刚好能满足需求的最小作用域内
  5. 避免将变量不必要地暴露给外部作用域
  6. 在块中使用函数声明时要小心
  7. 使用块作用域来防止意外的变量泄漏

理解函数作用域和块作用域的区别,可以帮助您编写更易于维护的代码,并减少因意外变量访问或冲突而导致的错误。

来源: scope-closures/ch6.md1-80 scope-closures/apA.md590-661