JS代码需不需要加分号,有这个问题就证明JS加不加分号都行,加了会减轻编译器的工作负担,不加编译器会自动补全分号,减轻开发者的负担。

编译器在处理JS代码的时候,找到分号就直接当语句结束,没找到会按照特定规则对分号进行补全。本文主要内容为:

  • 分号补全规则具体细节
  • 不加分号可能会遇到的问题以及解决方案
  • 分号自动补全规则

    主要规则如下:

    如果遇到换行符,且下一个符号是不符合语法的,会尝试插入分号;

    如果遇到换行符,且规定此处不能有换行符,会自动插入分号;

    源代码结束的时候,不能形成完整的脚本或者模块结构,会自动插入分号。

    下面举几个例子🌰;

    对应规则1

    const foo = 1 /* 这里不加分号 */
    void function(foo){
      console.log(foo);
    }(foo);
    

    第一句let foo = 1后面有换行符,然后遇到void关键字,不符合语法规则,所以会在void前面加分号。

    对应规则2

    const foo = 1;
    const bar = 1;
    const baz = 1;
    foo /* 这里不加分号 */
    ++  /* 这里不加分号 */
    bar /* 这里不加分号 */
    ++  /* 这里不加分号 */
    baz /* 这里不加分号 */
    

    foo++是合法的,但是JS规定(这里的规定后面会细说),foo++之间不能加换行符,所以会在foo后面加入分号,最后代码相当会变成这样:

    const foo = 1;
    const bar = 1;
    const baz = 1;
    ++bar;
    ++baz;
    // foo: 1, bar: 2, baz: 2
    

    对于规则3,加不加分号都无所谓,反正是在代码最后面,不影响前面代码的执行,所以就不举例子了。

    不能有换行符的语法

    在JS标准中,有个叫 [no LineTerminator here] 的规则,规定哪些语法不能加入换行符,如果开发者加了换行符,则JS编译器会无法识别并加入分号。

    带标签的continue语句,不能continue后插入换行;

    带标签的break语句,不能在break后面插入换行;

    return后面不能插入换行;

    后自增、后自减运算符前不能插入换行;

    throwException之间不能插入换行;

    async关键字,后面不能插入换行;

    箭头函数的箭头前,不能插入换行;

    yield之后,不能插入换行。

    下面用代码展示一下:

    带标签的continue语句,不能continue后插入换行:

    tag:for(var j = 0; j < 10; j++)
        for(var i = 0; i < j; i++)
            continue /*no LineTerminator here*/tag
    

    带标签的break语句,不能在break后面插入换行:

    tag:for(var j = 0; j < 10; j++)
        for(var i = 0; i < j; i++)
            break /*no LineTerminator here*/tag
    

    return后面不能插入换行:

    function sum(a, b) {
      return /*no LineTerminator here*/a + b;
    

    后自增、后自减运算符前不能插入换行:

    i/*no LineTerminator here*/++
    i/*no LineTerminator here*/--
    

    throwException之间不能插入换行:

    throw /*no LineTerminator here*/new Exception("error")
    

    async关键字,后面不能插入换行:

    async /*no LineTerminator here*/function foo(){
    const foo = async /*no LineTerminator here*/x => x*x
    

    箭头函数的箭头前,不能插入换行:

    const foo = x /*no LineTerminator here*/=> x*x
    

    yield之后,不能插入换行:

    function *g(){ 
      var i = 0; 
      while(true) {
          yield /*no LineTerminator here*/i++;
    

    以上所有情况都会在JS执行时在/*no LineTerminator here*/处加上分号。但是JS这些规则还不够完善,会有几种情况不符合预期,需要开发者而外注意。

    不加分号需要注意的情况

    下面列举几个不加分号需要注意的情况。

    以括号开头的语句

    (function(a){
        console.log(a);
    })()/*这里没有被自动插入分号*/
    (function(b){
        console.log(b);
    

    这里开发者的意图是想执行两个立即执行函数( IIFE ),但是()后面还是(),在JS看来是合法的语句(如:函数返回一个函数再调用),所以这里不会被自动加入分号,第二个 IIFE 会被当做第一个 IIFE 执行完之后继续执行的入参。然后由于第一个 IIFE 没有返回函数,这里会抛出一个xxx is not a function的类型错误。

    以数组开头的语句

    const arr = [[1, 2]]/*这里没有被自动插入分号*/
    [3, 2, 1, 0].forEach(item => console.log(item))
    

    这里开发者的意图是想给arr遍历赋值,然后遍历一个数组打印每一项的值,但是由于不会自动加分号,这里会被解读成下表运算符和逗号运算符,并且不会报错!!!

    代码解读:首先[3, 2, 1, 0]会被当做[[1, 2]]的下标,然后3, 2, 1, 0执行逗号运算符的结果是0,最后代码等价于:

    const arr = [[1, 2]][0].forEach(item => console.log(item)) // 1, 2
    

    结果完全不符合预期,还不报错,调试起来会异常困难。

    以正则表达式开头的语句

    这个例子让我想到了XSS攻击,利用语法规则对代码解读进行干预,从而窃取用户信息。

    const x = 1/*这里没有被自动插入分号*/
    /(a)/g.test("abc")
    console.log(RegExp.$1)
    

    这里开发者的意图是给变量x赋值,并打印正则匹配的结果。但是由于不会自动加分号,正则表达式的第一个/会被当成除号,1(a)/g.test("abc")结果为NaN,所以实际没有执行test函数。

    这里的函数也不会报错,找起问题来会很dan疼。

    以 Template 开头的语句

    这种情况比较少见,在和正则配合使用的时候会出现问题。

    const f = function(){
      return "";
    const g = f/*这里没有被自动插入分号*/
    `Template`.match(/(a)/);
    console.log(RegExp.$1)
    

    这里开发者的意图是将函数f赋值给函数g,然后执行match方法,查看正则匹配的值。但是由于不会自动加分号,f会和Template连起来解读,作为f的参数传入。

    注意,函数是可以跟着模板字符串表示执行函数,字符串内容会被保存到arguments中。

    var f = function(){
      console.log(arguments);  
    f`Template` // ['Template']
    f`Template${1}` // ['Template', '1']
    

    如何解决上述问题

    最保险的方法当然是自己加分号。

    如果你实在不想加,可以利用一些工具自动补全分号,如配合eslintvs code一起使用,在保存的时候根据规则自动补充分号:

  • eslintrc中加入这条规则:"semi": ["error", "always"]
  • vs code中设置一下:"eslint.autoFixOnSave": true
  • 也可以直接在eslint中限制写法,从源头解决问题。如:使用no-plusplus限制自增符号的使用,用+=的方式替代。

    以上就是本文的全部内容,个人觉得其实加不加分号都行,主要团队内要统一,别一会儿加一会儿不加。而且需要注意的规则也不多,遇到过几次估计都记住了。最后希望大家看完之后有所收获,将bug扼杀在摇篮里。

    ESLint
    私信