共计 3160 个字符,预计需要花费 8 分钟才能阅读完成。
这里的内容是读书笔记,仅供自己学习所用。
第 5 章 语法
5.1 语句和表达式
let a = 3 *6;
let b = a;
b ;
3 6 是一个表达式,let a = 3 6 和 let b = a 称为“声明语句”,因为它们声明了变量;
a = 3 * 6 和 b = a 叫做“赋值表达式”。
第三行代码中只有一个表达式 b,同时它也是一个语句(虽然没有太大意义)。这样的情况通常叫做“表达式语句”。
5.1.1 语句的结果值
语句都有一个结果值(undefined 也算)。
5.1.2 表达式的副作用
大部分表达式没有副作用;一些表达式也有副作用。
let a = 42;
let b = a++;
a; // 43
b; // 42
a++ 首先返回变量 a 的当前值 42(再将该值赋给 b),然后将 a 的值加 1。
let a = 42;
a++; // 42
a; // 43
++a; // 44
a; // 44
++ 在前面时,如 ++a,它的副作用(将 a 递增)产生在表达式返回结果值之前,而 a ++ 的副作用则产生在之后。
链式赋值的前提,所有变量都已经被声明过。
5.1.3 上下文规则
在 JavaScript 语法规则中,有时候同样的语法在不同的情况下会有不同的计师。这些语法规则孤立起来会很难理解。
1、大括号
(1)对象常量
// 假定函数 bar()已经定义
let a = {foo: bar()
};
{..}被赋值给 a,因为它是一个对象常量。
(2)标签
{foo: bar();
}
这里只是一个普通的代码块。
这叫作“标签语句”,foo 是语句 bar()的标签。
JSON 被普遍认为是 JavaScript 语言的一个真子集,{“a”: 42}这样的 JSON 字符串会被当作合法的 JavaScript 代码,其实不是的,在控制台中输入是会报错的。
因为标签不允许使用双引号,所以 ”a” 并不是一个合法的标签,因此后面不能带:。
tips: JSON 属性名必须使用双引号!
JSON 的确是 JavaScript 语法的一个子集,但是 JSON 本身并不是合法的 JavaScript 语法。
2、代码块
[] + {}; // "[object object]"
{} + []; // 0
表面上看 + 运算符根据第一个操作数([]或 {})的不同会产生不同的结果,实则不然。
第一行代码中,{}出现在 + 运算符表达式中,因此它被当作一个值(空对象)来处理。[]会被强制类型转换为 ””,而 {} 会被强制类型转换为 ”[object object]”。
第二行代码中,{}被当作一个独立的空代码块(不执行任何操作)。代码块结尾不需要分号,所注意这里不存在语法上的问题。最后 +[]显式强制类型转换为 0。
3、对象解构
从 ES6 开始,{..}也可以用于“解构赋值”,特别是对象的解构。
function getData() {
// ..
return {
a: 42,
b: "foo"
}
}
let {a, b} = getData();
console.log(a, b); // 42 "foo"
{a, b} = .. 就是 ES6 中的解构赋值。
{..}还可以用作函数命名参数的对象解构,方便隐式地用对象属性赋值。
function foo({a, b, c}) {
// 不再需要这样:let a = obj.a, b = obj.b, c= obj.c;
console.log(a, b, c);
}
foo({c: [1, 2, 3],
a: 42,
b: "foo"
}); // 42 "foo" [1, 2, 3]
4、else if 和可选代码块
事实上 JavaScript 没有 else if。
if (a) {..}
else if (b) {..}
else {..}
// 实际上是这样的
if (a) {..}
else {if (b) {..}
else {..}
}
5.2 运算符优先级
JavaScript 中的 && 和 || 运算符返回它们其中一个操作数的值,而非 true 或 false。
&& 运算符先于 || 执行。
5.2.1 短路
对 && 和 || 来说,如果从左边的操作数能够得出结果,就可以忽略右边的操作数,我们将这种现象称为“短路”。
function doSomething(opts) {if (opts && opts.cool) {// ..}
}
opts && opts.cool 中的 opts 条件判断如同一道安全保护,因为如果 opts 未赋值(或者不是一个对象),表达式 opts.cool 会出错。通过使用短路特性,opts 条件判断未通过时 opts.cool 就不会执行,也就不会产生错误!
5.2.2 更强的绑定
&& 运算符的优先级高于 ||,而 || 的优先级又高于? :。
5.2.3 关联
一般来说,运算符的关联不是从左到右就是从右到左,这取决于组合是从左开始还是从右开始。
注:关联和执行顺序不是一回事。
a&&b&&c 这样的表达式就涉及组合(隐式)。
从技术角度来说,因为 && 运算符是左关联(|| 也是),所以 a &&b&&c 会被处理为 (a&&b)&&c。
请好好品味下面这段话:如果 && 是有关联的话会被处理为 a &&(b&&c)。但这并不意味着 c 会在 b 之前执行。右关联不是指从右往左执行,而是指从右往左 组合。任何时候,不论是组合还是关联,严格的执行顺序都应该是从左到右,a,b,然后 c。
? : 是右关联的;= 也是右关联的。
5.2.4 释疑
如果运算符优先级 / 关联规则能够令代码更为简洁,就是用运算符优先级 / 关联规则;而如果()有助于提高代码可读性,就使用()。
5.3 自动分号
有时 JavaScript 会自动为代码行补上缺失的分号,即自动分号插入(ASI)。
注:ASI 只在换行符处起作用,而不会在代码行的中间插入分号。
纠错机制
ASI 是一个“纠错”机制,这里的错误是解析器错误。换句话说,ASI 的目的在于提高解析器的容错性。
tips:建议在所有需要的地方加上分号,将对 ASI 的依赖降到最低。
5.4 错误
JavScript 不仅有各种类型的运行时错误(TypeError、ReferenceError、SyntaxError 等),它的语法中也定义了一些编译时错误。
提前使用变量
ES6 规范定义了一个新概念,叫做 TDZ(暂时性死区)。
TDZ 指的是由于代码中的变量还没有初始化而不能被引用的情况。最直观的就是 let 作用域。
{
a = 2; // ReferenceError!
let a;
}
5.5 函数参数
let b = 3;
function foo(a = 42, b = a + b + 5) {
}
// b=a+b+5 在参数 b(= 右边的 b)的 TDZ 中访问 b,所以会出错。而访问 a 却没有问题
5.6 try..finally
finally 中的 return 会覆盖 try 和 catch 中 return 的返回值。
5.7 switch
可以把 switch 看作 if..else if..else.. 的简化版本:
switch (a) {
case 2:
// 执行一些代码
break;
case 42:
// 执行另外一些代码
break;
default:
// 执行缺省代码
}
a 和 case 表达式的匹配算法与 === 相同。
switch 中 true 和 true 之间仍然是严格相等比较。即如果 case 表达式的结果为真值,但不是严格意义上的 true,则条件不成立,所以在这里使用 || 和 && 等逻辑运算符就很容易掉进坑里。
let a = 'a';
let b = 10;
switch(true) {case (a || b == 10) // 结果是 'a' 而非 true,所以严格相等比较不成立。// 永远执行不到这里
break;
default:
console.log("Oops");
}
5.8 小结
语句和表达:语句就像英语中的句子,而表达式就像短语。
JavaScript 详细定义了运算符的优先级(运算符执行的先后顺序)和关联(多个运算符的组合方式)。
ASI(自动分号插入)是 JavaScript 引擎代码解析纠错机制,它会在需要的地方自动插入分号来纠正解析错误。
JavaScript 中有很多错误类型,分为两大类:早期错误(编译时错误,无法被捕获)和运行时错误(可以通过 try..catch 来捕获)。所有语法错误都是早期错误,程序有语法错误则无法运行。