热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

备胎的自我教养——趣谈JavaScript中的ASI(AutomaticSemicolonInsertion)

什么是ASI?自动分号插进去(automaticsemicoloninsertion,ASI)是一种顺序剖析手艺,它在JavaScript顺序的语法分析(parsing)阶段起作用

《备胎的自我教养——趣谈 Javascript 中的 ASI (Automatic Semicolon Insertion)》

什么是 ASI ?

自动分号插进去 (automatic semicolon insertion, ASI) 是一种顺序剖析手艺,它在 Javascript 顺序的语法分析 (parsing) 阶段起作用。

依据 ES2015 范例,某些(不是悉数) Javascript 语句需要用 ; 来示意语句的完毕;然则为了轻易誊写,在某些状况下这些分号是可以从源码中省略的,此时我们称 ; 被 parser 自动插进去到标记流 (token stream) 中,这类机制称为 ASI。

所谓的“自动分号插进去”着实只是一种抽象的形貌,parser 并没有真的插进去一个个分号。ASI 只是示意编译器准确明白了顺序员的企图。听起来就像编译器对顺序员说:“Hey,哥们!虽然这里你没写分号,但我晓得你想说这条语句已完毕了。”

需要用 ; 来示意完毕的语句是:

  • 空语句
  • letconstimportexport 开首的声明语句
  • var 开首的变量声明语句
  • 表达式语句
  • debugger 语句
  • continue 语句
  • break 语句
  • return 语句
  • throw 语句

并不是一切的 Javascript 语句都需要用 ; 示意完毕,比方:

  • 块语句
  • if 语句
  • try 语句

这些语句原本就不需要 ; 示意完毕。

举例来说,函数声明 (Function declaration, FD) 不需要以分号完毕:

function add10(num) {
return num + 10;
} // I don't need a semicolon here

假如你弄巧成拙地在 FD 以后写了一个 ;,它会被剖析为一条空语句。

ASI 划定规矩

ASI 是备胎(第二挑选)

编译器不会优先启用 ASI 机制。实际上,在碰到行完毕符 (Line Terminator) 时,编译器老是先试图将行完毕符分开的标记流看成一条语句来剖析(着实有少数几个惯例:returnthrowbreakcontinueyield++--,随后会引见),着实不符合准确语法的状况下,才会退而求其次,启用 ASI 机制,将行完毕符分开的标记流看成两条语句(俗称,插进去分号)。来看下面的例子:

var a = 0
var b = 1

这个简朴代码段的标记流为:

var a = 0 \n var b = 1

parser 从左至右剖析这个标记流,剖析过程当中它碰到了换行符 LF ( \n , 行完毕符之一)。它看起来如许喃喃自语:“我碰到了一个换行符,让我先尝尝去掉它,把这个代码段看成一条语句尝尝!”

因而 parser 实际上先剖析了如许一条语句:

var a = 0 var b = 1
// Uncaught SyntaxError: Unexpected token var

很显然这是一条有语法错误的语句,此路不通!

parser 说:“这个标记流假如看成一条语句的话,是有语法错误的!这该怎么办呢?我是否是要就此摒弃、直接抛出语法错误呢?不!我可是要成为海贼王的男子!我要启用 ASI 机制尝尝。”

因而不折不挠的 parser 又剖析了下面的语句:

var a = 0; var b = 1 // legal syntax

Bingo! 没有 SyntaxError ,剖析经由过程!

parser 因而自满地对顺序员说:“Hey,哥们!虽然在 \n 前面你没写分号,但我晓得你想说 var a = 0 这条赋值语句已完毕了!”

“高!着实是高!”

软弱的标记、被误会的源码

需要注重的是,parser 对标记流的这类处置惩罚机制有时会致使它误会顺序员的企图。

var a = [1, [2, 3]]
[3, 2, 1].map(function(num) {
return num * num;
})

由于 parser 老是优先将换行符前后的标记流看成一条语句剖析,parser 实际上先剖析了下面的语句:

