Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ECMAScript的运算符、自动分号插入 #1

Open
fengma1992 opened this issue Mar 29, 2018 · 0 comments
Open

ECMAScript的运算符、自动分号插入 #1

fengma1992 opened this issue Mar 29, 2018 · 0 comments

Comments

@fengma1992
Copy link
Owner

fengma1992 commented Mar 29, 2018

1. ECMAScript 运算符优先级

运算符 描述
. [] () 字段访问、数组下标、函数调用以及表达式分组
++ — - + ~ ! delete new typeof void 一元运算符、返回数据类型、对象创建、未定义值
* / % 乘法、除法、取模
+ - + 加法、减法、字符串连接
<< >> >>> 移位
< <= > >= instanceof 小于、小于等于、大于、大于等于、instanceof
== != === !== 等于、不等于、严格相等、非严格相等
& 按位与
^ 按位异或
   
&& 逻辑与
   
?: 条件
= oP= 赋值、运算赋值
, 多重求值

2. ECMAScript 一元运算符(+、-)

一元运算符只有一个参数,即要操作的对象或值。它们是 ECMAScript 中最简单的运算符。

delete, void, --, ++W3school里有详细讲解。

image

其中涉及到几个ECMAScript定义的抽象操作,ToNumber(x),ToPrimitive(x)等等 下一章详细解答,下面出现的抽象定义也同理,先不管这个,有基础想深入了解可以提前熟读ECMAScript5规范(点击查看)

一元加法对数字无作用,但会把字符串转换成数字。

与一元加法运算符相似,一元减法运算符也会把字符串转换成近似的数字,此外还会对该值求负

3. ECMAScript 加法运算符(+)

在多数程序设计语言中,加性运算符(即加号或减号)通常是最简单的数学运算符。
在 ECMAScript 中,加性运算符有大量的特殊行为。

ES5规范:
image
第七条,运算数中至少有一个为字符串,则将运算数转为字符串,结果为两个字符串相加。这就是为什么1 + '1' = '11'而不是2的原因。

var result = 5 + 5; //两个数字
alert(result);      //输出 "10"
var result2 = 5 + "5";  //一个数字和一个字符串
alert(result);      //输出 "55"

在处理特殊值时,ECMAScript 中的加法也有一些特殊行为:

  • 某个运算数是 NaN,那么结果为 NaN。
  • -Infinity 加 -Infinity,结果为 -Infinity。
  • Infinity 加 -Infinity,结果为 NaN。
  • +0 加 +0,结果为 +0。
  • -0 加 +0,结果为 +0。
  • -0 加 -0,结果为 -0。

4. ECMAScript 减法运算符(-)

减、乘和除没有加法特殊,都是一个性质,这里我们就单独解读减法运算符(-)
image
与加法运算符一样,在处理特殊值时,减法运算符也有一些特殊行为:

某个运算数是 NaN,那么结果为 NaN。

  • Infinity 减 Infinity,结果为 NaN。
  • -Infinity 减 -Infinity,结果为 NaN。
  • Infinity 减 -Infinity,结果为 Infinity。
  • -Infinity 减 Infinity,结果为 -Infinity。
  • +0 减 +0,结果为 +0。
  • -0 减 -0,结果为 -0。
  • +0 减 -0,结果为 +0。
  • 某个运算符不是数字,那么结果为 NaN。
    注释:如果运算数都是数字,那么执行常规的减法运算,并返回结果。

5. ECMAScript 前自增运算符(++)

image

直接从 C(和 Java)借用的两个运算符是前增量运算符和前减量运算符。
所谓前增量运算符,就是数值上加 1,形式是在变量前放两个加号(++):

var num = 10;
++num;

第二行代码把 iNum 增加到了 11,它实质上等价于:

var num = 10;
num = num + 1;

6. ECMAScript 自动分号(;)插入

尽管 JavaScript 有 C 的代码风格,但是它不强制要求在代码中使用分号,实际上可以省略它们。

JavaScript 不是一个没有分号的语言,恰恰相反,它需要分号来就解析源代码。 因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。

6.1 例子

var foo = function() {
} // 解析错误,分号丢失
test()

自动插入分号,解析器重新解析。

var foo = function() {
}; // 没有错误,解析继续
test()

6.2 工作原理

下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。

(function(window, undefined) {
    function test(options) {
        log('testing!')

        (options.list || []).forEach(function(i) {

        })

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        )

        return
        {
            foo: function() {}
        }
    }
    window.test = test

})(window)

(function(window) {
    window.someLibrary = {}
})(window)

下面是解析器插入分号后的结果。

(function(window, undefined) {
    function test(options) {

        // 没有插入分号,两行被合并为一行
        log('testing!')(options.list || []).forEach(function(i) {

        }); // <- 插入分号

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        ); // <- 插入分号

        return; // <- 插入分号, 改变了 return 表达式的行为
        { // 作为一个代码段处理
            foo: function() {}
        }; // <- 插入分号
    }
    window.test = test; // <- 插入分号

// 两行又被合并了
})(window)(function(window) {
    window.someLibrary = {}; // <- 插入分号
})(window); //<- 插入分号

解析器显著改变了上面代码的行为,在另外一些情况下也会做出错误的处理。

6.3 ECMAScript对自动分号插入的规则

查看7.9章节,看看其中插入分号的机制和原理,清楚以后就可以尽量少踩坑。
image
image
image
看着头晕,看看具体的总结说明, 化抽象为具体 。
首先这些规则是基于两点:

  • 以换行为基础;
  • 解析器会尽量将新行并入当前行,当且仅当符合ASI规则时才会将新行视为独立的语句。

6.3.1 ASI的规则
a. 新行并入当前行将构成非法语句,自动插入分号。

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

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

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

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

a
++
c
// 等价于
a;
++c;

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

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

6.3.2 No ASI的规则
a. 新行以 ( 开始

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

b. 新行以 [ 开始

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) // 2

c. 新行以 / 开始

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

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

var a = 2
var b = a
+a
// 会解析如下格式
var a = 2
var b = a + a

e. 新行以 , 或 . 开始

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);

6.4 结论

提倡将花括号和相应的表达式放在一行, 对于只有一行代码的 if 或者 else 表达式,也不应该省略花括号。 这些良好的编程习惯不仅可以提到代码的一致性,而且可以防止解析器改变代码行为的错误处理。
关于JavaScript 语句后应该加分号么?(点我查看)我们可以看看知乎上大牛们对着个问题的看法。

原文参见:从++[[]][+[]]+[+[]]==10?深入浅出弱类型JS的隐式转换

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant