你不知道的JavaScript(中卷) 第五章

这里的内容是读书笔记,仅供自己学习所用。


第5章 语法

5.1 语句和表达式

let a = 3 *6;
let b = a;
b ;

36是一个表达式,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来捕获)。所有语法错误都是早期错误,程序有语法错误则无法运行。

相关推荐