JS魔法堂:ASI(自动分号插入机制)和前置分号

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

一、前言                                

  今晚在知乎看到百姓网前端技术专家——贺师俊对《JavaScript 语句后应该加分号么?》的回答,让我又一次看到大牛的风采,实在佩服万分。但单纯的敬佩是不足以回报他如此优秀的文字,必须深入理解文字的含义和背后的原理才不愧呢!

  在这之前我们需要先理解ASI(自动分号插入机制)。

 

二、 Automatic Semicolon Insertion (ASI, 自动分号插入机制)     

   主要参考:http://justjavac.com/javascript/2013/04/22/automatic-semicolon-insertion-in-javascript.html

   从事C#和Java的猴子们都知道分号是用作断句(EOS,end of statement)的,而且必须加分号,否则编译就不通过了。但JavaScript由于存在ASI机制,因此允许我们省略分号。ASI机制不是说在解析过程中解析器自动把分号添加到代码中,而是说解析器除了分号还会以换行为基础按一定的规则作为断句的依据,从而保证解析的正确性。

   首先这些规则是基于两点:

      1. 以换行为基础;

      2. 解析器会尽量将新行并入当前行,当且仅当符合ASI规则时才会将新行视为独立的语句。

   ASI的规则

     1. 新行并入当前行将构成非法语句,自动插入分号

if(1 < 10) a = 1
console.log(a)
// 等价于
if(1 < 10) a = 1;
console.log(a);

     2. 在continue,return,break,throw后自动插入分号

return
{a: 1}
// 等价于
return;
{a: 1};

     3. ++、--后缀表达式作为新行的开始,在行首自动插入分号

复制代码
a
++
c
// 等价于
a;
++c;
复制代码

     4. 代码块的最后一个语句会自动插入分号

