热门标签 | 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

推荐阅读
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • EST:西湖大学鞠峰组污水厂病原菌与土著反硝化细菌是多重抗生素耐药基因的活跃表达者...
    点击蓝字关注我们编译:祝新宇校稿:鞠峰、袁凌论文ID原名:PathogenicandIndigenousDenitrifyingBacte ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • C语言中全部可用的数学函数有哪些?2.longlabs(longn);求长整型数的绝对值。3.doublefabs(doublex);求实数的绝对值。4.doublefloor(d ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • DVWA学习笔记系列:深入理解CSRF攻击机制
    DVWA学习笔记系列:深入理解CSRF攻击机制 ... [详细]
  • 深入理解排序算法:集合 1(编程语言中的高效排序工具) ... [详细]
  • 本指南详细介绍了在Linux环境中高效连接MySQL数据库的方法。用户可以通过安装并使用`mysql`客户端工具来实现本地连接,具体命令为:`mysql -u 用户名 -p 密码 -h 主机`。例如,使用管理员账户连接本地MySQL服务器的命令为:`mysql -u root -p pass`。此外,还提供了多种配置优化建议,以确保连接过程更加稳定和高效。 ... [详细]
  • Python内置模块详解:正则表达式re模块的应用与解析
    正则表达式是一种强大的文本处理工具,通过特定的字符序列来定义搜索模式。本文详细介绍了Python内置的`re`模块,探讨了其在字符串匹配、验证和提取中的应用。例如,可以通过正则表达式验证电子邮件地址、电话号码、QQ号、密码、URL和IP地址等。此外,文章还深入解析了`re`模块的各种函数和方法,提供了丰富的示例代码,帮助读者更好地理解和使用这一工具。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • HBase Java API 进阶:过滤器详解与应用实例
    本文详细探讨了HBase 1.2.6版本中Java API的高级应用,重点介绍了过滤器的使用方法和实际案例。首先,文章对几种常见的HBase过滤器进行了概述,包括列前缀过滤器(ColumnPrefixFilter)和时间戳过滤器(TimestampsFilter)。此外,还详细讲解了分页过滤器(PageFilter)的实现原理及其在大数据查询中的应用场景。通过具体的代码示例,读者可以更好地理解和掌握这些过滤器的使用技巧,从而提高数据处理的效率和灵活性。 ... [详细]
  • 面向切面编程(AOP)是Spring框架的两大核心概念之一,另一个核心概念是控制反转(IoC)。AOP通过在应用程序中分离横切关注点,如日志记录、事务管理和安全性,从而提高代码的模块化和可维护性。本文将深入探讨AOP的核心概念和术语,帮助读者更好地理解和应用这一重要技术。 ... [详细]
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社区 版权所有