1 算数运算符
https://wangdoc.com/javascript/operators/index.html
1.1 概述
指数:x ** y
余数:x % y
自增:++x x++
自减:x-- --x
1.2 加法运算符
1.2.1 基本规则
JS 允许非数值的相加
true + true // 2
1+ true // 2
如果是两个字符串相加,则会拼接。如果1个是字符串,另外一个是非字符串,则会先把非字符串转化成字符串。
1 + "2" // "12"
加号这种有时表示相加,有时表示拼接的,这种现象叫做重载。
3+4+"5" // "75"
1.2.2 对象的相加
如果运算子是对象,必须先转成原始类型的值,再相加。
let obj = {p:1};
obj + 2;
// "[object object]2"
上面代码中,对象 obj
转成原始类型的值是 [object object]
,再加2就得到了上面的结果。
对象转成原始类型的值,规则如下。
首先,自动调用对象的 valueOf
方法。
var obj = {p: 1};
obj.valueOf()
// {p : 1}
一般而言,valueOf() 总是返回对象自身,这时再自动调用对象的 toString
方法,将其转为字符串。
obj.valueOf().toString()
// "[object object]"
知道这个规则后,就可以自定义 valueOf
方法或toString
方法,得到想要的结果。—— 这一点是和Java 相同的,在Java 中调用 equal / toString 方法,很多时候是需要自己重新定义的。
var obj = {
valueOf : function(){
return 1;
}
};
obj + 2 // 3
由于 obj 返回了一个原始类型值的方法,所以不再调用 toString
方法。
自定义 toString
方法。
var obj = {
toString : ()=>{
return "hello";
}
}
obj + 1 // "hello1"
1.3 余数运算符
余数的值是由第一个运算子决定的。
-5 % 2 // -1
也就是说,余数在 JS 可能是负数的。Python 中也会出现负数的:
5 % (-2) // -1
负数的余数,这个是个数学问题,具体怎么实现的,应该按照语言来。
所以为了得到负数的正确余数值,应该先使用绝对值:
function isOdd(n){
return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
1.4 自增和自减运算符
++放在变量之后,会先返回变量操作前的值,再进行自增运算;
++放在变量之前,会先进行自增运算,然后再返回变量值。
var x = 1;
var y = 1;
console.log(x++); // 1
console.log(++x); // 2
1.5 数值运算符,负数值运算符
数值运算符(+
)同样使用加号,但是是一元运算符(只需要一个操作数),而加法运算符是二元运算符(需要两个操作数)。
数值运算符的作用在于可以将任何值转为数值(与 Number
函数的作用相同)。
+true // 1
+[] // 1
+{} // NaN
1.6 指数运算符
**
1.7 赋值运算符
就是等号
2 比较元素符
教程:https://wangdoc.com/javascript/operators/comparison.html
2.1 概述
运算符分为两类:相等比较和非相等比较。对于非相等比较,算法是比较两个是否是字符串,如果是,按照字典顺序比较(实际上是 Unicode);否则,会把两个运算子都转成数值,再比较数值的大小。
2.2 非相等运算符:字符串的比较
"cat" > "dog" // false
2.3 非相等运算符:非字符串的比较
如果两个运算子都不是字符串,分为三种情况:
(1) 原始类型值
如果两个值都是原始类型的值,那么先转成数值再比较。
true > false // true
2 > true // true
(2) 如果运算子是对象,会转为原始类型的值,再比较。
对象转化为原始类型的值,算法会先调用 valueOf 方法,如果返回的还是对象,再调用 toString 方法
var x = [2];
x > "3" // false;
// 等同于
x.valueOf().toString() > "3"
x.valueOf = ()=>{
return 100;
}
x > 99; // true
两个对象之间的比较也是如此。
{a:2} >= {b:1} // true
// 等同于
({a:2}).valueOf().toString() >= ({b:3}).valueOf().toString()
(3)复合值类型
2.4 严格相等运算符
相等运算符(==
)比较两个值是否相等,严格相等运算符(===
)比较它们是否是“同一个值”。
如果两个值不是同一类型的值,严格相等运算符会直接返回 false,而相等运算符会把他们转化为同一个类型在进行比较。
(1) 不同类型的值
如果两个值的类型不同,直接返回 false
(2) 同一类型的原始值
同一类型的原始值,比较值是否相同
(3) 符合类型值
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一地址。
{} === {} // false
[] === [] // false
(function(){} === function(){}) // false
这几个例子它们都指向不同的地址,所以都是 false。如果两个变量引用同一个对象,则它们相等。
var v1 = {};
var v2 = {};
v1 === v2 // true
对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的值。
var obj1 = {};
var obj2 = {};
obj1 === obj2 // false
obj1 > obj2 // false
obj1 < obj2 // false
obj1 >= obj2 // true
(4) undefined 和 null
undefined 和 null 与自身严格相等。
undefined === undefined; // true
null === null // true
由于变量声明后默认值是 undefined
,因此两个只声明未赋值的变量是相等的。
var v1;
var v2;
v1 === v2 // true
2.5 严格不相等运算符
!== 就是严格相等运算符的否定形式。
2.6 相等运算符
相等运算符用来比较相同类型的数据时,与严格相等运算符完全一样。
1.0 == 1
// 等同于
1 === 1.0
(1)原始类型值
原始类型的值会转换成数值再进行比较。
1 == true // true
// 等同于 1 == Number(true)
0 == false // false
// 等同于 0 === Number(false)
'1' == true // true
// 等同于 Number('1') == Number(true)
(2) 对象与原始类型值比较
对象(广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较。
[1] == '1' // true
[1] == true // true
(3) undefined
和 null
undefined
和 null
与其他类型的值比较时,结果都为 false, 它们互相比较时结果为 true。
false == null; // false
false == undefined // false
0 == null // false
0 == undefined // false
undefined == null // true
(4) 相等运算符的缺点
相等运算符隐藏的类型转化,会带来一些反直觉的结果
0 == ''; // true
0 == '0'; // true
'' == '0'; // false
false == 'false' // false
false == '0' // true
false == undefined // false
'\t\r\n' == 0; // true
2.7 不相等运算符
不相等运算符(!=
)的算法,先求相等运算符的结果,然后返回相反值。
1 != '1' // false
// 等同于
!(1 == '1')
3 布尔值运算符
https://wangdoc.com/javascript/operators/boolean.html
3.1 概述
3.2 取反运算符(!)
相当于 python 的 not,当前布尔值取反。
对于非布尔值,取反运算符会将其转为布尔值。以下六个值取反后为true
,其他的值都为 false
:
undefined
null
false
- 0
- NaN
- 空字符串('')
注意,空对象的布尔值是true
Boolean([]) // true
Boolean({}) // true
如果对一个值连续两次取反,效果跟使用 Boolean 函数一样。
!! x
// 等同于
Boolean(x)
3.3 且运算符(&&)
注意且运算符可以用来进行短路运算,即
let a = 0;
a && b
// 如果a为false,则直接返回,不会对b进行运算。
// 比如上面的 b 没有定义,但是编辑器根本不 care。
3.4 或运算符(||)
或运算也会短路。
3.5 三元条件运算符(? :)
Java 中 和这个一模一样,
condition ? a : b
注意三元表达式是一个表达式,会返回值。这是和 if ... else ...
不同的。
4 二进制运算符
https://wangdoc.com/javascript/operators/bit.html
4.1 概述
- 二进制或运算符(or):符号为 |,表示若两个二进制都为0,则结果为0,否则为1
- 二进制与运算符(and):符号为 &,表示如果两个二进制都为1,则结果为1,否则为0.
- 二进制否运算符(not):符号为~, 表示对一个二进制取反。
- 异或运算符(xor):符号为 ^,若两个二进制不相同,结果为1,否则为0.
- 左移运算符(left shift), 符号为 << ,
- 右移运算符 (right shift), 符号为 >>
- 带符号位的右移运算符(zero filled right shift),符号为 >>>
这些位运算符直接处理每一个比特位(bit),所以是非常底层的运算,好处是速度快,坏处是不直观。
有一点需要特别注意,位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。另外,虽然在 JS 内部,数值都是以 64 位浮点数的形式存储,但是做位运算的时候,是以 32 位带符号的整数进行运算的,并且返回值也是一个 32 位带符号的整数。
i = i | 0;
上行代码的意思,就是将 i
(不管是整数还是小数) 转成 32 位整数。
利用这个特性,就可以写出一个函数,将任意数值转化为 32 位整数。
function toInt32(x){
return x | 0;
}
上面这个函数将任意值与 0
进行一次或运算,这个位运算会自动将一个值转化为 32 位的整数。
toInt32(1.7) // 1
toInt32(-1) // 1
对于大于 2 的 32 次方的整数,大于 32 位的数位都会被舍弃。
4.2 二进制或运算符
二进制或运算符(|
)逐位比较两个运算子,两个二进制位之中只要有一个为1
,就返回1
,否则返回0
。
0 | 3 // 3
上面的代码中,0
和 3
的二进制分别是 00
和 11
,所以二进制运算会得到 11
(即 3
)。—— 这段解释就是为啥二进制或运算能返回整数。概述中的解释,让我误以为只能返回 0 或 1.
4.3 二进制与运算符
二进制与运算符(&
)的规则是逐位比较两个运算子,两个二进制之中只要有一个位是0
,就返回0
,否则返回 1
.
0 & 3 // 0
4.4 二进制否运算符
二进制的否运算符(~
)将每个二进制位都变为相反值(0 变成1,1 变成0.)。它的返回结果有时比较难理解,因为涉及到计算机内部的数值表示机制。
-3 // -4
3
的 32 位整数形式是 0000000000000000000000011
,二进制否运算以后得到 111111111111111111100
。由于第一位(第一位是符号位)是1,所以这个数是一个负数。JS 内部采用补码的形式表示负数,即需要将这个数减去 1,再取一次反,然后加上负号,才能得到这个负数对应的 10 进制值。这个数减去 1 等于 111111111111111111011
,再取反得到0000000000000000000100
,再加上负号就是 -4. 考虑到这样的过程比较麻烦,可以简单记忆,一个数与自身的取反值相加,等于 -1
~ -3 // 2
一个数连续进行两次取反,会得到它自身。
~~ 3 // 3
所有的位运算都只对整数有效。当二进制否运算遇到小数时,会将小数部分舍去,只保留整数部分。所以,对一个小数连续进行两次二进制否运算,能得到取整效果。
~~ 2.9 // 2
二进制否运算取整,是所有取整方法中最快的一种。
对字符串进行二进制否运算,JS 引擎会先调用 Number
函数,将字符串转为数值。
~"1.2" // 1
4.5 异或运算符
异或运算 (^
) 在两个二进制位不同时返回1
, 相同时返回0
:
0 ^ 3 // 3
上面表达式中,0
(二进制00
)与3
(二进制11
)进行异或运算,它们每一个二进制位都不同,所以得到11
(即3
)。
“异或运算”有一个特殊运用,连续对两个数a
和 b
进行三次异或运算,它们每一个二进制都不同,所以得到 11
(3
)。
“异或运算” 有一个特殊运用,连续对两个数a
和 b
进行三次异或运算,a ^= b; b ^= a; a ^= b;
,可以互换它们的值。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值。
var a = 10;
var b = 99;
a ^= b, b ^= a, a ^= b;
a // 99
b // 10
这是互换两个变量的值的最快方法。
异或运算也可以用来取整。
12.9 ^ 0 // 12
4.6 左移运算符
左移运算符(<<
)表示将一个数的二进制值向左移动指定的位数,尾部补0
,乘以 2
的指定次方。
// 4 的二进制形式为 100
// 左移一位为 `1000` (即十进制的 8)
// 相当于乘以2的1次方
4 << 1
// 8
-4 << 1
// -8
4.7 右移运算符
右移运算符(>>
)表示将一个数的二进制向右移动指定的位数,头部补0,除以2的指定次方。
4 >> 1
// 2
// 因为 4 的二进制是 000000000000000000000100,
// 右移动得到 00000000000000000010,
// 这是十进制的 2
-4 >> 1
// -2
// -4 的二进制是 1111111111111111111111111100,
// 右移一位,头部补1,得到 111111111111111111111110,
即为十进制的 -2.
右移可以模拟 2 的整除运算。
5 >> 2
// 2
21 >> 2
// 21 / 4 = 5
4.8 带符号位的右移运算符
带符号位的右移运算符(>>>
)表示将一个数的二进制形式向右移动,包括符号位也参与移动。所以,该运算总是能得到正值。对于正数,该运算的结果与右移运算符(>>
)完全一致,区别在于负数。
4 >>> 1
// 2
-4 >>> 1
// 2147483646
// 因为 -4 的二进制形式为 111111111111111111111111111100,
// 带符号位的右移一位,得到 011111111111111111111111110,
// 即十进制的 2147483646
这个运算实际上将一个值转为 32 位无符号整数位。
查看一个负整数在计算机内部的存储形式,最快的方法就是使用这个方法。
-1 >>> 0
// 4294967295
4.9 开关作用
位运算符可以用设置对象属性的开关。
假定某个对象有四个开关,每个开关都是一个变量。那么,可以设置一个四位的二进制数,它的每个位对应一个开关。
var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000
上面设置了 A、B C D 四个开关,每个开关分别占有一个二进制位。
然后,就可以用二进制与运算检验,当前设置是否打开了指定开关。
var flags = 5; // 0101
if (flags && FLAG_C){
....
}
// 0101 & 0100 => 0100 => true
上面代码检验是否打开了开关 C。如果打开了,返回 true, 反之返回 false。
现在假设需要打开 A、B、C三个开关,我们可以构造一个掩码变量。
var mask = FLAG_A | FLAG_B | FLAG_C;
// 0001 | 0010 | 0100 => 1011
有了掩码,二进制或运算可以确保打开指定的开关。
// 没看明白。。。
5 其他运算符,运算顺序
5.1 void 运算符
void 的作用是执行一个表达式,然后不返回任何值,或者说返回的是undefined
void 0 // undefined
void(0) // undefined
这两种写法都会,但是建议使用带括号的。因为 void 运算符的优先级很高,如不使用括号,容易造成错误。比如,void 4 + 7 实际上是,(void 4) + 7
这个运算符的主要用途是 浏览器的书签工具,以及在超级链接中插入代码防止网页跳转。
<script>
function f(){
console.log("hello world");
}
</script>
<a href="http://example.com" onclick="f(); return false;">点击</a>
上面的代码,点击链接后,会先执行 onclick 的代码,由于 onclick 返回 false,所以浏览器不会跳转。
void 运算符可以取代上面的写法。
<a href = "javascript: void(f())">文字</a>
下面是一个更实际的例子,用户点击链接提交表单,但是不会产生页面跳转。
<a href="javascript: void(document.form.submit())">
提交
</a>
5.2 逗号运算符
逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
1,2 // 2
var x = 0;
var y = (x++, 10);
x // 1
y // 10
逗号运算符的一个用途是,在返回一个值前,进行一些辅助性的操作。
—— 最典型的是用到 for 循环里
var value = (console.log("HI"), true);
// hi
value // true
5.3 运算顺序
5.3.1 优先级
运算符是有优先级的。
5.3.2 圆括号的作用
圆括号可以用来提升优先级,圆括号的优先级是最高的。
圆括号不是运算符,而是一种语法结构。一共有两种用法:一种是把表达式放在圆括号之中;另外一种是跟在函数后面,作用是调用函数。
5.3.3 左结合与右结合
一般是从左向右运算。
但也有例外,
w = x = y = z;
q = a ? b : c ? d : e ? f : g;
上面的代码相当于:
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));