专业汉语词典知识平台,分享汉字词语知识、历史文学知识解答!

励北网
励北网

ECMAScript是什么,JavaScript基础 - ECMAScript

来源:小易整编  作者:小易  发布时间:2023-02-25 07:25
摘要:ECMAScript是什么,JavaScript基础-ECMAScript在这节内容中,我们将会深入理解ECMAScript语言以及文法定义。ECMAScript规范使用上下文无关文法定义语言,如果你这个文法还不了解的话,最好现在去了解一下...

ECMAScript是什么,JavaScript基础 - ECMAScript

在这节内容中,我们将会深入理解 ECMAScript 语言以及文法定义。ECMAScript 规范使用 上下文无关文法 定义语言,如果你这个文法还不了解的话,最好现在去了解一下它的基础文法知识。

ECMAScript 文法 #

ECMAScript 规范定义了四种文法:

词法文法描述了如何将 Unicode 码点 编译为输入元素(标记、换行符、注释、空格)序列。

语法文法定义了是如何用标记构成文法正确的程序。

正则文法描述了是如何将 Unicode 码点编译成正则表达式。

数值字符串文法描述了如何将字符串编译转译成数字类型的值。

每一种文法都由上下文无关文法进行定义,其中包含了一组产生式。

四种文法使用的表达式相差无二:语法文法表示为 LeftHandSideSymbol,词法文法和正则文法表示为 LeftHandSideSymbol ::,而数值字符串文法表示为 LeftHandSideSymbol :::。

接下来,我们详细分析一下词法文法和语法文法。

ECMAScript是什么,JavaScript基础 - ECMAScript

词法文法 #

规范将 ECMAScript 源文本定义为一串 Unicode 码点序列。举个例子,变量名称不仅可以是 ASCII 字符,还可以包含其它的 Unicode 字符。规范并没有谈论实际的编码 (比如 UTF-8 或者 UTF-16)。规范已经假定源码已经按照自己的编码方式转化为了 Unicode 码点序列。

很难在初始时对 ECMAScript 源码进行标记,这会让定义词法文法会变得稍显复杂。

比如说,如果不去看 / 所在的上下文环境是什么,就不知道 / 指的是除法操作符还是正则表达式开头。

const x = 10 / 5;复制代码

这里的 / 指的是 DivPunctuator。

const r = /foo/;复制代码

这里第一个 / 指的是 RegularExpressionLiteral 的开头。

模板引入了类似的歧义 — 比如,}` 的解释取决于它所在的上下文环境。

const what1 = 'temp';const what2 = 'late';const t = `I am a ${ what1 + what2 }`;复制代码

这里的 `I am a ${ 是 TemplateHead,而 }` 是 TemplateTail。

if (0 == 1) {}`not very useful`;复制代码

这里的 } 是 RightBracePunctuator,而 ` 是 NoSubstitutionTemplate 的开头。

