跳至主要內容

JavaScript 运算符

h7mljavascriptjavascript大约 7 分钟约 2184 字

JavaScript 运算符

ECMAScript 中的操作符比较独特,包括字符串、数值、布尔值,甚至还有对象。应用给对象时通常会调用会调用valueOf()toString()方法。

一元运算符

递增/递减

++n; // 先自增 1,再运算
n++; // 先运算,再自增 1
--n; // n-- 同理
!n; // 转换为 Boolean 值

操作符在前,先自递增/递减后再进行运算。

let num1 = 2;
let num2 = 20;
let num3 = --num1 + num2;
let num4 = num1 + num2;
console.log(num3); // 21
console.log(num4); // 21

操作符在后,先运算再进行自递增/递减。

let num1 = 2;
let num2 = 20;
let num3 = num1-- + num2;
let num4 = num1 + num2;
console.log(num3); // 22
console.log(num4); // 21
let s1 = '2'; // 是有效的数值形式:则转换为数值再应用改变。
let s2 = 'z'; // 不是有效的数值:则将变量的值设置为NaN 。
let b = false; // 如果是false,则转换为0 再应用改变。如果是true,则转换为1 再应用改变。
let f = 1.1; // 对于浮点值,加1 或减1。
let o = {
  // 开头说过,如果是对象,valueOf()方法取得可以操作的值,再应用上面的规则。如果是NaN,则调用toString()并再次应用其他规则。
  valueOf() {
    return -1;
  },
};
s1++; // 值变成数值3
s2++; // 值变成NaN
b++; // 值变成数值1
f--; // 值变成0.10000000000000009(因为浮点数不精确)
o--; // 值变成-2

一个变量同时等于两个值

参考上面所说的,如果是对象,操作符会先调用 valueOf 取值,重写 valueOf 即可使 o == 1 且 o == 2

let o = {
  a: 0,
  valueOf() {
    return ++this.a;
  },
};
if (o == 1 && o == 2) {
  // true
  console.log('yes');
}
// yes

一元加和减

let s1 = '01';
let s2 = '1.1';
let s3 = 'z';
let b = false;
let f = 1.1;
let o = {
  valueOf() {
    return -1;
  },
};
s1 = +s1; // 值变成数值1
s1 = -s1; // 值变成数值-1
s2 = +s2; // 值变成数值1.1
s2 = -s2; // 值变成数值-1.1
s3 = +s3; // 值变成NaN
b = +b; // 值变成数值0
f = +f; // 不变,还是1.1
f = -f; // 变成-1.1
o = +o; // 值变成数值-1

算术运算符

  • + 加法比较特殊

    • 两边都是数字时,做数学运算
    • 一边为字符串,进行字符串连接
    • 一边为对象类型 object,将对象使用 toString 方法转换为字符串,进行连接
  • - * / % 只能数学运算 隐式用 Number 转换 不能转 ---> NaN

alert([10] + 10); // '1010'
alert([1, 2, 3] + 10); // '1,2,310'
alert({ name: 'joth' } + 10); // '[object Object]10'
alert(null + 10); // 10
alert(undefined + 10); // NaN
alert(true + 10); // 11

alert(null - 10); // -10
alert(undefined * 10); // NaN
alert([10, 20] / 10); // NaN

alert(1 % 0); // NaN
alert(100 % null); // NaN

比较运算符

  • 大于 >,小于 <,大于等于 >=,小于等于 <=, 等于 ==,全等 =,不等于 !=,不全等 !
  • == 等于时:只需要值相等,不用管数据类型,实际上也是通过 Number 进行类型转换
  • === 全等时:不会进行数据类型转换 那么需要两边的数据类型和值都相等
  • 特例 undefined == null 为真( js 真理)undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等
'10' == 10; // true
'10' === 10; // false
undefined == null; // true
undefined === null; // false 数据类型不同

逻辑运算符

  • 非 ! 取反 非真为假 非假为真
  • 与 && 与运算见假则假
  • 或 || 或运算见真则真
!false; // true
!!false; // false

// 与运算:与运算见假则假
true && false; // false
false && true; // false
false && false; // false
true && true; // true

// 或运算:或运算见真则真
true || false; // true
false || true; // true
true || true; // true
false || false; // false
10 > 3 && '10' > '3'; // false

短路运算

  • 短路与: 第一个值为 true 返回第二个值, 第一个值为 false,则返回第一个值
  • 短路或: 第一个值为 true 返回第一个值, 第一个值为 false,则返回第二个值
// 短路与
10 && null; // null
undefined && 'abc'; // undefined

// 短路或
10 || null; // 10
undefined || 'abc'; // 'abc'

三目运算符

  • 方法: ? : ---> 判断条件 ? 当条件为真时 返回的值 : 当条件为假时返回的值
var y = -20 > 0 ? 1 : -20 == 0 ? 0 : -1;

赋值运算符

  • = 赋值 += -= *= /= %=
a += 5; // 等价于 a = a + 5;
a -= 10; // 等价于 a = a - 10;
a *= 3; // 等价于 a = a * 3
a /= 2; // 等价于 a = a / 2
a %= 2; // 等价于 a = a % 2