var a = [1, [2, 3]][3, 2, 1].map(function(num) {
return num * num;
})

这是一条语法准确的语句。它的寄义是:先声明变量a ,对 [1, [2, 3]][3, 2, 1] 求值以后获得数组 [2, 3] ,对 [2, 3] 举行 (num) => num * num 映照操纵获得 [4, 9],将数组 [4, 9] 赋给变量 a

( 最先的语句,比方 IIFE ,也会致使顺序被 parser 误会。

(function fn() {
return fn;
})()
(function() {
console.log('我会显现在控制台吗?');
})()

它等价于

// 一条函数一连挪用语句
(function fn() {
return fn;
})()(function() {
console.log('我会显现在控制台吗?');
})() // => fn

/ 最先的语句,通常是正则表达式放在语句肇端处(这类状况比较少见),也会致使顺序被 parser 误会。

var a = 2
/error/i.test('error')

它等价于

var a = 2 / error / i.test('error')
// => Uncaught ReferenceError: error is not defined

需要注重的是,虽然 var a = 2 / error / i.test('error') 会抛出 ReferenceError 非常,但它是一条没有语法错误 (SyntaxError) 的语句。换句话说,该语句在 parser 眼里是一条语法准确的语句,因而 parser 不会启用 ASI 机制。

语句肇端处的 +- 也会致使源码被误会(越发少见)。

var num = 5
+new Date - new Date(2009, 10)

等价于

var num = 5 + new Date - new Date(2009, 10)

源码的企图被 parser 误会,有两个必要条件:

  1. parser 优先将行完毕符前后的标记流按一条语句剖析,这是 ECMAScript 规范的划定,一切 parser 必需要按此请求完成。
  2. 行完毕符以后的标记 (token) 有二义性,使得该标记与上条语句可以无缝对接,不致使语法错误。

实际上,有二义性的标记原本就不多,能致使源码企图被转变的标记数来数去就只有 [(/+- 这五个罢了。我们可以把它们明白成“软弱的标记”,在它们前面显式地加上防御性分号 (defensive semicolon) 来庇护其寄义不被转变。

限定发生式——备胎转正

前文说到,ASI 是一种备用挑选。然则在 ECMAScript 中,有几种特别语句是不许可行完毕符存在的。假如语句中有行完毕符,parser 会优先以为行完毕符示意的是语句的完毕,这在 ECMAScript 规范中称为限定发生式 (restricted production)。

浅显地说,在限定发生式中,parser 优先启用 ASI 机制。

一个典范限定发生式的例子是 return 语句。

function a() {
return
{};
}
a() // => undefined

根据平常剖析划定规矩,假如 ASI 是第二挑选,那末 parser 优先疏忽 \n ,该代码段应与下面的顺序无异:

function a() {
return {};
}
a() // => {} (empty object)

然则现实并不是如此,由于 ECMAScript 规范对正当的 return 语句做了以下限定:

ReturnStatement:
return [no LineTerminator here] Expression ;

return 语句中是不许可在 return 关键字以后涌现行完毕符的,所以上面的代码段着实等价于:

function a() {
return; // ReturnStatement
{} // BlockStatement
; // EmptyStatement
}
a() // => undefined

函数体内的代码被剖析为 return 语句、块语句、空语句三条零丁的语句。

规范划定的别的限定发生式有:

  • continue 语句
  • break 语句
  • throw 语句
  • 箭头函数 (箭头左边不许可有行完毕符)
  • yield 表达式
  • 后自增/自减表达式

这些状况都不许可有换行符存在。

a
++
b

被剖析为

a;
++b;

ES2015 规范给出了关于限定发生式的编程发起:

  • A postfix ++ or -- operator should appear on the same line as its operand. (后自增运算符或后自减运算符应与它的操纵数处于统一行。)
  • An Expression in a return or throw statement or an AssignmentExpression in a yield expression should start on the same line as the return, throw, or yield token. (returnthrow 语句中的表达式以及 yield 表达式中的赋值表达式应与 returnthrowyield 这些关键字处于统一行。)
  • An IdentifierReference in a break or continue statement should be on the same line as the break or continue token. (breakcontinue 语句中的标署名应与 breakcontinue 关键字处于统一行。)

言而总之,总而言之,ES2015 规范这一节就通知你一件事:在限定临盆式中别换行,换行就自动插进去分号。

for 轮回与空语句——永不运用的备胎

ASI 不适用于 for 轮回头部,即 parser 不会在这里自动插进去分号。

var a = ['once', 'a', 'rebound,', 'always', 'a', 'rebound.']
var msg = ''
for (var i = 0, len = a.length
i i++) {
msg += a[i] + ' '
}
console.log(msg)

好吧,或许你愿望 parser 在 a.length 背面和 i 背面自动为你插进去分号,补全这个 for 轮回语句,然则 parser 不会在 for 轮回的头部启用 ASI 机制。parser 起首尝试按一条语句剖析

(var i = 0, len = a.length \n i

这个标记流,发明它并不是一条正当语句后,就直接抛出了语法错误 Uncaught SyntaxError: Unexpected Identifier,根本不尝试补全分号。

所以 for 轮回头部的分号必需要显式地写出:

var a = ['once', 'a', 'rebound,', 'always', 'a', 'rebound.']
var msg = ''
for (var i = 0, len = a.length;
i i++) {
msg += a[i] + ' '
}
console.log(msg)
// => 'once a rebound, always a rebound.' (一朝为备胎,永远为备胎)

类似地,有特别寄义的空语句也不可以省略分号:

function infiniteLoop() {
while('a rebound is a rebound is a rebound')
}

此段代码是不正当的语句,parser 会抛出语法错误 Uncaught SyntaxError: Unexpected token } 。这是由于轮回体中作为空语句而存在的 ; 不能省略。

// legal syntax
function infiniteLoop() {
while('a rebound is a rebound is a rebound');
}
// it is true that a rebound is a rebound is a rebound (备胎就是备胎,这是真谛。)

总结

ASI 机制的存在为 Javascript 顺序员供应了一种挑选:你可以省略源码中的绝大部分 ; 而不影响顺序的准确剖析。与 IT 业界的 “Vim 和 Emacs 哪一个是更好的编辑器” 一样,Javascript 社区隔一段时间就会涌现“该不该写分号”如许的看法之争。本文并不是想证实哪一种看法更好,而是关注 ASI 机制自身的一些风趣现实。即便是坚决的无分号党,也不能不认可,有些分号是不能省略的。这些不能省略的分号有:

  • for 轮回头部的分号
  • 作为空语句存在的分号
  • 以 5 个“软弱标记”开首的语句之前的分号 (严厉来说,此处的分号不是必需的;由于除了运用分号,还可以用种种 hack 要领,比方 void)

而关于坚决的分号党,有一个现实也不能不认可,那就是你的顺序中很可能有 99% 的分号都是过剩的!假如你想尝试一下不写分号,可以根据下面的步骤:

  1. 删掉你一切语句结尾处的分号
  2. 假如你的语句开首是 [(,在它前面加一个分号。Over!

相干材料

  1. Effective Javascript: 68 Specific Ways to Harness the Power of Javascript. Item. 6
  2. ES2015 Spec Section 11.9.1: Rules of Automatic Semicolon Insertion
  3. An Open Letters to Javascript Leaders Regarding Semicolons
  4. Javascript Semicolon Insertion. Everything you need to know
  5. A Bit of Advice for the Javascript Semicolon Haters
  6. Automatic semicolon insertion in Javascript

推荐阅读
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • 本文介绍了如何使用elementui分页组件进行分页功能的改写,只需一行代码即可调用。通过封装分页组件,避免在每个页面都写跳转请求的重复代码。详细的代码示例和使用方法在正文中给出。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • 先看看ElementUI里关于el-table的template数据结构:<template><el-table:datatableData><e ... [详细]
author-avatar
梦的影子2502931765
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有