范围,或确定变量所在位置的一组规则,是任何编程语言的最基本概念之一。事实上,它是如此基本,以至于我们很容易忘记这些规则是多么微妙!
准确理解 JavaScript 引擎如何“思考”作用域将使您避免编写提升可能导致的常见错误,让您做好准备专注于闭包,并让您离永远不再编写错误更近一步 再次。
...无论如何,它会帮助您理解提升和关闭。
在本文中,我们将了解:
- JavaScript 中作用域的基础知识
- 解释器如何决定哪些变量属于哪个作用域
- 吊装的实际工作原理
- ES6 关键字
let
和const
如何改变游戏规则
让我们深入探讨。
如果您有兴趣了解有关 ES6 的更多信息以及如何利用语法和功能来改进和简化 JavaScript 代码,为什么不看看这两门课程:
词法范围
如果您之前编写过一行 JavaScript,您就会知道定义变量的位置决定了您可以使用的位置> 他们。变量的可见性取决于源代码的结构,这一事实称为词法 范围。
在 JavaScript 中创建作用域有三种方法:
- 创建函数。在函数内部声明的变量仅在该函数内部可见,包括在嵌套函数中。
-
使用
let
或const
在代码块内声明变量。此类声明仅在块内可见。 -
创建
catch
块。不管你相信与否,这实际上确实创建了一个新的范围!
"use strict"; var mr_global = "Mr Global"; function foo () { var mrs_local = "Mrs Local"; console.log("I can see " + mr_global + " and " + mrs_local + "."); function bar () { console.log("I can also see " + mr_global + " and " + mrs_local + "."); } } foo(); // Works as expected try { console.log("But /I/ can't see " + mrs_local + "."); } catch (err) { console.log("You just got a " + err + "."); } { let foo = "foo"; const bar = "bar"; console.log("I can use " + foo + bar + " in its block..."); } try { console.log("But not outside of it."); } catch (err) { console.log("You just got another " + err + "."); } // Throws ReferenceError! console.log("Note that " + err + " doesn't exist outside of 'catch'!")
上面的代码片段演示了所有三种作用域机制。您可以在 Node 或 Firefox 中运行它,但 Chrome 还无法与 let
很好地配合。
我们将详细讨论其中的每一个。让我们首先详细了解 JavaScript 如何确定哪些变量属于哪个作用域。
编译过程:鸟瞰
当您运行一段 JavaScript 时,会发生两件事使其正常工作。
- 首先,编译您的源代码。
- 然后,编译后的代码将被执行。
在编译步骤期间,JavaScript 引擎:
- 记下所有变量名称
- 将它们注册到适当的范围
- 为自己的价值观保留空间
只有在执行期间,JavaScript 引擎才真正将变量引用的值设置为等于其赋值。在那之前,它们是 undefined
。
第 1 步:编译
// I can use first_name anywhere in this program var first_name = "Peleke"; function popup (first_name) { // I can only use last_name inside of this function var last_name = "Sengstacke"; alert(first_name + ' ' + last_name); } popup(first_name);
让我们逐步了解一下编译器的作用。
首先,它读取行 var first_name = "Peleke"
。接下来,它确定将变量保存到的var first_name = "Peleke"
。接下来,它确定将变量保存到的范围。因为我们位于脚本的顶层,所以它意识到我们处于全局范围。然后,它将变量 first_name
保存到全局范围,并将其值初始化为 undefined
范围
全局范围function popup (first_name)
。然后,它将变量 first_name
保存到全局范围,并将其值初始化为 。
的行。因为 var last_name = "Sengstacke"
,因此编译器会将变量 last_name
保存到 class="inline">popup
—不到全局范围 — 并将其值设置为 undefined
function
果然,编译器找到了一个。由于我们函数的第一行中有
var last_name = "Sengstacke"
,因此编译器会将变量 last_name
保存到 class="inline">popup
—不到全局范围 — 并将其值设置为 。
🎜由于函数内不再有变量声明,编译器将退回到全局作用域。由于不再有变量声明,因此此阶段已完成。🎜 🎜请注意,我们实际上还没有运行任何东西。🎜🎜此时编译器的工作只是确保它知道每个人的名字;它不关心他们做什么。 🎜 🎜此时,我们的程序知道:🎜- 全局范围内有一个名为
first_name
的变量。 - 全局范围内有一个名为
的函数。
- 在
范围内有一个名为
last_name
的变量。 -
first_name
和last_name
的值均为undefined
。
它并不关心我们是否在代码中的其他地方分配了这些变量值。引擎在执行时会处理这个问题。
第 2 步:执行
在下一步中,引擎再次读取我们的代码,但这一次,执行它。
首先,它读取行 var first_name = "Peleke"
。为此,引擎会查找名为 first_name
的变量。由于编译器已经用该名称注册了一个变量,引擎会找到它,并将其值设置为 "Peleke"
。
接下来,它读取行 function popup (first_name)
。由于我们没有在这里执行该函数,因此引擎不感兴趣并跳过它。
最后,它读取行 。由于我们在这里执行一个函数,因此引擎:
- 查找
的值
- 查找
first_name
的值 - 将
作为函数执行,并传递
first_name
的值作为参数
当执行 时,它会经历相同的过程,但这次是在函数
内。它:
- 查找名为
last_name
的变量 - 将
last_name
的值设置为等于"Sengstacke"
- 查找
alert
,将其作为函数执行,并以"Peleke Sengstacke"
作为参数
事实证明,幕后发生的事情比我们想象的要多得多!
既然您已经了解了 JavaScript 如何读取和运行您编写的代码,我们就准备好解决一些更贴近实际的问题:提升的工作原理。
显微镜下的吊装
让我们从一些代码开始。
bar(); function bar () { if (!foo) { alert(foo + "? This is strange..."); } var foo = "bar"; } broken(); // TypeError! var broken = function () { alert("This alert won't show up!"); }
如果运行此代码,您会注意到三件事:
- 在分配之前,您可以引用
foo
,但其值为undefined
。 - 您可以在定义之前调用
已损坏的
,但您会收到TypeError
。 - 您可以在定义之前调用
bar
,它会按需要工作。
提升是指 JavaScript 使我们声明的所有变量名称在其作用域内的任何地方都可用,包括在我们分配给它们之前 .
代码段中的三种情况是您在自己的代码中需要注意的三种情况,因此我们将一一逐步介绍它们。
提升变量声明
请记住,当 JavaScript 编译器读取像 var foo = "bar"
这样的行时,它会:
- 将名称
foo
注册到最近的范围 - 将
foo
的值设置为未定义
我们可以在赋值之前使用 foo
的原因是,当引擎查找具有该名称的变量时,它确实存在。这就是为什么它不会抛出 ReferenceError
。
相反,它获取值 undefined
,并尝试使用该值执行您要求的任何操作。通常,这是一个错误。
记住这一点,我们可能会想象 JavaScript 在我们的函数 bar
中看到的更像是这样:
function bar () { var foo; // undefined if (!foo) { // !undefined is true, so alert alert(foo + "? This is strange..."); } foo = "bar"; }
如果您愿意的话,这是提升的第一条规则:变量在其整个范围内都可用,但其值为 undefined
,直到您代码分配给他们。
常见的 JavaScript 习惯用法是将所有 var
声明写入其作用域的顶部,而不是首次使用它们的位置。用 Doug Crockford 的话来说,这可以帮助您的代码阅读更像它运行。
仔细想想,这是有道理的。当我们以 JavaScript 读取代码的方式编写代码时,为什么 bar
的行为方式非常清楚,不是吗?那么为什么不一直这样写呢?
提升函数表达式
事实上,当我们在定义之前尝试执行 broken
时,我们得到了 TypeError
,这只是第一条提升规则的一个特例。
我们定义了一个名为 broken
的变量,编译器会在全局范围内注册该变量,并将其设置为等于 undefined
。当我们尝试运行它时,引擎会查找 broken
的值,发现它是 undefined
,并尝试将 undefined
作为函数执行.
显然,undefined
不是一个函数,这就是为什么我们得到 TypeError
!
提升函数声明
最后,回想一下,在定义 bar
之前,我们可以调用它。这是由于第二条提升规则:当 JavaScript 编译器找到函数声明时,它会使其名称和定义在其作用域的顶部可用。再次重写我们的代码:
function bar () { if (!foo) { alert(foo + "? This is strange..."); } var foo = "bar"; } var broken; // undefined bar(); // bar is already defined, executes fine broken(); // Can't execute undefined! broken = function () { alert("This alert won't show up!"); }
同样,当您编写作为JavaScript读取时,它更有意义,您不觉得吗?
查看:
- 变量声明和函数表达式的名称在其整个范围内都可用,但它们的值在赋值之前为
undefined
。 - 函数声明的名称和定义在其整个范围内都可用,甚至在其定义之前。
现在让我们来看看两个工作方式稍有不同的新工具:let
和 const
。
<strong>let</strong>
、<strong></strong>const
和临时死区强>强>
与 var
声明不同,使用 let
和 const
声明的变量不 被编译器提升。
至少,不完全是。
还记得我们如何调用 已损坏的
,但却因为尝试执行 undefined
而收到 TypeError
吗?如果我们使用 let
定义 broken
,我们就会得到 ReferenceError
,而不是:
"use strict"; // You have to "use strict" to try this in Node broken(); // ReferenceError! let broken = function () { alert("This alert won't show up!"); }
当 JavaScript 编译器在第一遍中将变量注册到其作用域时,它对待 let
和 const
的方式与处理 var
的方式不同。
当它找到 var
声明时,我们将该变量的名称注册到其范围,并立即将其值初始化为 undefined
。
但是,使用 let
,编译器会将变量注册到其作用域,但不会初始化其值为 undefined
。相反,它会使变量保持未初始化状态,直到引擎执行您的赋值语句。访问未初始化变量的值会抛出 ReferenceError
,这解释了为什么上面的代码片段在运行时会抛出异常。
let
声明和赋值语句的顶部开头之间的空间称为临时死区。该名称源自以下事实:即使引擎知道名为 foo
的变量(位于 bar
范围的顶部),该变量是“死的”,因为它没有值。
...还因为如果您尝试尽早使用它,它会杀死您的程序。
const
关键字的工作方式与 let
相同,但有两个主要区别:
- 使用
const
声明时,必须分配一个值。 - 您不能为使用
const
声明的变量重新赋值。
这可以保证 const
将始终拥有您最初分配给它的值。
// This is legal const React = require('react'); // This is totally not legal const crypto; crypto = require('crypto');
块范围
let
和 const
与 var
在另一方面有所不同:它们的范围大小。
当您使用 var
声明变量时,它在作用域链的尽可能高的位置可见 - 通常是在最近的函数声明的顶部,或者在全局范围,如果您在顶层声明它。
但是,当您使用 let
或 const
声明变量时,它会尽可能本地可见 -仅 > 在最近的街区内。
块是由大括号分隔的一段代码,如 if
/else
块、for
循环,以及显式“阻止”的代码块,如本段代码所示。
"use strict"; { let foo = "foo"; if (foo) { const bar = "bar"; var foobar = foo + bar; console.log("I can see " + bar + " in this bloc."); } try { console.log("I can see " + foo + " in this block, but not " + bar + "."); } catch (err) { console.log("You got a " + err + "."); } } try { console.log( foo + bar ); // Throws because of 'foo', but both are undefined } catch (err) { console.log( "You just got a " + err + "."); } console.log( foobar ); // Works fine
如果您在块内使用 const
或 let
声明变量,则该变量仅在块内可见,且仅 分配后。
但是,使用 var
声明的变量在尽可能远的地方可见 - 在本例中是在全局范围内。
如果您对 let
和 const
的具体细节感兴趣,请查看 Rauschmayer 博士在《探索 ES6:变量和范围》中对它们的介绍,并查看有关它们的 MDN 文档。
词法 <strong>this</strong>
和箭头函数
从表面上看,this
似乎与范围没有太大关系。事实上,JavaScript 并没有根据我们在这里讨论的范围规则来解析 this
的含义。
至少,通常不会。众所周知,JavaScript 不会根据您使用该关键字的位置来解析 this
关键字的含义:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak : function speak () { this.languages.forEach(function(language) { console.log(this.name + " speaks " + language + "."); }) } }; foo.speak();
我们大多数人都认为 this
表示 foo
在 forEach
循环内,因为这就是它在循环之外的含义。换句话说,我们期望 JavaScript 能够解析 this
词法的含义。
但事实并非如此。
相反,它会在您定义的每个函数中创建一个新 this
,并根据您如何调用该函数来决定其含义 -不是您定义它的位置。
第一点类似于在子作用域中重新定义任何变量的情况:
function foo () { var bar = "bar"; function baz () { // Reusing variable names like this is called "shadowing" var bar = "BAR"; console.log(bar); // BAR } baz(); } foo(); // BAR
将 bar
替换为 this
,整个事情应该立即清楚!
传统上,要让 this
按照我们期望的普通旧词法范围变量的方式工作,需要以下两种解决方法之一:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak_self : function speak_s () { var self = this; self.languages.forEach(function(language) { console.log(self.name + " speaks " + language + "."); }) }, speak_bound : function speak_b () { this.languages.forEach(function(language) { console.log(this.name + " speaks " + language + "."); }.bind(foo)); // More commonly:.bind(this); } };
在 speak_self
中,我们将 this
的含义保存到变量 self
中,并使用该变量来得到我们想要的参考。在 speak_bound
中,我们使用 bind
来永久将 this
指向给定对象。
ES2015 为我们带来了一种新的选择:箭头函数。
与“普通”函数不同,箭头函数不会通过设置自己的值来隐藏其父作用域的 this
值。相反,他们从词汇上解析其含义。
换句话说,如果您在箭头函数中使用 this
,JavaScript 会像查找任何其他变量一样查找其值。
首先,它检查本地范围内的 this
值。由于箭头函数没有设置一个,因此它不会找到一个。接下来,它检查 this
值的父范围。如果找到,它将使用它。
这让我们可以像这样重写上面的代码:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak : function speak () { this.languages.forEach((language) => { console.log(this.name + " speaks " + language + "."); }) } };
如果您想了解有关箭头函数的更多详细信息,请查看 Envato Tuts+ 讲师 Dan Wellman 的有关 JavaScript ES6 基础知识的精彩课程,以及有关箭头函数的 MDN 文档。
结论
到目前为止,我们已经了解了很多内容!在本文中,您了解到:
- 变量在编译期间注册到其作用域,并在执行期间与其赋值相关联。
- 在赋值之前引用使用
let
或 class="inline">const 声明的变量会引发ReferenceError
,并且此类变量是范围到最近的块。 - 箭头函数 允许我们实现
this
的词法绑定,并绕过传统的动态绑定。
您还了解了提升的两条规则:
-
第一条提升规则:函数表达式和
var
声明在其定义的整个范围内都可用,但其值为undefined
直到您的赋值语句执行。 - 第二条提升规则:函数声明的名称及其主体在定义它们的范围内可用。
下一步最好是利用 JavaScript 作用域的新知识来理解闭包。为此,请查看 Kyle Simpson 的 Scopes & Closures。
最后,关于 this
有很多话要说,我无法在此介绍。如果该关键字看起来仍然像是黑魔法,请查看此和对象原型来了解它。
与此同时,利用您所学到的知识并减少编写错误!
学习 JavaScript:完整指南
我们构建了一个完整的指南来帮助您学习 JavaScript,无论您是刚刚开始作为 Web 开发人员还是想探索更高级的主题。
以上是理解JavaScript中的作用域的详细内容。更多信息请关注PHP中文网其他相关文章!

您想将博客从 WordPress.com 移至 WordPress.org 吗? 许多初学者从 WordPress.com 开始,但很快意识到其局限性,并希望切换到自托管 WordPress.org 平台。 在本分步指南中,我们将向您展示如何正确地将博客从 WordPress.com 移动到 WordPress.org。 为什么从 WordPress.com 迁移到 WordPress.org? WordPress.com 允许任何人通过创建帐户来

您是否正在寻找自动化 WordPress 网站和社交媒体帐户的方法? 通过自动化,您将能够在 Facebook、Twitter、LinkedIn、Instagram 等平台上自动分享您的 WordPress 博客文章或更新。 在本文中,我们将向您展示如何使用 IFTTT、Zapier 和 Uncanny Automator 轻松实现 WordPress 和社交媒体的自动化。 为什么要自动化 WordPress 和社交媒体? 自动化您的WordPre

就在几天前,我们的一位用户报告了一个不寻常的问题。问题是他达到了自定义菜单项的限制。达到菜单项限制后他保存的任何内容都将根本无法保存。我们从未听说过这个问题,因此我们决定在本地安装上尝试一下。创建了 200 多个菜单项并保存。效果很好。将 100 个项目移至下拉列表中,保存效果非常好。那时我们就知道这与服务器有关。经过进一步研究,似乎还有许多其他人也遇到了同样的问题。深入挖掘后,我们发现了一张 trac 票证 ( #14134 ) 强调了这个问题。在阅读了非常

您需要将自定义元字段添加到 WordPress 中的自定义分类法吗? 自定义分类法可让您组织除类别和标签之外的内容。有时添加其他字段来描述它们很有用。 在本文中,我们将向您展示如何将其他元字段添加到他们创建的分类法中。 何时应将自定义元字段添加到自定义分类法? 当您在WordPress 网站上创建新内容时,您可以使用两种默认分类法(类别和标签)对其进行组织。 一些网站受益于自定义分类法的使用。这些允许您以其他方式对内容进行排序。 例如,

Windows live writer 是一款多功能工具,可让您直接从桌面将帖子发布到 WordPress 博客上。这意味着您根本不需要登录 WordPress 管理面板来更新您的博客。在本教程中,我将向您展示如何使用 Windows Live Writer 为您的 WordPress 博客启用桌面发布。 如何在 WordPress 上设置 Windows Live Writer 第 1 步: 要通过 Windows Live Writer 在 WordPr

最近,我们的一位用户报告了一个非常奇怪的安装问题。写帖子时,他们看不到自己写的任何内容。因为帖子编辑器的文字是白色的。更重要的是,所有可视化编辑器按钮都丢失了,并且从可视化切换到 HTML 的功能也不起作用。在本文中,我们将向您展示如何修复 WordPress 可视化编辑器中的白色文本和缺失按钮问题。 初学者注意事项:如果您正在寻找可能在其他网站的屏幕截图中看到的隐藏按钮,那么您可能正在寻找厨房水槽。您必须单击厨房水槽图标才能看到其他选项,例如下划线、从单词复制等。

您想在 WordPress 中显示用户电子邮件中的头像吗? Gravatar 是一项将用户的电子邮件地址连接到在线头像的网络服务。WordPress 会自动在评论部分显示访问者的头像,但您可能也想将它们添加到网站的其他区域。 在本文中,我们将向您展示如何在 WordPress 中显示用户电子邮件中的头像。 什么是 Gravatar 以及为什么要显示它? Gravatar代表全球认可的头像,它允许人们将图片链接到他们的电子邮件地址。 如果网站支

您想更改 WordPress 中的默认媒体上传位置吗? 将媒体文件移动到其他文件夹可以提高网站的速度和性能,并帮助您更快地创建备份。它还使您可以自由地以最适合您的方式组织文件。 在本文中,我们将向您展示如何更改 WordPress 中的默认媒体上传位置。 为什么要更改默认媒体上传位置? 默认情况下,WordPress 将所有图像和其他媒体文件存储在 /wp-content/uploads/ 文件夹中。 在此文件夹中,您将找到不同年份和月份的子


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

Dreamweaver Mac版
视觉化网页开发工具

记事本++7.3.1
好用且免费的代码编辑器

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

安全考试浏览器
Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。