即使 / 和 }` 的解释取决于它们所在的上下文(它们在代码中的语法位置),但是我们接下来谈论的文法仍然是和上下文无关的。

词法文法中使用一些目标符来区分对不同上下文环境中允许哪些输入元素、不允许哪些输入元素。例如,目标符 InputElementDiv 被用于 / 是除法以及 /= 是除法赋值的上下文中。InputElementDiv 产生式列出了在次上下文中可能产生的标记:

InputElementDiv ::  WhiteSpace  LineTerminator  Comment  CommonToken  DivPunctuator  RightBracePunctuator复制代码

在这个上下文中,遇到 / 会产生 DivPunctuator ,并不会产生 RegularExpressionLiteral。

同样地,在一个 / 是正则开头的上下文中,InputElementRegExp 是对应的目标符。

InputElementRegExp ::  WhiteSpace  LineTerminator  Comment  CommonToken  RightBracePunctuator  RegularExpressionLiteral复制代码

从上面的产生式中可以看出,可能产生 RegularExpressionLiteral 输入元素,但是不会产生 DivPunctuator。

相似的,这里还有另一种目标符, 在目标符 InputElementRegExpOrTemplateTail 所对应的上下文环境中, 除了允许 RegularExpressionLiteral,还允许 TemplateMiddle 和 TemplateTail。最后,InputElementTemplateTail 目标符对应的上下文环境只允许 TemplateMiddle and TemplateTail,不允许RegularExpressionLiteral 出现。

在实现过程中,语法文法分析器(解析器),又称为词法文法分析器(标记器或词法器),将目标符作为参数进行传递,并请求下一个符合这个目标符的输入元素。

Syntactic grammar # 语法文法 #

词法文法法定义了如何从 Unicode 码点构建标记。而语法文法在词法文法的基础上,定义了如何将标记组成正确的程序。

例子:允许合法的标志符 #

将一种新的关键词引入到文法中可能会造成翻天覆地的变化,要是原有的代码已经将这个关键词作为一个标志符在使用呢?

比如,在 await 被定为关键字之前,有人可能写过下面的代码:

function old() {  var await;}复制代码

ECMAScript 文法在不影响代码正常运行下小心地加入了 await 关键字。在异步函数中,await 是一个关键词,因此下面的代码不能正常运行:

async function modern() {  var await; // Syntax error}复制代码

类似地,在非生成器代码中 yield 可以是标志符,而在执行器中禁止使用作为标志符。

要理解 await 是如何被允许用作标志符的,需要理解 ECMAScript-specific 语法文法标记。让我们来看一下吧。

产生式和缩写 #

让我们来看看 VariableStatement 产生式是如何被定义的。乍一看时,下面的文法可能有点可怕。

VariableStatement[Yield, Await] :  var VariableDeclarationList[+In, ?Yield, ?Await] ;复制代码

下标([Yield, Await])和前缀(+In 里面的 +,以及 ?Async 里的 ?)表示什么意思呢?

这种表示法在文法表示法中有解释。

下标是表示一组产生式的缩写,总的来说,是一组产生式左端符号。产生式左端符号有两个参数,可以展开为四个”真正的“产生式左端符号,分别是:VariableStatement, VariableStatement_Yield,VariableStatement_Await, 以及 VariableStatement_Yield_Await.

注意这里的 VariableStatement 只是表示 VariableStatement,不包含 _Await 和 _Yield 在内的。不要把它和 VariableStatement[Yield, Await] 混淆了。

在产生式右端,缩写 +In 的意思是”使用带 _In 的版本“,?Await 意思是”当且仅当产生式左端符号中有 _Await 的话使用带 _Await 的版本“。(?Yield 同理)

第三个缩写,~Foo 没有在这个产生式中出现,它的意思是”使用没有的 _Foo 的版本“。

了解到上面的信息后,我们可以将上面的产生式展开:

VariableStatement :  var VariableDeclarationList_In ;VariableStatement_Yield :  var VariableDeclarationList_In_Yield ;VariableStatement_Await :  var VariableDeclarationList_In_Await ;VariableStatement_Yield_Await :  var VariableDeclarationList_In_Yield_Await ;复制代码

最终,我们需要知道两件事情:

  1. 我们在哪里判断是否处于要使用还是不使用 _Await的情况下?

  2. Something_Await 和 Something(没有 _Await) 产生式的差异在哪里?

_Await 还是没有 _Await? #

让我们先解决第一个问题。不管我们是否将 _Await 放到函数体前,我们都很容易区分异步函数和非异步函数。通过阅读异步函数声明的产生式,我们会发现这个:

AsyncFunctionBody :  FunctionBody[~Yield, +Await]复制代码

注意 AsyncFunctionBody 是没有参数的,参数是在产生式右端被添加到 FunctionBody 中的。

如果我们展开上面的产生式,我们可以得到:

AsyncFunctionBody :  FunctionBody_Await复制代码

换句话说,异步函数有 FunctionBody_Await,这意味着这个函数前的 await 会被当做一个关键字。

另一方面,如果是在一个非异步函数里面,对应的产生式 是:

FunctionDeclaration[Yield, Await, Default] :  function BindingIdentifier[?Yield, ?Await] ( FormalParameters[~Yield, ~Await] ) { FunctionBody[~Yield, ~Await] }复制代码

(FunctionDeclaration 还有一个别的产生式,但是它和我们的代码例子无关。)

为了避免组合式展开,我们会忽略不在这个特别的产生式中使用的 Default 参数。

这个产生式的展开形式为:

FunctionDeclaration :  function BindingIdentifier ( FormalParameters ) { FunctionBody }FunctionDeclaration_Yield :  function BindingIdentifier_Yield ( FormalParameters ) { FunctionBody }FunctionDeclaration_Await :  function BindingIdentifier_Await ( FormalParameters ) { FunctionBody }FunctionDeclaration_Yield_Await :  function BindingIdentifier_Yield_Await ( FormalParameters ) { FunctionBody }复制代码

在这个产生式,只有 FunctionBody 和 FormalParameters(没有 _Yield 和 _Await) ,因为它们在未展开的产生式中都带有 [~Yield, ~Await] 参数。

函数名的处理是不一样的:如果产生式左端符号带上了 _Await 和 _Yield,则右端的函数名也会带上对应的参数。

总的来说:异步函数中有 FunctionBody_Await,非异步函数中有 FunctionBody (不含 _Await)。因为我们谈论的是非生成器函数,因此我们的异步函数例子和非异步函数都不携带 _Yield 参数。

要记住哪个是 FunctionBody 和 FunctionBody_Await 也许很难。在有 FunctionBody_Await 的函数中,await 是标志符,还是关键字呢?

你可以认为 _Await 参数指的是“await 是一个关键字”。这么说未来也不会出错。如果添加一个新的关键字 blob,只能在 “blobby” 函数中使用。非 “blobby” 和非异步,非生成器函数跟现在一样,仍然有 FunctionBody(不带 _Await, _Yield 和 _Blob)。“blobby” 函数中有 FunctionBody_Blob,异步 “blobby” 函数有 FunctionBody_Await_Blob 等等。我们虽然要在产生式中添加 Blob 下标,但是已存在函数的 FunctionBody 扩展形式仍保持原状。

不允许 await 用作标识符 #

接下来,我们需要探究在 FunctionBody_Await 中是如何禁止 await 被当作标记符的。

我们可以通过深入探究产生式,发现从 FunctionBody 到我们先前谈论的VariableStatement 产生式都携带了 _Await 参数。

因此,在一个异步函数中,有 VariableStatement_Await,而在一个非异步函数中,有 VariableStatement。

再更加深入产生式,并且注意到里面的参数。鉴于之前我们已经看到了 VariableStatement 产生式:

VariableStatement[Yield, Await] :  var VariableDeclarationList[+In, ?Yield, ?Await] ;复制代码

所有的 VariableDeclarationList 产生式都携带了参数:

VariableDeclarationList[In, Yield, Await] :  VariableDeclaration[?In, ?Yield, ?Await]复制代码

(这里只展示和我们所举例子相关的产生式。)

VariableDeclaration[In, Yield, Await] :  BindingIdentifier[?Yield, ?Await] Initializer[?In, ?Yield, ?Await] opt复制代码

opt 缩写的意思是产生式右端符号是可选的;实际上这里有两种产生式,一个是带可选的符号,另一个不带。

对于和我们所举例子相关的简单实例来说,VariableStatement 中包含了关键字 var,紧跟着一个没有初始化器的 BindingIdentifier,最后以分号收尾。

是否允许将 await 视为 BindingIdentifier,我们希望结果是下面这样:

BindingIdentifier_Await :  Identifier  yieldBindingIdentifier :  Identifier  yield  await复制代码

这表示不允许在异步函数中把 await 作为标志符,但是在非异步函数中允许作为标志符。

规范中并没有给出这样的定义,但是我们找到了这个产生式:

BindingIdentifier[Yield, Await] :  Identifier  yield  await复制代码

将其扩展,会得到下面的产生式:

BindingIdentifier_Await :  Identifier  yield  awaitBindingIdentifier :  Identifier  yield  await复制代码

(这里省略了例子里不需要的 BindingIdentifier_Yield 和 BindingIdentifier_Yield_Await 产生式。)

看起来似乎 await 和 yield 在任何场景下都可以当作标志符。为什么呢?是不是这篇文章白费功夫?

静态语义来拯救

事实证明,禁止在异步函数中将 await 作为一个标识符使用, 需要用到静态语义

静态语义描述了一些在程序运行之前进行校验的静态规则。

在这里,BindingIdentifier 的静态语义 定义了以下语法文法导向的规则:

BindingIdentifier[Yield, Await] : await 复制代码

如果这个产生式有一个 [Await] 参数,就是一个语法错误.

事实上,这会禁止 BindingIdentifier_Await : await 产生式。

规范中解释,之所以存在这个产生式但是又会被静态语义定义为文法错误,是由于与自动插入分号(ASI)有冲突。

当根据文法产生式无法将一行代码进行解析时,ASI 就会介入。为了满足语句和声明必须以分号结尾的要求,ASI 会尝试添加分号。(在下集我们会深入介绍 ASI)

思考以下的代码(摘自规范中):

async function too_few_semicolons() {  let  await 0;}复制代码

如果文法不允许将 await作为一个标识符使用,ASI 此时就会介入,把代码转化为下面所示文法正确的代码, let 会被当成一个标识符:

async function too_few_semicolons() {  let;  await 0;}复制代码

ASI 的这种介入干涉会让人觉得困惑,因此静态语义才会用来会禁止将 await 当成标识符。

不允许标识符的 StringValues #

这里还有另外一条相关的规则:

BindingIdentifier : Identifier 复制代码

如果产生式有 [Await] 参数并且 Identifier 的 StringValue 是 "await", 就是一个语法错误。

开始可能让人有点困惑。标识符 的定义是这样的:

Identifier :  IdentifierName but not ReservedWord复制代码

await 是一个 ReservedWord,所以 await 怎么可以作为 Identifier 呢?

事实证明,await 不能作为 Identifier,但是它可以 StringValue 为 "await"(字符序列 awat 一种不同的表示方式) 的其他值。

标志符名称的静态语义定义了如何计算标志符名称的 StringValue。举个例子,a 的 Unicode 转义序列是 \u0061,因此 \u0061wait 的 StringValue 是 "await"。词法文法不会把 \u0061wait 当作关键字,而是当作一个 Identifier。静态语义禁止在异步函数中将 \u0061wait 作为变量名。

因此,这样的代码可以运行:

function old() {  var \u0061wait;}复制代码

这样的代码无法运行:

async function modern() {  var \u0061wait; // Syntax error}复制代码

总结 #

在这集内容中,我们了解了词法文法,语法文法,以及用于定义语法文法所用到的缩写。在示例中,我们了解到在异步方法中,await 不能作为标志符使用,但是在非异步方法中可以被当作标志符。


本文地址:百科问答频道 https://www.neebe.cn/wenda/903399.html,易企推百科一个免费的知识分享平台,本站部分文章来网络分享,本着互联网分享的精神,如有涉及到您的权益,请联系我们删除,谢谢!


百科问答
小编:小易整编
相关文章相关阅读
  • 销售额是什么意思?

    销售额是什么意思?

    销售额是指商业企业的一项关键指标,代表企业在一定时期内的营业额,它实时反映企业经营活动的情况,是衡量企业经营能力和效益的重要指标。销售额可以有多种形式,如零售销售额、批发销售额和出口销售额等。销售额的计算方法很简单,即:销售额=销售数量...

  • iTools是什么意思?

    iTools是什么意思?

    iTools是一款功能强大的苹果设备管理工具。它为苹果设备用户提供了便捷的设备管理功能,包括文件传输、备份还原、应用程序管理、屏幕截图、屏幕录制、音乐管理、视频管理等多种功能,而且支持所有的i系列设备,例如iPod、iPhone和iPad...

  • 无损分区工具是什么意思?

    无损分区工具是什么意思?

    无损分区工具是一种硬盘分区方式,是指在分区时保持原有数据不受损坏的一种分区技术,这种分区技术称为“无损分区”。无损分区可以保护硬盘上的数据,在分区时不会破坏硬盘上的任何数据。而且,在数据安全性上也有一定的优势,因为它不会破坏的数据,因此可...

  • 消费凭证是什么意思?

    消费凭证是什么意思?

    消费凭证是指一种书面的证明凭证,当消费者购买商品或服务时,商家会向其提供消费凭证,凭此凭证可以证明消费者购买了特定的商品或服务,以及在这次购买中耗费的钱财数额。消费凭证是一种用于表示购买交易的依据,是为消费者提供全面鉴证的凭证。它可以表明...

  • DSM系统是什么意思?

    DSM系统是什么意思?

    DSM是一种新晋的分布式、可扩展的模块化系统。它是以无服务器、全节点一致性的系统架构为基础,采用多进程系统将服务、运维功能以模块化的方式集合到一起而不会配置服务的的系统设计模型。DSM的核心特点在于采用了一致性的分布式架构,模块化的结构,...

  • 裸金属是什么意思?

    裸金属是什么意思?

    简而言之,裸金属是指被提取出来,没有被添加任何基材的未加工金属。它是将矿石熔化下去后提炼出来的金属,用来做各种活动和企业的原材料。裸金属通常是一种切削或加工的未经热处理的金属。裸金属一般不经过任何表面处理,以自身属性状态出售。它不会涂以油...

  • CVSS是什么意思?

    CVSS是什么意思?

    CVSS(CommonVulnerabilityScoringSystem,中文译名“公共漏洞评分系统”)是一种漏洞分级和评分系统,是由美国国家信息安全标准研究所(NIST)推行的一套机制,它利用特定的参数来构建一个漏洞评级分类体系...

  • 键盘接口是什么意思?

    键盘接口是什么意思?

    键盘接口一般是用于挂接键盘的接口。它也称为键盘插座或键盘接头,它是一个连接键盘和计算机系统之间的重要硬件部件。键盘接口可以用来接收键盘上发出信号,以便在主板上发出完整的指令。键盘接口可以分为有线和无线两种,有线键盘接口是指采用插孔的连接方...

  • 周排行
  • 月排行
  • 年排行

精彩推荐