JavaScript横向知识总结系列(2)一表达式与运算符

199 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


表达式是大家都未曾关注的概念,对表达式的理解有助于我们编写更简易的代码,运算符虽然是一个经常使用的东西,但仍有许多细节并不为人所熟知,本文对这两块内容做一个总结。(注:文章发布时并未详尽所有内容,部分内容将在日后写在其它文章中并以超链接的形式引入)

1.表达式

语句是一个完整逻辑的表达,在JS中用分号结尾,比如变量声明语句,流程控制语句(if、while...)等。而表达式是语句的一部分,也有可能是整条语句,表达式的特征是表达式执行完后会返回一个值。在控制台中输入表达式便能得到它的值。如下3*7便是一个算术表达式。


这里举两个比较常见的表达式例子:

  1. 赋值也是一个表达式。如下a=7这个赋值表达式的结果就是赋给a的值7,因此b也被赋值7
let a;
let b;
b = a = 7;
console.log(a, b); //7 7
  1. 逻辑运算表达式。如下0||1这个表达式的返回值1被赋值给了a1 && 0这个表达式的返回值0被赋值给了b

|| 与 &&运算存在短路现象,如果已经能确定整个逻辑运算表达式的返回值,逻辑运算符右边或者剩余的表达式不再执行

let a;
let b
if(a = 0 || 1) {
  console.log(a); //1
}
if(b = 1 && 0) {
  console.log(b); //不执行
}
console.log(b); //0

2.运算符

2.1赋值运算符

不多说,形式为操作数1=操作数2,将右边的值赋值给左边的变量

解构赋值

解构赋值为ES6新增语法,分为数组解构和对象解构。简单来说,解构赋值可以让我们轻松地从数组或者对象中取出我们想要的值并赋给数组和对象之外所定义的变量上。

解构赋值也是表达式,最终也会返回一个结果。如果是对象解构则是等号右边的对象,如果是数组解构,则是等号右边的数组。


对象解构

  1. 正常用法