function(){ a = 1 }
// 等价于
function(){ a = 1; }

   No ASI的规则

     1. 新行以 ( 开始

复制代码
var a = 1
var b = a
(a+b).toString()
// 会被解析为以a+b为入参调用函数a,然后调用函数返回值的toString函数
var a = 1
var b =a(a+b).toString()
复制代码

     2. 新行以 [ 开始

复制代码
var a = ['a1', 'a2']
var b = a
[0,1].slice(1)
// 会被解析先获取a[1],然后调用a[1].slice(1)。
// 由于逗号位于[]内,且不被解析为数组字面量,而被解析为运算符,而逗号运算符会先执行左侧表达式,然后执行右侧表达式并且以右侧表达式的计算结果作为返回值
var a = ['a1', 'a2']
var b = a[0,1].slice(1)
复制代码

    3. 新行以 / 开始

复制代码
var a = 1
var b = a
/test/.test(b)
// /会被解析为整除运算符,而不是正则表达式字面量的起始符号。浏览器中会报test前多了个.号
var a = 1
var b = a / test / .test(b)
复制代码

    4.   新行以 + 、 - 、 % 和 * 开始

复制代码
var a = 2
var b = a
+a
// 会解析如下格式
var a = 2
var b = a + a
复制代码

   5.  新行以 , 或 . 开始

复制代码
var a = 2
var b = a
.toString()
console.log(typeof b)
// 会解析为
var a = 2
var b = a.toString()
console.log(typeof b)
复制代码

   到这里我们已经对ASI的规则有一定的了解了,另外还有一样有趣的事情,就是“空语句”。

复制代码
// 三个空语句
;;;

// 只有if条件语句,语句块为空语句。
// 可实现unless条件语句的效果
if(1>2);else
  console.log('2 is greater than 1 always!');

// 只有while条件语句,循环体为空语句。
var a = 1
while(++a < 100);
复制代码

 

三、前置分号                          

  重申一下分号的作用——作为语句的断言(EOS),目的是让解析器正确解析程序。那既然存在ASI机制,那为什么还有那么多团队的代码规范中还规定必须写分号呢?不外乎三个原因:1. 因为存在No ASI的情况,懒得记忆这些特例;2. 团队的工程师需要兼顾前后端开发(苦逼如我~~),而后端采用Java、C#或PHP,保持两端代码规范接近管理成本较低;3. 旧有的规范就是这样,现在也没必要改了。

  对于省略分号后代码压缩工具会出问题,jslint会对无分号的代码报warning等问题,贺师俊已经在回复中对其进行详细说明了。因此分不分号纯属个人和团队的偏好问题,当然也可以混合使用咯(下面借一下大牛@高原的图)

  对于我这种能少敲键盘则少敲,能不用鼠标就不用的大懒虫,自然而然加入到“无分号党”的怀抱咯,入党的前提条件就是记住一下规则来应付No ASI的情况:

  在以 ([/+- 开头的语句前加分号(由于正常写法均不会出现以 .,*% 作为语句开头,因此只需记住前面5个即可,你看能懒则懒哦)

   然后就是通过合理的缩进空白行来使代码结构更为清晰(coffeescript不就是这样的吗?!)

  示例:

复制代码
;(function(exports, undefined){
  var getKeys = Object.getOwnPropertyName 
     && Object.getOwnPropertyName.bind(Object)
       || function(obj){
         var keys = []
          for (var key in obj)
            keys.push(key)
          return keys
       }

    var each = exports.forEach = exports.each = function(arrayLike, fn, ctx){
      if(arrayLike == undefined) return

       var isObj = arrayLike.length !== +arrayLike.length
         ,keys = isObj ? getKeys(arrayLike) : arrayLike
          ,len = keys.length
          ,idx
       for (var i = 0; idx = isObj ? keys[i] : i, i < len; ++i)
         fn.call(ctx, idx, arrayLike[idx])
    }
}(new Function('return this')(), void 0))

forEach({'s':1,'c':2}, function(i, item){
  console.log(i + '  ' + item)
})
forEach([1,2], function(i, item){
  console.log(i + '  ' + item)
})
复制代码

  现在我们就可以安心做“无分号党”了哦!

 

四、总结                               

  ASI再一次展示JavaScript语法的自由度之高,因此对于团队开发而言代码规范显得如此的重要。而对语法的掌握程度也从另一个侧面反映前端工程师的技术水平。看来要继续努力才行了!

  尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4154503.html ^_^肥子John

 

五、其他参考                             

  http://justjavac.com/javascript/2013/04/22/automatic-semicolon-insertion-in-javascript.html

  http://inimino.org/~inimino/blog/javascript_semicolons

  http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding

如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!

posted @ 2014-12-10 15:20 ^_^肥仔John 阅读( 1201) 评论( 0) 编辑 收藏
 

公告

肥仔John@github
作品:
本文转自
^_^肥仔John博客园博客,原文链接: http://www.cnblogs.com/fsjohnhuang/p/4154503.html,如需转载请自行联系原作者
 
 
相关文章
|
2月前
|
设计模式 JavaScript 前端开发
深入理解 JavaScript 中的绑定机制(下)
深入理解 JavaScript 中的绑定机制(下)
|
2月前
|
前端开发 JavaScript UED
深入理解JavaScript中的事件循环机制
JavaScript中的事件循环机制是其异步编程的核心,深入理解该机制对于开发高效、流畅的前端应用至关重要。本文将介绍事件循环的工作原理、常见的事件循环模型,以及如何利用这些知识解决前端开发中的常见问题。
|
27天前
|
设计模式 JavaScript 前端开发
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要
33 7
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
|
14天前
|
JavaScript 前端开发 API
js 运行机制(含异步机制、同步任务、异步任务、宏任务、微任务、Event Loop)
js 运行机制(含异步机制、同步任务、异步任务、宏任务、微任务、Event Loop)
8 0
|
2月前
|
缓存 移动开发 JavaScript
WKWebView对网页和js,css,png等资源文件的缓存机制及如何刷新缓存
WKWebView对网页和js,css,png等资源文件的缓存机制及如何刷新缓存
54 1
|
1月前
|
JavaScript 前端开发
深入解析JavaScript中的面向对象编程,包括对象的基本概念、创建对象的方法、继承机制以及面向对象编程的优势
【6月更文挑战第12天】本文探讨JavaScript中的面向对象编程,解释了对象的基本概念,如属性和方法,以及基于原型的结构。介绍了创建对象的四种方法:字面量、构造函数、Object.create()和ES6的class关键字。还阐述了继承机制,包括原型链和ES6的class继承,并强调了面向对象编程的代码复用和模块化优势。
27 0
|
2月前
|
开发框架 JavaScript 前端开发
JavaScript的事件循环机制是其非阻塞I/O的关键
【5月更文挑战第13天】JavaScript的事件循环机制是其非阻塞I/O的关键,由调用栈、事件队列和Web APIs构成。当异步操作完成,回调函数进入事件队列,待调用栈空时,事件循环取队列中的任务执行。在游戏开发中,事件循环驱动游戏循环更新,包括输入处理、游戏逻辑更新和渲染。示例代码展示了如何模拟游戏循环,实际开发中则常使用游戏框架进行抽象处理。
51 4
|
2月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包机制
闭包是JavaScript中一个重要且常被误解的概念。本文将深入探讨闭包的本质、工作原理以及在实际开发中的应用。通过详细解析闭包的定义、作用域链、内存管理等方面,读者将对闭包有更清晰的理解,并能够运用闭包解决实际开发中的问题。
|
2月前
|
前端开发 JavaScript UED
JavaScript 的事件循环机制是其非阻塞 I/O 模型的核心
【5月更文挑战第9天】JavaScript的事件循环机制是其非阻塞I/O的关键,通过单线程的调用栈和任务队列处理异步任务。当调用栈空时,事件循环从任务队列取出一个任务执行,形成循环。异步操作完成后,回调函数进入任务队列,等待被事件循环处理。微任务如Promise回调在每个宏任务结束后执行。此机制确保JavaScript能高效处理异步操作,不阻塞主线程,提供流畅的用户体验。
27 2