第5章 表达式和运算符
表达式是一种特殊的语句,它可以计算出一个值。非表达式语句不产生值。
非表达式语句通常会产生某种结果,但是只有表达式语句会对生成的结果做显式的转换。
因为表达式能解析成值,所以可以将它们与其他表达式组合在一起,进而将返回的结果再与其他表达式进行组合,以此类推。
表达式能解析成值,所以可以用它们来赋值。
let x,y;
x = 3 * 5;
y = x =
3 * 5;
第二行存在两个组合在一起的表达式。
第一个表达式是3 *5,结果为15
第二个表达式是15赋给变量x。赋值也是一个表达式。
JavaScript对表达式求值的顺序叫做运算符优先级。
大部分表达式,比如乘法和赋值,都是运算符表达式。
有两种非运算符表达式,它们是标识符表达式(变量名和常量名)和字面量表达式:变量和常量本身就是一个表达式,字面量也是。
表达式的本质:任何能够产生值的语句都是表达式。
5.1 运算符
可以认为运算符是表达式的“动作”。
5.2 算术运算符
JavaScript所有的数字都是双精度的,这意味着整数做算术运算的时候,有可能返回小数(如3/2,返回1.5)。
一元负号和一元正号的优先级高于加减法运算。
一元正号通常是强制将字符串转换成数字,或者调整那些否定的值。
自增和自减运算符实际上是将赋值和加(减)法运算结合在一起。
前置运算符修改了变量,表达式的值是修改后的值;后置运算符也修改了变量,但是表达式的值没有变。
var x = 2, y = 10;
//分别运行
x++ + x++; //5
++x + ++x; //7
x++ + ++x; //6
++x + x++; //6
y-- + y--; //19
--y + --y; //17
x++ + y--; //12
x++ + y++; //12
++x + y++; //13
5.3 运算符优先级
括号具有最高优先级。同一优先级按从左到右的顺序执行。
赋值运算符则是从右到左。
5.4 比较运算符
有三种类型的比较运算符:严格相等、非严格相等、相关性。
建议始终选择严格相等运算符。
严格相等(===),其否定形式(!==);
非严格相等(==),其否定形式(!=);
关系运算符比较的是两个值的关系。仅仅适用于拥有自然排序特性的数据类型。
关系运算符有小于( < )、小于等于( <= )、大于( > )、大于等于( >= )。
5.5 比较数字
特殊的数值NaN与任何值都不相等,包括它自己。使用内置函数isNaN()测试:如果 x 是NaN,isNaN(x)会返回true。
let n = 0;
while(true){
n += 0.1;
if(n === 0.3) break;
}
console.log(`Stopped at ${n}`);
while循环跳过了0.3,无限地执行下去了。因为0.1并不能精确地表示一个双精度数值,它介于两个二进制小数之间。
使用Number.EPSILON重新设置上面的循环:
let n = 0;
while(true){
n += 0.1;
if(Math.abs(n - 0.3) < Number.EPSILON) break;
}
console.log(`Stopped at ${n}`);
//Stopped at 0.30000000000000004
Number.EPSILON是一个数值常量,这个值非常小。Math.abs取绝对值。
取绝对值来比较两个值是否足够接近,是测试两个双精度数值是否相等的通用方法。
5.6 字符串连接
+运算符可以做数字的加法,也可以用作字符串连接。
5.7 逻辑运算符
逻辑运算符只关心布尔值,且只有两种值:true 或 false。
真值和假值
代表false的有:
-
undefined
-
null
-
false
-
0
-
NaN
-
""(空字符串)
其他值都是真,包括:
-
仅仅包含空格的字符串
-
字符串“false”。
-
所有数组,包括空数组
-
所有对象
如果想让空数组arr的值为假,使用arr.length(空数组返回0, 0代表假)可以达成目的。
5.8 与、或、非
三种逻辑运算符:与 && , 或 || , 非 !
有时候,或运算符又叫做“同或”,因为如果两个值都为true,结果就为true。
存在“异或”XOR,当两个值为true时,它的值为false。JavaScript不支持XOR运算符,但它有一个位异或运算符( ^)。
如果要对变量 x 和 y 做异或运算,可以使用等价表达式。
(x || y) && x !== y
//x和y为真的情况下,会返回false
5.8.1 短路求值
对于x && y,如果 x 是 false,不管 y 的值是什么,结果都为 false。
对于x || y,如果 x 是 true,不管 y 的值是什么,结果都为true。
const skipIt = true;
let x = 0;
const result = skipIt || x++;
result;
//true
由于短路求值,x的自增表达式没有执行,所以 x 的值还是 0 。如果将skipIt改为false,两个表达式都会被执行。在这里,自增就是副作用。
自增执行后,result的变成了 0。result为什么是 0 而不是 false?看下一主题。
5.8.2 非布尔值的逻辑运算符
如果使用布尔值做逻辑运算,结果只能为布尔值。如果使用非布尔值,能够确定结果的那个值就是逻辑运算的结果。
对于 x && y 。x 和 y 都为 true时, 返回 y;x 或 y 有 false 时,优先返回 x 为 false 的 x 。
对于 x || y 。x 和 y 都为 false时, 返回 y;
x 或 y 有 true 时,优先返回 x 为 true 的 x 。
const options = suppliedOptions || {name: "Default"};
如果suppliedOptions是一个对象,options 将引用 suppliedOptions。如果suppliedOptions 的值是null 或undefined,options也会有一个默认值。
使用 !非运算符 ,永远返回布尔值。
5.8.3 条件运算符
也称三元运算符,它有三个操作数(其他运算符都是一个或两个)。
const doIt = false;
const result = doIt ? "Did it!" : "Don't do it.";
如果第一个操作数为真,执行第二个操作数;否则执行第三个操作数。
5.8.4 逗号运算符
逗号运算符可以简单的将表达式组合起来:他会按顺序执行两个表达式,并返回第二个表达式的结果。
let x = 0, y = 10, z;
z = (x++, y++);
x 和 y 都自增了,z 最后的值为10。逗号的优先级是最低的,所以用了括号,否则会先赋值。
5.9 分组运算符
分组运算符除了修改或澄清运算符的优先级外,没有任何影响。
5.9.1 位运算符
位运算符允许在每个二进制位上执行操作。
位运算符将其操作数当成二进制补码格式的 32 位有符号整型数字。
因为JavaScript中,所有数字都是双精度的,JavaScript在执行位运算前会先将数字转换成 32 位的整型,并在返回结果之前转换回来。
位运算符是在整型的每一个二进制位上进行逻辑操作(与、或、非、异或)。还包含二进制位进行位移的位移运算符。
左移位实际上是乘以 2,右移位则是除以 2 然后舍去位数。
存在两种补码,最左边的二进制位为 1 时表示负数,0 表示整数。
5.9.2 类型判断运算符
类型判断运算符返回一个字符串形式的类型名称。
typeof的类型判断:
typeof 不能正确判断数组,typeof [],返回“object”。
5.9.3 void运算符
void的用途:计算它的操作数并返回undefined。
它可以强制表达式返回undefined。
常被用作HTML 标签<a>的URI:
<a href="javascript:void 0">Do nothing.</a>
5.9.4 赋值运算符
赋值运算符:将一个值指派给某个变量。
等号左边必须是变量、属性或者数组元素。
链式赋值:
let v, v0;
v = v0 = 1; //链式赋值,首先给v0 赋值1,然后给 v 赋值 1.
运算中赋值:
5.10 解构赋值
ES6新特性。
解构赋值,允许将一个对象或者数组“分解”成多个单独的值。
const obj = { b: 2, c: 3, d: 4 };
//对象解构赋值
const {a,b,c} = obj;
a; //undefined
b; //2
c; //3
d; //引用error:"d" 未定义
解构对象时,变量名必须与对象中的属性名保持一致(数组解构只能指派那些作为标识符的属性名)。
上面例子中,如果没有const进行声明,JavaScript会认为左边是一个代码块,会报错。可以添加括号运算符。
const obj = {b: 2, c: 3, d: 4};
let a, b, c;
//会报错
{a, b, c} = obj;
//正常运行
({a, b, c} = obj);
数组解构时,可以给数组的元素任意指定变量名(按顺序)。
const arr = [1, 2, 3];
//数组解构赋值
let [x, y] = arr;
x; //1
y; //2
z; //错误:z 未定义
这个例子中,x 接受了数组的第一个元素,y 接受了第二个元素,所有没被接受的元素都被丢弃了。
也可以把剩下的元素放入一个新的数组中,用
展开运算符( ... )。第六章学习这个运算符。
const arr = [1, 2, 3, 4, 5];
let [x, y, ...rest] = arr;
x; //1
y; //2
rest; //[3, 4, 5]
数组解构可以很方便的交换变量的值。
let a = 5, b = 10;
[a, b] = [b, a];
a; //10
b; //5
数组解构不仅适用于数组,还适用于任何可迭代的对象。
5.11 对象和数组运算符
5.12 模板字符串中的表达式
第 3 章中介绍。
5.13表达式和控制流模式
5.13.1 将 if ... else 语句转化成条件表达式
if(isPrime(n)){
label = "prime";
}else{
label = "non-prime";
}
//可以写成
label = isPrime(n) ? "prime" : "non-prime";
5.13.2 将 if 语句转化成短路求值的逻辑或( || )表达式
if(!option) option = {};
//可以写成
options = options || {};