返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JavaScript的闭包与变量作用域介绍
  • 122
分享到

JavaScript的闭包与变量作用域介绍

2024-04-02 19:04:59 122人浏览 薄情痞子
摘要

这篇文章主要讲解了“javascript的闭包与变量作用域介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JavaScript的闭包与变量作用域介绍”吧!

这篇文章主要讲解了“javascript的闭包与变量作用域介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JavaScript的闭包与变量作用域介绍”吧!

JavaScript 是一种非常面向函数的语言。它给了我们很大的自由度。在 JavaScript  中,我们可以随时创建函数,可以将函数作为参数传递给另一个函数,并在完全不同的代码位置进行调用。

我们已经知道函数可以访问其外部的变量。

但是,如果在函数被创建之后,外部变量发生了变化会怎样?函数会获得新值还是旧值?

如果将函数作为参数传递并在代码中的另一个位置调用它,该函数将访问的是新位置的外部变量吗?

让我们通过本文来学习这些相关知识,以了解在这些场景以及更复杂的场景下到底会发生什么。

我们将在这探讨一下 let/const

在 JavaScript 中,有三种声明变量的方式:let,const(现代方式),var(过去留下来的方式)。

在本文的示例中,我们将使用 let 声明变量。用 const 声明的变量的行为也相同(译注:与 let 在作用域等特性上是相同的),因此,本文也涉及用  const 进行变量声明。旧的 var 与上面两个有着明显的区别,我们将在 旧时的 "var" 中详细介绍。

一、代码块

如果在代码块 {...} 内声明了一个变量,那么这个变量只在该代码块内可见。

例如:

