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
- 负数以二补数(补码)储存
- 以绝对值的二进制表示(-18先确定18的二进制)
- 0 变成 1,1 变成 0(称为补数/补码)
- 给结果加 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
// 把符号位也当做值来位移了,导致结果相差很大