要解构的目标是对象,左边也用{}的“对象“形式,其中key是你想从obj中取出的属性的属性名a c),value是接收属性值的变量(即xy

注意:变量xy没有声明,所以前面要加let

let obj = {
  a: 1,
  b: 2,
  c: 3
}

let {a: x, c: y} = obj
console.log(x, y) //1 3
  1. 不使用let

如果接收属性值的变量已经定义,可以不用使用let,但整个赋值表达式以要用()包裹

如果不用(),左边的{}会被当做一整个块语句,而不是对象

let obj = {
  a: 1,
  b: 2,
  c: 3
}
let x, y
({a: x, c: y} = obj)
console.log(x, y) //1 3
  1. 使用表达式作为key

左边{}中的key可以是任意表达式(表达式的结果会作为最后的key值),value除了是普通变量以外也能是某个对象的属性

表达式需要用[]括起来

let obj1 = {
  a: 1,
  b: 2,
  c: 3
};
let x, obj2 = {};
let key = ['a', 'c'];
({[key[0]]: x, [key[1]]: obj2.y} = obj1);
console.log(x, obj2.y) //1 3
  1. 结合扩展运算符

扩展运算符...用于函数形参时可表现为收集特性(即将传递进来的剩余的实参都收集到一个形参变量中),解构赋值也可以利用这一特性,将没有取出的值收集到一个变量中

let obj1 = {
  a: 1,
  b: 2,
  c: 3
};
let {a: x, ...rest} = obj1
console.log(x, rest) //1 {b: 2, c: 3}
  1. 更多

这里只做一个简述,还有嵌套解构,数组的解构等更多用法将另开帖子说明

复合赋值运算符

复合赋值运算符指的是赋值运算符可以与其它运算符构成新的运算符,其用法直接举例如下:

let a = 1, b = 1;
a += b; //等价于a = a + b
console.log(a); //2

当左边被赋值的变量参与右边表达式的运算时,可以使用复合赋值运算符

除了加法运算符结合以外,还可以与以下运算符复合:

  • 取余
  • 幂运算
  • 左移
  • 右移
  • 无符号右移
  • 按位与
  • 按位或
  • 按位异或

链式赋值

赋值运算符可以在一条语句中链式使用,如下

let a, b, c
a = b = c = 1
console.log(a, b, c) // 1 1 1

a = b = c = 1可以被拆分成三个表达式,即c = 1,然后将该表达式的返回值赋值给b,再把b = c = 1表达式的结果赋值给a


由上述的拆分可以指出我们一个常见的误用操作——无意创建的全局变量,如下

function func() {
  let a = b = c = 1;  
}
func();
console.log(b, c); // 1 1
console.log(a);
//Uncaught ReferenceError: a is not defined
  

由表达式拆分结果可知,变量bc没有使用定义变量的关键字,所以被定义成了全局变量。只有a才被定义成函数func的局部变量。

该操作在严格模式下会报错

2.2算术运算符

常见的算术运算符包括:加+、减-、乘*、除/、取余%


除此之外,还有自增 ++ 、自减--运算符

  • 特性1:只有一个操作数,运算的结果会赋给操作数本身。
  • 特性2:当它作为一个表达式时,表达式的返回值有两种可能:
    • 当操作数在左边时(a++),表达式返回值为操作数本身
    • 当操作数在右边时(++a),表达式返回值为自增/自减运算后的值
let a1 = 1, a2 = 1;
let b = a1++; // a1为操作数,在运算符左边
console.log(b); // 1
let c= ++a2;
console.log(c); // 2

ES6新增了幂运算(指数运算符),2 ** 3 = 8(2为底数,3为指数,结果为2的3次方)

2.3逻辑运算符

逻辑运算符包括与&&、或||、非!

这里说明逻辑运算符的短路特性。 逻辑运算符左右两边既可以是一般的bool值,也可以是表达式,短路特性指的是这些表达式会从左往右计算,当能确定整个逻辑表达式的真/假时便停止运算,并将最后一个表达式的运算结果作为整个逻辑表达式的结果返回。

  • A || B,当A为true时,则能确定A || Btrue,因此B表达式不会再执行,直接返回A的结果
  • A && B,当A为false时,则能确定A && Bfalse,因此B表达式不会再执行,直接返回A的结果
let a = 0, b = 0;
let c = ++a || ++b;
console.log(a, b, c); // 1 0 1
//可以看到b0没有运算,++a的结果被返回给c了
//-----------------------------------------
let a = 1, b = 1;
let c = --a && --b;
console.log(a, b, c); // 0 1 0
//--a表达式的返回值为0,强制转化为bool值为false

2.4位运算符

移位运算

移位运算符有三种:

  • 左移位运算<<:低位补0
  • 带符号右移位运算>>:为负数,则高位补1,否则补0
  • 无符号右移位运算>>>:无论如何高位都补0

左移位运算有一个特性,当移动位数超过31(大于等于32)时,会自动使得移动位数对32取余

let n = 1;
n <<= 32; //由于对32取余,等价于左移0位
console.log(n); // 1
n <<= 33; //取余后,等价于左移1位
console.log(n); // 2

//如果先位移31位,在位移1位,则是正常位移,共移位32位
n = 1;
n <<= 31;
console.log(n); // -2147483648
n <<= 1;
console.log(n); // 1

逻辑位运算

有四种:

  • 按位与&
  • 按位或|
  • 按位非~
  • 按位异或^

位运算特性

参与位运算的操作数会被截断为32位有符号整数(第32位为符号位) :即小数部分被整个截断,整数部分只有低位开始的32位。

let n = 1;
n = n << 30;
console.log(n, n.toString(2), n.toString(2).length);
//1073741824 '1000000000000000000000000000000' 31
n = 1;
n <<= 31
console.log(n, n.toString(2), n.toString(2).length);
//-2147483648 '-10000000000000000000000000000000' 33
//负数编码采用补数,因此转化成正数为'10000000000000000000000000000000' 即2147483648

快速向下取整

利用截断特性,我们可以使用按位非操作~进行快速地向下取整操作

let n = 32.6;
n = ~~n;
console.log(n); // 32

位运算sao操作

  1. 利用位运算实现算术运算(不使用+、-、*、/运算符)

在其他文章中说明

  1. 使用异或实现交换变量值

如下所示,我们不使用额外变量,通过异或实现了两个变量值的交换。其原理为异或运算有一个特性:某个数对同一个数进行了两次异或运算后等价于本身(对运算的顺序不限制)。因此a ^ b ^ b等价于aa ^ b ^ a等价于b

let a = 8;
let b = 4;
a = a ^ b;
b = a ^ b;
a = a ^ b;
console.log(a, b) // 4 8

2.5比较运算符

比较运算符分为关系运算符(>、<、≥、≤)与相等运算符,相等运算符又分为两种:等于(==)和不等(!=)、全等(===)和不全等(!==)。

比较时的强制类型转换

见其它文章

相等运算符的相等性判断问题

见其它文章

2.6三元运算符

JS中只有一个三元运算符称为条件运算符,形式为表达式1 ? 表达式2: 表达式3

  • 如果表达式1的结果为true,则执行表达式2,返回其结果作为整个三元表达式的返回值
  • 如果为false,则执行表达式3返回其结果作为整个三元表达式的返回值

三元运算符的一个常见用法为嵌套使用/链式使用,即表达式2或表达式3也可以写作一个三元表达式。等价于在if...else块中嵌套if...else块 / 编写if...else if...语句

let sex = 'male';
let age = 18;
if(sex == 'male') {
  if(age == 18) {
    console.log('符合条件,男');
  } else {
    console.log('不符合条件,男');
  }
} else {
  if(age == 20) {
    console.log('符合条件,女');
  } else {
    console.log('不符合条件,女');
  }
}
// 嵌套使用
sex == 'male' ? (age == 18 ? console.log('符合条件,男'): console.log('不符合条件,男')) :
  (age == 20 ? console.log('符合条件, 女'): console.log('不符合条件,女'))
//'符合条件,男'

/*--------------------------------------*/
let num = 3;
if(num == 1) console.log(1)
else if(num == 2) console.log(2)
else if(num == 3) console.log(3)
else if(num == 4) console.log(4);
//3

// 链式使用
num == 1 ?  console.log(1): 
  num == 2 ? console.log(2) : 
  num == 3 ? console.log(3) : console.log(4);
//3