1.语句和表达式
表达式可以返回一个结果值。
语句包含表达式,可以比表达式长(就像句子可以包含短语)。语句也有结果值。
1.1 语句的结果值
在控制台输入一个语句,执行完,控制台输出的就是结果值。
在代码中没法取到结果值。硬要看的话,可以用eval来看。实际开发中不推荐用eval。
目前语句的结果值还无关紧要,以后它可能会有比较重要的意义。
1.2 表达式的副作用
表达式会造成副作用的情况:
①有些函数调用
②递增运算符++和递减运算符--
③delete运算符,操作成功返回true,失败则返回false,其副作用是删除对象的属性
tips:逗号运算符可将多个语句连成一个,例:let b = (a++, a),b得到的就是a自增之后的值。
④=运算符,a=42返回42,其副作用是给a赋值
tips:
let matches
matches = str.match(/[aeiou]/g) // 提取所有元音字母
if (matches) {...}
就可以写成:
let matches
if (matches = str.match(/[aeiou]/g)) {...}
(作者说的副作用啥的我能理解,但等号运算符,那个赋值短语直接拿去做判断,还是感觉不确定靠谱不靠谱,赋值短语的值就等于赋的值吗??)
1.3 上下文规则
在js中同样的语法在不同情况下会有不同的解释
①大括号
目前用到大括号的情况有两种(以后还会更多):
(1)用大括号定义对象。如 let a = {foo: bar()} // 假设bar函数已经定义过
(2)标签。如{foo: bar()} // 假设bar函数已经定义过
此时大括号会被当作代码块。foo: bar()会被当作标签语句。
所谓标签语句,就是:
a.如果给循环加了标签,它可以跟break或continue结合使用。break 某标签,表示跳出标签指定的那层循环。continue 某标签,表示执行标签指定的那层循环的下一轮循环。
b.如果给代码块加了标签,它可以跟break结合使用,break 某标签,表示跳过标签指定的代码块的剩余语句。
这两种写法都不太常见,也不建议使用,如果要使用务必写注释。
(但是我自己在控制台试了下{foo: bar()}并没被当作标签啊,貌似foo得写在花括号外面才会被当作标签。。迷惑)
②代码块
{} + [] // 0
是因为前面的{}会被当作空代码块,而不是一个对象常量。所以结果等于+ [],即0。
③解构赋值
④if else和可选代码块
JavaScript没有else if。常用的else if,其实是,else后面跟着单独一条语句,而那个语句恰好是if...
2.运算符优先级
首先作者举了3个例子,分别说明:a.逗号操作符连接若干个语句时,逗号操作符的优先级最低;b.&&操作符优先级高于=操作符;c.&&操作符优先级高于||。
然后作者说,JavaScript规范对运算符优先级并没有一个集中的介绍,因此需要从语法规则中间逐一了解。(不过也早有人整理出专门的文章了)
2.1 短路
对&&和||来说,如果从左边的操作数能够得出结果,就可以忽略右边的操作数。我们将这种现象称为“短路”。
实际开发中,短路经常用来做判断,如果左边的操作数满足某条件,后续的操作就可以略过。
2.2 更强的绑定
&&运算符的优先级高于||,而||的优先级又高于? :。
这个规则的另外一种讲法:“&&和||比?:的绑定更强”。
2.3 关联
&&运算符是左关联(||也是),所以a && b && c会被处理为(a&& b) &&c。
像&&和||左关联还是右关联计算结果都是一样。
但三目运算符?:就不是这样a ? b : c ? d : e是左关联还是右关联就会影响到结果。
三目运算符是右关联的,a ? b : c ? d : e 相当于 a ? b : (c ? d : e)
=赋值运算符也是右关联。
2.4
有时候用()把短语包住,可以使代码更易懂。有时候,不用(),仅凭运算符优先级,可以使代码更简洁。建议根据实际情况斟酌后选择。
3.自动分号
JavaScript会自动为代码行补上缺失的分号,即自动分号插入(Automatic SemicolonInsertion, ASI)。
ASI只在换行符处起作用,而不会在代码行的中间插入分号,并且只有在代码行末尾与换行符之间除了空格和注释之外没有别的内容时,它才会这样做。
仔细阅读规范就会发现,ASI实际上是一个“纠错”(error correction)机制。这里的错误是指解析器错误。换句话说,ASI的目的在于提高解析器的容错性。
依赖于ASI实际上是将换行符当作有意义的“空格”来对待。因此作者说,不要依赖ASI,该加分号的地方自己加上分号。
4.错误
JavaScript不仅有各种类型的运行时错误(TypeError、ReferenceError、SyntaxError等),它的语法中也定义了一些编译时错误。
在编译阶段发现的代码错误叫作“早期错误”(early error)。语法错误是早期错误的一种。
然后作者介绍了一种从ES6起才有的语法错误,叫TDZ(Temporal Dead Zone,暂时性死区)。
指的是由于代码中的变量还没有初始化而不能被引用的情况。
例:
{
a = 2; // ReferenceError!
let a;
}
又例:
{
typeof a; // ReferenceError!
typeof b; // undefined
let a;
}
5. 函数参数
函数参数中的TDZ:
var b = 3;
function foo(a = 42, b = a + b + 5) {...}
a + b + 5
在参数b的TDZ中访问b,会报错,但它访问a是没问题的,因为此时刚好跨出了参数a的TDZ。
function foo(a = 42, b = a + 1) {...}
此处42是a的默认值,a + 1是b的默认值。
如果没有传入参数,或传入undefined,则取该参数的默认值。
但是没传参数和传入undefined有一个区别,arguments的length会不一样,不传的话length为0,传的话arguments中会出现一个值位undefined的单元。
不建议用arguments,自从ES6有了剩余参数...之后,就更没必要了。
6.try..finally
我们知道finally中的代码总是会在try之后执行,如果有catch的话则在catch之后执行。
假如try中有return语句,会怎么样呢?
funcion foo() {
try{
return 42;
}
finally{
console.log("Hello");
}
}
console.log(foo());
// Hello
// 42
这里return 42先执行,并将foo()函数的返回值设置为42。然后try执行完毕,接着执行finally。最后foo()函数执行完毕,console.log(..)显示返回值。
如果try中有throw语句也是,finally代码块会被执行,然后函数抛出异常。
如果finally中抛出异常,则此前try中(如果有)已有的return值会被丢弃,函数抛出异常。
try中有continue语句也一样,try后面如果有finally,finally还是会被执行,再continue去下一轮循环。
另外,finally中的return会覆盖try和catch中return的返回值。(要注意,在finally中return后面如果不跟值,则会返回前面的return设定的返回值。)
finally中如果有break标签,还可以跳过try中的return:
function foo() {
bar: {
try {
return 42;
}
finally {
break bar;
}
}
console.log('Creazy');
return 'Hello';
}
console.log(foo());
// Crazy
// Hello
7.switch
注意switch case中的匹配是===,严格相等才匹配。
switch中的break可以带标签。
一个奇葩例子:
var a = 10;
switch(a) {
case 1:
case 2:
// 永远执行不到这里
default:
console.log("default");
case 3:
console.log("3");
break;
case 4:
console.log("4")
}
// default
// 3
首先遍历并找到所有匹配的case,如果没有匹配则执行default中的代码。因为其中没有break,所以继续执行已经遍历过的case 3代码块,直到break为止。