{   // 使用在代码块外不可见的局部变量做一些工作   let message = "Hello"; // 只在此代码块内可见   alert(message); // Hello }  alert(message); // Error: message is not defined

我们可以使用它来隔离一段代码,该段代码执行自己的任务,并使用仅属于自己的变量:

{   // 显示 message   let message = "Hello";   alert(message); }  {   // 显示另一个 message   let message = "Goodbye";   alert(message); }

这里如果没有代码块则会报错

请注意,如果我们使用 let 对已存在的变量进行重复声明,如果对应的变量没有单独的代码块,则会出现错误:

// 显示 message let message = "Hello"; alert(message);  // 显示另一个 message let message = "Goodbye"; // Error: variable already declared alert(message);

对于 if,for 和 while 等,在 {...} 中声明的变量也仅在内部可见:

if (true) {   let phrase = "Hello!";   alert(phrase); // Hello! }  alert(phrase); // Error, no such variable!

在这儿,当 if 执行完毕,则下面的 alert 将看不到 phrase,因此会出现错误。

太好了,因为这就允许我们创建特定于 if 分支的块级局部变量。

对于 for 和 while 循环也是如此:

for (let i = 0; i < 3; i++) {   // 变量 i 仅在这个 for 循环的内部可见   alert(i); // 0,然后是 1,然后是 2 }  alert(i); // Error, no such variable

从视觉上看,let i 位于 {...} 之外。但是 for 构造在这里很特殊:在其中声明的变量被视为块的一部分。

二、嵌套函数

当一个函数是在另一个函数中创建的时,那么该函数就被称为“嵌套”的。

在 JavaScript 中很容易实现这一点。

我们可以使用嵌套来组织代码,比如这样:

function sayHiBye(firstName, lastName) {    // 辅助嵌套函数使用如下   function getFullName() {     return firstName + " " + lastName;   }    alert( "Hello, " + getFullName() );   alert( "Bye, " + getFullName() );  }

这里创建的 嵌套 函数 getFullName() 是为了更加方便。它可以访问外部变量,因此可以返回全名。嵌套函数在 JavaScript  中很常见。

更有意思的是,可以返回一个嵌套函数:作为一个新对象的属性或作为结果返回。之后可以在其他地方使用。不论在哪里调用,它仍然可以访问相同的外部变量。

下面的 makeCounter 创建了一个 “counter” 函数,该函数在每次调用时返回下一个数字:

function makeCounter() {   let count = 0;    return function() {     return count++;   }; }  let counter = makeCounter();  alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2

尽管很简单,但稍加变型就具有很强的实际用途,比如,用作 随机数生成器 以生成用于自动化测试的随机数值。

这是如何运作的呢?如果我们创建多个计数器,它们会是独立的吗?这里的变量是怎么回事?

理解这些内容对于掌握 JavaScript 的整体知识很有帮助,并且对于应对更复杂的场景也很有益处。因此,让我们继续深入探究。

三、词法环境 Lexical Environment

为了使内容更清晰,这里将分步骤进行讲解。

Step 1. 变量

在 JavaScript 中,每个运行的函数,代码块 {...} 以及整个脚本,都有一个被称为 词法环境(Lexical Environment)  的内部(隐藏)的关联对象。

词法环境对象由两部分组成:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 环境记录(Environment Record) &mdash;&mdash; 一个存储所有局部变量作为其属性(包括一些其他信息,例如 this 的值)的对象。

  3. 对 外部词法环境 的引用,与外部代码相关联。

一个“变量”只是 环境记录 这个特殊的内部对象的一个属性。“获取或修改变量”意味着“获取或修改词法环境的一个属性”。

举个例子,这段没有函数的简单的代码中只有一个词法环境:

JavaScript的闭包与变量作用域介绍

这就是所谓的与整个脚本相关联的 全局 词法环境。

在上面的图片中,矩形表示环境记录(变量存储),箭头表示外部引用。全局词法环境没有外部引用,所以箭头指向了 null。

随着代码开始并继续运行,词法环境发生了变化。

这是更长的代码:

JavaScript的闭包与变量作用域介绍

右侧的矩形演示了执行过程中全局词法环境的变化:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 当脚本开始运行,词法环境预先填充了所有声明的变量。最初,它们处于“未初始化(Uninitialized)”状态。这是一种特殊的内部状态,这意味着引擎知道变量,但是在用  let 声明前,不能引用它。几乎就像变量不存在一样。

  3. 然后 let phrase 定义出现了。它尚未被赋值,因此它的值为 undefined。从这一刻起,我们就可以使用变量了。

  4. phrase 被赋予了一个值。

  5. phrase 的值被修改。

现在看起来都挺简单的,是吧?

  • 变量是特殊内部对象的属性,与当前正在执行的(代码)块/函数/脚本有关。

  • 操作变量实际上是操作该对象的属性。

词法环境是一个规范对象:

“词法环境”是一个规范对象(specification object):它仅仅是存在于 编程语言规范  中的“理论上”存在的,用于描述事物如何运作的对象。我们无法在代码中获取该对象并直接对其进行操作。

但 JavaScript 引擎同样可以优化它,比如清除未被使用的变量以节省内存和执行其他内部技巧等,但显性行为应该是和上述的无差。

Step 2. 函数声明

一个函数其实也是一个值,就像变量一样。

不同之处在于函数声明的初始化会被立即完成。

当创建了一个词法环境(Lexical Environment)时,函数声明会立即变为即用型函数(不像 let 那样直到声明处才可用)。

这就是为什么我们可以在(函数声明)的定义之前调用函数声明。

例如,这是添加一个函数时全局词法环境的初始状态:

JavaScript的闭包与变量作用域介绍

正常来说,这种行为仅适用于函数声明,而不适用于我们将函数分配给变量的函数表达式,例如 let say = function(name)...。

Step 3. 内部和外部的词法环境

在一个函数运行时,在调用刚开始时,会自动创建一个新的词法环境以存储这个调用的局部变量和参数。

例如,对于 say("John"),它看起来像这样(当前执行位置在箭头标记的那一行上):

JavaScript的闭包与变量作用域介绍

在这个函数调用期间,我们有两个词法环境:内部一个(用于函数调用)和外部一个(全局):

  • 内部词法环境与 say 的当前执行相对应。它具有一个单独的属性:name,函数的参数。我们调用的是 say("John"),所以 name 的值为  "John"。

  • 外部词法环境是全局词法环境。它具有 phrase 变量和函数本身。

内部词法环境引用了 outer。

当代码要访问一个变量时 &mdash;&mdash; 首先会搜索内部词法环境,然后搜索外部环境,然后搜索更外部的环境,以此类推,直到全局词法环境。

如果在任何地方都找不到这个变量,那么在严格模式下就会报错(在非严格模式下,为了向下兼容,给未定义的变量赋值会创建一个全局变量)。

在这个示例中,搜索过程如下:

  • 对于 name 变量,当 say 中的 alert 试图访问 name 时,会立即在内部词法环境中找到它。

  • 当它试图访问 phrase 时,然而内部没有 phrase,所以它顺着对外部词法环境的引用找到了它。

Step 4. 返回函数

让我们回到 makeCounter 这个例子。

function makeCounter() {   let count = 0;    return function() {     return count++;   }; }  let counter = makeCounter();

在每次 makeCounter() 调用的开始,都会创建一个新的词法环境对象,以存储该 makeCounter 运行时的变量。

因此,我们有两层嵌套的词法环境,就像上面的示例一样:

JavaScript的闭包与变量作用域介绍

不同的是,在执行 makeCounter() 的过程中创建了一个仅占一行的嵌套函数:return count++。我们尚未运行它,仅创建了它。

所有的函数在“诞生”时都会记住创建它们的词法环境。从技术上讲,这里没有什么魔法:所有函数都有名为 [[Environment]]  的隐藏属性,该属性保存了对创建该函数的词法环境的引用。

JavaScript的闭包与变量作用域介绍

因此,counter.[[Environment]] 有对 {count: 0}  词法环境的引用。这就是函数记住它创建于何处的方式,与函数被在哪儿调用无关。[[Environment]] 引用在函数创建时被设置并永久保存。

稍后,当调用 counter() 时,会为该调用创建一个新的词法环境,并且其外部词法环境引用获取于  counter.[[Environment]]:

JavaScript的闭包与变量作用域介绍

现在,当 counter() 中的代码查找 count 变量时,它首先搜索自己的词法环境(为空,因为那里没有局部变量),然后是外部  makeCounter() 的词法环境,并且在哪里找到就在哪里修改。

在变量所在的词法环境中更新变量。

这是执行后的状态:

JavaScript的闭包与变量作用域介绍

如果我们调用 counter() 多次,count 变量将在同一位置增加到 2,3 等。

闭包

开发者通常应该都知道“闭包”这个通用的编程术语。

闭包  是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函数被返回(寿命终结)了之后。在某些编程语言中,这是不可能的,或者应该以特殊的方式编写函数来实现。但是如上所述,在  JavaScript 中,所有函数都是天生闭包的(只有一个例外,将在 "new Function" 语法 中讲到)。

也就是说:JavaScript 中的函数会自动通过隐藏的 [[Environment]] 属性记住创建它们的位置,所以它们都可以访问外部变量。

面试时,前端开发者通常会被问到“什么是闭包?”,正确的回答应该是闭包的定义,并解释清楚为什么 JavaScript  中的所有函数都是闭包的,以及可能的关于 [[Environment]] 属性和词法环境原理的技术细节。

四、垃圾收集

通常,函数调用完成后,会将词法环境和其中的所有变量从内存中删除。因为现在没有任何对它们的引用了。与 JavaScript  中的任何其他对象一样,词法环境仅在可达时才会被保留在内存中。

但是,如果有一个嵌套的函数在函数结束后仍可达,则它将具有引用词法环境的 [[Environment]] 属性。

在下面这个例子中,即使在函数执行完成后,词法环境仍然可达。因此,此嵌套函数仍然有效。

例如:

function f() {   let value = 123;    return function() {     alert(value);   } }  let g = f(); // g.[[Environment]] 存储了对相应 f() 调用的词法环境的引用

请注意,如果多次调用 f(),并且返回的函数被保存,那么所有相应的词法环境对象也会保留在内存中。下面代码中有三个这样的函数:

function f() {   let value = Math.random();    return function() { alert(value); }; }  // 数组中的 3 个函数,每个都与来自对应的 f() 的词法环境相关联 let arr = [f(), f(), f()];

当词法环境对象变得不可达时,它就会死去(就像其他任何对象一样)。换句话说,它仅在至少有一个嵌套函数引用它时才存在。

在下面的代码中,嵌套函数被删除后,其封闭的词法环境(以及其中的 value)也会被从内存中删除:

function f() {   let value = 123;    return function() {     alert(value);   } }  let g = f(); // 当 g 函数存在时,该值会被保留在内存中  g = null; // &hellip;&hellip;现在内存被清理了

五、实际开发中的优化

正如我们所看到的,理论上当函数可达时,它外部的所有变量也都将存在。

但在实际中,JavaScript 引擎会试图优化它。它们会分析变量的使用情况,如果从代码中可以明显看出有未使用的外部变量,那么就会将其删除。

在 V8(Chrome,Edge,Opera)中的一个重要的副作用是,此类变量在调试中将不可用。

打开 Chrome 浏览器的开发者工具,并尝试运行下面的代码。

当代码执行暂停时,在控制台中输入 alert(value)。

function f() {   let value = Math.random();    function g() {     debugger; // 在 Console 中:输入 alert(value); No such variable!   }    return g; }  let g = f(); g();

正如你所见的 &mdash;&mdash; No such variable! 理论上,它应该是可以访问的,但引擎把它优化掉了。

这可能会导致有趣的(如果不是那么耗时的)调试问题。其中之一 &mdash;&mdash; 我们可以看到的是一个同名的外部变量,而不是预期的变量:

let value = "Surprise!";  function f() {   let value = "the closest value";    function g() {     debugger; // 在 console 中:输入 alert(value); Surprise!   }    return g; }  let g = f(); g();

V8 引擎的这个特性你真的应该知道。如果你要使用 Chrome/Edge/Opera 进行代码调试,迟早会遇到这样的问题。

这不是调试器的 bug,而是 V8 的一个特别的特性。也许以后会被修改。你始终可以通过运行本文中的示例来进行检查。

感谢各位的阅读,以上就是“JavaScript的闭包与变量作用域介绍”的内容了,经过本文的学习后,相信大家对JavaScript的闭包与变量作用域介绍这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: JavaScript的闭包与变量作用域介绍

本文链接: https://lsjlt.com/news/79855.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • JavaScript的闭包与变量作用域介绍
    这篇文章主要讲解了“JavaScript的闭包与变量作用域介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JavaScript的闭包与变量作用域介绍”吧!...
    99+
    2024-04-02
  • JavaScript 中的作用域与闭包
    目录一、JavaScript 是一门编译语言1.1 传统编译语言的编译步骤1.2 JavaScript 与传统编译语言的区别二、作用域(Scope)2.1 LHS查询 和 RHS查询...
    99+
    2024-04-02
  • JavaScript闭包原理与使用介绍
    目录1. 认识闭包2. 变量的作用域和生命周期2.1 变量的作用域2.2 变量的生命周期3. 闭包的概念及其作用3.1 闭包的概念3.2 闭包的应用3.2.1 保存私有变量3.2.2...
    99+
    2022-11-13
    JavaScript闭包 JS闭包
  • Shell中变量作用域的介绍与使用命令
    前言 众所周知Shell 中的变量只作用于当前进程。如需在子进程中创建副本可使用 export 内建命令。 有时使用临时变量语法可以非常方便。 变量使用 任何地方都可以定义 Shell 变量,使用 =...
    99+
    2022-06-04
    变量 命令 作用
  • Javascript作用域与闭包详情
    目录1、作用域2、作用域链3、词法作用域5、闭包的应用6、闭包的缺陷7、高频闭包面试题1、作用域 简单来说,作用域是指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限 在E...
    99+
    2024-04-02
  • JavaScript闭包的简单介绍
    本篇内容主要讲解“JavaScript闭包的简单介绍”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript闭包的简单介绍”吧!什么是JS闭包?先看一...
    99+
    2024-04-02
  • 你真的了解JavaScript的作用域与闭包吗
    目录一、作用域二、闭包总结一、作用域 1.作用域总体来说就是根据名称查找变量的一套规则。JS查找变量的方式有两种:LHS和RHS。 LHS(left hand side)大致可以理解...
    99+
    2024-04-02
  • JavaScript局部变量与全局变量的作用域是什么
    本文小编为大家详细介绍“JavaScript局部变量与全局变量的作用域是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“JavaScript局部变量与全局变量的作用域是什么”文章能帮助大家解决疑惑,下面...
    99+
    2024-04-02
  • JavaScript(函数,作用域和闭包)
    目录 一,什么是函数1.1,常用系统函数1.2,函数声明 1.3,函数表达式二,预解析2.1,函数自调用 2.2,回调函数三,变量的作用域3.1,隐式全局变量 四,作用域与块级作用...
    99+
    2023-09-05
    前端 javascript 开发语言
  • 怎样理解JavaScript中的变量与作用域
    怎样理解JavaScript中的变量与作用域,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1、原始值与引用值6种简单数据类型的值都是原始值, 原始值通过变量赋值给另一个变量时,...
    99+
    2023-06-25
  • Javascript的作用域、作用域链以及闭包详解
    一、javascript中的作用域 ①全局变量-函数体外部进行声明 ②局部变量-函数体内部进行声明 1)函数级作用域 javascript语言中局部变量不同于C#、Java等高级语言...
    99+
    2024-04-02
  • Python编程中闭包的变量作用域问题解析
    目录闭包闭包中的变量闭包 ​ 在我们使用返回函数的时候,由于我们在一个函数中需要返回另一个函数,因此,我们在这个函数中就需要重新定义一个函数。而这样,就造成了我们的函数嵌...
    99+
    2024-04-02
  • golang函数闭包和局部变量作用域的关系
    答案:go 中的闭包可以访问其定义作用域外的变量,即使这些变量超出函数的定义范围。详细描述:局部变量的作用域限于其声明的函数或块中。闭包包含函数代码和对周围作用域变量的引用。闭包可以访问...
    99+
    2024-04-23
    golang 闭包 作用域
  • 【6】装饰器、闭包、偏函数、变量作用域问
            【一】、装饰器思想 装饰器是其实想增加一个函数的功能,但是又不想变动原来函数的代码,就用装饰器。 比如:我们用别人的一个函数,又不是很满意,所以用装饰器装饰一下即可。   def fun1(): print(...
    99+
    2023-01-30
    变量 函数 作用
  • Javascript之作用域、作用域链、闭包的示例分析
    这篇文章主要介绍Javascript之作用域、作用域链、闭包的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!什么是作用域?作用域是一种规则,在代码编译阶段就确定了,规定了变量...
    99+
    2024-04-02
  • JavaScript深入理解作用域链与闭包详情
    目录深入作用域链与闭包作用域链[[Environment]]完善环境记录闭包函数实例什么是闭包变量绑定同一个闭包总结深入作用域链与闭包 为什么要把作用域链和闭包放在一起讲呢,它们有什...
    99+
    2024-04-02
  • mysql的全局变量与局部变量介绍
    通常在服务器启动时,会将每个全局变量初始化为其默认值(我们可以通过命令行或选项文件中指定的选项更改这些默认值),然后服务器还为每个连接的客户端维护一组会话变量,客户端的会话变量在连接时使用相应全局变量的当前...
    99+
    2024-04-02
  • JavaScript中变量的作用域详解
    一、变量的分类 在JavaScript中变量分为两种: 全局变量局部变量 二、变量的作用域 1、局部变量的作用域 局部变量:在函数内部定义的变量称为局部变量,其作用域为该函数内部,在...
    99+
    2024-04-02
  • 在javascript中闭包是不是作用域
    本篇内容介绍了“在javascript中闭包是不是作用域”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • JavaScript的变量作用域实例分析
    这篇文章主要讲解了“JavaScript的变量作用域实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“JavaScript的变量作用域实例分析”吧!1.变量作用域的分析首先,我们先研究一...
    99+
    2023-06-29
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作