隐式类型转换

  • + - * / %
  • + 转换方式比较多
  • - * / % 都是使用 Number 转数字 能转数字就运算 不能转数字就 NaN

括号/逗号运算符

var a = (1, 2, 2, 1, 0); // 0 返回最后一项
  • 应用
const arr = [{ a: 1 }, { a: 2 }];
arr.reduce((prev, next) => ((prev += 2), prev + next.a), 0); // 7

指数运算符

  • ES2016 新增了一个指数运算符(**)
2 ** 2; // 4
2 ** 3; // 8

2 ** (3 ** 2); // 512,相当于 2 ** (3 ** 2)
a **= 2; // 等同于 a = a * a;
b **= 3; // 等同于 b = b * b * b;

位操作符

  • ECMAScript 中的所有数值都以 IEEE 754 64 位格式存储

  • 但位操作并不直接应用到 64 位表示,而是先把值转换为 32 位整数,位操作之后再把结果转换为 64 位。(所以只需要考虑 32 位)

  • 32 位,前面 31 位表示数值,32 位表示数值的符号,0 表示正,1 表示负。(称为符号位)

  • 正值以二进制格式存储,31 位都是 2 的幂。(第一位 2º,第二位 2¹,以此类推)

数值 18 用二进制来表示为 00000000000000000000000000010010(32 位数),前面的 0 可以省略 10010。

10010 = (2^4 1)+(2^3 0)+(2^2 0)+(2^1 1)+(2^0 * 0) = 18

  • 负数以二补数(补码)储存
    1. 以绝对值的二进制表示(-18 先确定 18 的二进制)
    2. 0 变成 1,1 变成 0(称为补数/补码)
    3. 给结果加 1

按上述步骤表示 -18:

第一步:表示绝对值 18

0000 0000 0000 0000 0000 0000 0001 0010

第二步:补码

1111 1111 1111 1111 1111 1111 1110 1101

第三部:给补数加 1

1111 1111 1111 1111 1111 1111 1110 1110 (这就是 -18 的二进制表示)

let num = -18;
console.log(num.toString(2)); // '-10010'

输出'-10010',这个过程会求处二补数,然后符合逻辑的表示出来。ECMA 中存在无符号的整数,也就是说无符号的整数比有符号的范围更大,因为符号位可以用来表示数值。

按位非

~ 来表示,作用是返回数值的补数。

let num1 = 25; // 二进制00000000000000000000000000011001
let num2 = ~num1; // 二进制11111111111111111111111111100110 这里取反后还减了 1
console.log(num2); // -26

这样的结果比-num1 - 1结果更快,位操作符是在底层表示进行的。

应用

let a = 25.513;
~~a; // 25 取整
let b = 5.9;
~~b; // 5 取整

~a 反补减 1 得 -26,再~ 反补得到正 26 减 1 得到 25。

按位与

& 来表示,两个数的位 1 1 得 1,0 1 得 0, 0 0 得 0。

let result = 25 & 3;
console.log(result); // 1

25 = 0000 0000 0000 0000 0000 0000 0001 1001

  3 = 0000 0000 0000 0000 0000 0000 0000 0011

只有都为 1 时二进制位才取 1

0000 0000 0000 0000 0000 0000 0000 0001

所以结果就是 1

按位或

| 来表示,看懂了按位与那么按位或也是同理。有一个位为 1 则为 1,都为 0 时则为 0。

let result = 25 | 3;
console.log(result); // 27
// 25 = 0000 0000 0000 0000 0000 0000 0001 1001
//  3 = 0000 0000 0000 0000 0000 0000 0000 0011
// 得   0000 0000 0000 0000 0000 0000 0001 1011
// 11011 等于27

按位异或

^ 来表示,它只有在一位是 1,一位是 0 时才会得 1。都是 0 或都是 1 则得 0。

let result = 25 ^ 3;
console.log(result); // 26
// 25 = 0000 0000 0000 0000 0000 0000 0001 1001
//  3 = 0000 0000 0000 0000 0000 0000 0000 0011
// 得   0000 0000 0000 0000 0000 0000 0001 1010
// 二进制码11010 等于26

相同的两个值,按位异或比按位或得出的结果小 1

左移

<< 表示,二进制位向左移动的位数

let oldValue = 2; // 二进制表示 10
let newValue = oldValue << 5; // 二进制表示 1000000
console.log(newValue); // 十进制 64

2 的二进制 10,向左移 5,补了 5 个 0,1000000 即为 64。

但是左移会保留符号,-2 左移 5 得到 -64,并不是 64

有符号右移

>> 表示,与左移同理,也会保留符号。

let oldValue = 64; // 二进制表示 1000000
let newValue = oldValue >> 5; // 二进制表示 10
console.log(newValue); // 十进制 2

无符号右移

>>> 表示,

let oldValue = 64; // 二进制表示 1000000
let newValue = oldValue >>> 5; // 二进制表示 10
console.log(newValue); // 十进制 2

let oldValue = -64; // 二进制表示 11111111111111111111111111000000
let newValue = oldValue >>> 5; // 二进制表示 134217726
// 把符号位也当做值来位移了,导致结果相差很大