JavaScript运算符
(“整理自网道项目”)
算数运算符
1. 概述
JavaScript 共提供10个算术运算符
- 加法运算符:x + y
- 减法运算符:x - y
- 乘法运算符:x * y
- 除法运算符:x / y
- 指数运算符:x ** y
- 余数运算符:x % y
- 自增运算符:++x 或者 x++
- 自减运算符:–x 或者 x–
- 数值运算符:+x
- 负数值运算符:-x
2. 加法运算符
2.1 基本规则
加法运算符(二元运算符)是在运行时决定执行相加,还是执行连接。运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)
true + false // 1 加法运算时,boolean转换为number,true为1,false为0
true + 1 // 2
true + "a" // "truea"
1 + "1" // "11"
字符串的位置不同会导致不同的结果
1 + 2 + "1" // "31"
"1" + 1 + 2 // "112"
除了加法运算符,其他算术运算符都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算
1 - "2" // -1
1 * "2" // 2
1 / "2" // 0.5
2.2 对象的相加
如果运算子是对象,必须先转成原始类型的值,然后再相加
let obj = { p: 1};
obj + 2 // "[object Object]2"
上面代码中,对象obj
转成原始类型的值是[object Object]
,再加2
就得到了上面的结果。如下:
// 首先,对象转成原始类型的值,会自动调用对象的 valueOf方法
// 对象的valueOf方法总是返回对象自身
obj.valueOf() // { p: 1 }
// 然后自动调用对象的toString方法,将其转为字符串
obj.valueOf().toString() // "[object Object]"
// 最后进行加法运算
"[object Object]" + 2 // "[object Object]2"
可以自己定义valueOf
方法或toString
方法,得到想要的结果
var obj = {
valueOf: function () {
return 1;
}
};
obj + 2 // 3
上面代码中,由于valueOf
方法直接返回一个原始类型的值,所以不再调用toString
方法
下面是自定义toString
方法的例子
var obj = {
toString: function () {
return 'hello';
}
};
obj + 2 // "hello2"
这里有一个特例,如果运算子是一个Date
对象的实例,那么会优先执行toString
方法
var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 2 // "hello2"
上面代码中,对象obj
是一个Date
对象的实例,并且自定义了valueOf
方法和toString
方法,结果toString
方法优先执行
3. 余数运算符
余数运算符(%
)返回前一个运算子被后一个运算子除,所得的余数(可以运用在浮点数上,但精度不准确)
12 % 5 // 2
运算结果的正负号由第一个运算子的正负号决定(为了得到负数的正确余数值,可以先使用绝对值函数Math.abs()
)
-1 % 2 // -1
1 % -2 // 1
4. 自增和自减运算符
自增和自减运算符,是一元运算符。它们的作用是将运算子首先转为数值,然后加上1或者减去1。会修改原始变量
自增和自减运算符放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值
var x = 1;
var y = 1;
x++ // 1
++y // 2
5.数值运算符,负数值运算符
数值运算符(+
)是一元运算符
作用在于可以将任何值转为数值(与Number
函数的作用相同)
+true // 1
+[] // 0
+{} // NaN
负数值运算符(-
),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符
var x = 1;
-x // -1
-(-x) // 1
数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值
6.指数运算符
指数运算符(**
)完成指数运算,**
后一个运算子是指数
多个指数运算符连用时,先进行最右边的计算
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2 // 512
7.赋值运算符
赋值运算符(=
)用于给变量赋值
复合的赋值运算符先进行指定运算,再将得到的值返回给左边的变量
x += y // 等同于 x = x + y
x -= y // 等同于 x = x - y
比较运算符
1. 概述
比较运算符可以比较任意类型两个值的大小,然后返回一个布尔值,表示是否满足指定的条件
JavaScript 一共提供了8个比较运算符
>
大于运算符<
小于运算符<=
小于或等于运算符>=
大于或等于运算符==
相等运算符===
严格相等运算符!=
不相等运算符!==
严格不相等运算符
2. 字符串的比较
字符串按照字典顺序进行比较
'大' > '小' // false
“大”的 Unicode 码点是22823,“小”是23567,因此返回false
3.非字符串的比较
原始类型值比较
如果两个运算子都是原始类型的值,则是先转成数值再比较
5 > '4' // true
// 等同于 5 > Number('4')
true > false // true
// 等同于 Number(true) > Number(false)
2 > true // true
// 等同于 2 > Number(true)
任何值(包括NaN
本身)与NaN
使用非相等运算符进行比较,返回的都是false
1 > NaN // false
1 <= NaN // false
NaN > NaN // false
NaN <= NaN // false
对象比较
如果运算子是对象,会转为原始类型的值,再进行比较
对象转换成原始类型的值,算法是先调用valueOf
方法;如果返回的还是对象,再接着调用toString
方法
var x = [2];
x > '11' // true
// 等同于 [2].valueOf().toString() > '11'
// 即 '2' > '11'
x.valueOf = function () { return '1' };
x > '11' // false
// 等同于 [2].valueOf() > '11'
// 即 '1' > '11'
两个对象之间的比较也是如此
[2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'
[2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'
{ x: 2 } >= { x: 1 } // true
// 等同于 { x: 2 }.valueOf().toString() >= { x: 1 }.valueOf().toString()
// 即 '[object Object]' >= '[object Object]'
4. 相等运算符
比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较
(1)原始类型值
原始类型的值会转换成数值再进行比较
1 == true // true
// 等同于 1 === Number(true)
'true' == true // false
// 等同于 NaN === 1
'\n 123 \t' == 123 // true
// 因为字符串转为数字时,省略前置和后置的空格
(2)对象与原始类型值比较
对象(包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较
// 数组与字符串的比较
[1, 2] == '1,2' // true
上面例子中,JavaScript 引擎会先对数组[1]调用数组的valueOf()
方法,由于返回的还是一个数组,所以会接着调用数组的toString()
方法,得到字符串形式,再进行比较
(3)undefined 和 null
undefined
和null
只有与自身比较,或者互相比较时,才会返回true
;与其他类型的值比较时,结果都为false
5. 严格相等运算符
JavaScript 提供两种相等运算符:==
和===
相等运算符(==
)比较两个值是否相等,严格相等运算符(===
)比较是否为“同一个值”
以下为严格相等运算符的算法
(1)不同类型的值
如果两个值的类型不同,直接返回false
1 === "1" // false
(2)同一类的原始类型值
同一类型的原始类型的值比较时,值相同就返回true,值不同就返回false
1 === 0x1 // true
NaN
与任何值都不相等(包括自身),正0
等于负0
(3)复合类型值
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址
{} === {} // false
[] === [] // false
(function () {} === function () {}) // false
如果两个变量引用同一个对象,则它们相等
var v1 = {};
var v2 = v1;
v1 === v2 // true
注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值
(4)undefined 和 null
undefined
和null
与自身严格相等,所有两个只声明未赋值的变量也是相等的
undefined === undefined // true
null === null // true
6. 不相等运算符和严格不相等运算符
相等运算符有一个对应的“不相等运算符”(!=
),它的算法就是先求相等运算符的结果,然后返回相反值
1 != '1' // false
// 等同于 !(1 == '1')
严格相等运算符有一个对应的“严格不相等运算符”(!==
),它的算法就是先求严格相等运算符的结果,然后返回相反值
1 !== '1' // true
// 等同于 !(1 === '1')
布尔运算符
1. 概述
布尔运算符用于将表达式转为布尔值,一共包含四个运算符
- 取反运算符:
!
- 且运算符:
&&
- 或运算符:
||
- 三元运算符:
?:
2. 取反运算符(!)
取反运算符是一个感叹号,用于将布尔值变为相反值,即true
变成false
,false
变成true
!true // false
!false // true
对于非布尔值,取反运算符会将其转为布尔值。可以这样记忆,以下六个值取反后为true
,其他值都为false
undefined
null
false
0
NaN
- ``空字符串(
''
)
不管什么类型的值,经过取反运算后,都变成了布尔值
如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数的作用相同。这是一种常用的类型转换的写法
!!x // 等同于 Boolean(x)
3. 且运算符(&&)
且运算符(&&
)往往用于多个表达式的求值
它的运算规则是:如果第一个运算子的布尔值为true
,则返回第二个运算子的值;如果第一个运算子的布尔值为false
,则直接返回第一个运算子的值,且不再对第二个运算子求值
true && false // false
false && true // false
var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1
最后这种跳过第二个运算子的机制,也被称为“短路”。可以用它取代if结构,如下
if (i) {
alert("a");
}
// 等价于 i && alert("a");
运算符可以多个连用,这时返回第一个布尔值为false
的表达式的值。如果所有表达式的布尔值都为true
,则返回最后一个表达式的值
true && '' && 4 && true // ''
1 && 2 && 3 // 3
4. 或运算符(||)
或运算符(||
)也用于多个表达式的求值。
它的运算规则是:如果第一个运算子的布尔值为true
,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false
,则返回第二个运算子的值
true || false // true
false || true // true
短路规则对这个运算符也适用
var x = 1;
true || (x = 2) // true
x // 1
或运算符也可以多个连用,这时返回第一个布尔值为true
的表达式的值。如果所有表达式都为false
,则返回最后一个表达式的值
false || 0 || '' || 4 || true // 4
false || 0 || '' // ''
或运算符常用于为一个变量设置默认值
function saveText(text) {
text = text || '';
// ...
}
// 或者写成
saveText(this.text || '')
上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串
5. 三元条件运算符(?:)
三元条件运算符由问号(?
)和冒号(:
)组成,分隔三个表达式。它是 JavaScript 语言唯一一个需要三个运算子的运算符。如果第一个表达式的布尔值为true
,则返回第二个表达式的值,否则返回第三个表达式的值
true ? "a" : "b" // a
false ? "a" : "b" // b
三元条件表达式与if...else
语句具有同样表达效果。最大的差别是if...else
是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用if…else
console.log(true ? 'a' : 'b');
二进制位运算符
1.概述
二进制位运算符用于直接对二进制位进行计算,一共有7个
- 二进制或运算符(or):符号为
|
,表示若两个二进制位都为0
,则结果为0
,否则为1
- 二进制与运算符(and):符号为
&
,表示若两个二进制位都为1
,则结果为1
,否则为0
- 二进制否运算符(not):符号为
~
,表示对一个二进制位取反 - 异或运算符(xor):符号为
^
,表示若两个二进制位不相同,则结果为1
,否则为0
- 左移运算符(left shift):符号为
<<
- 右移运算符(right shift):符号为
>>
- 头部补零的右移运算符(zero filled right shift):符号为
>>>
2. 二进制或运算符
二进制或运算符(|
)逐位比较两个运算子,两个二进制位之中只要有一个为1
,就返回1
,否则返回0
0 | 3 // 3
位运算只对整数有效,遇到小数时,会将小数部分舍去,只保留整数部分。所以,将一个小数与0进行二进制或运算,等同于对该数去除小数部分,即取整数位
2.9 | 0 // 2
-2.9 | 0 // -2
不过这种取整方法不适用超过32位整数最大值2147483647的数
2147483649.4 | 0;
// -2147483647
3. 二进制与运算符
二进制与运算符(&
)的规则是逐位比较两个运算子,两个二进制位之中只要有一个位为0
,就返回0
,否则返回1
0 & 3 // 0
4. 二进制否运算符
二进制否运算符(~
)将每个二进制位都变为相反值(0
变为1
,1
变为0
)简单理解为:一个数加1
取反
~ 3 // -4
~ -3 // 2
3的32位整数形式是00000000000000000000000000000011,二进制否运算以后得到11111111111111111111111111111100。(由于第一位(符号位)是1,所以这个数是一个负数)
所有的位运算都只对整数有效。二进制否运算遇到小数时,会将小数部分舍去,只保留整数部分。所以,对一个小数连续进行两次二进制否运算,能达到取整效果(使用二进制否运算取整,是所有取整方法中最快的一种)
~~1.9999 // 1
对于其他类型的值,二进制否运算先用Number转为数值,然后再进行处理
~'011' // -12
// 相当于~Number('011')
~'d' // -1
// 相当于~Number('d')
~[] // -1
// 相当于 ~Number([])
~NaN // -1
// 相当于 ~Number(NaN)
~null // -1
// 相当于 ~Number(null)
5. 异或运算符
异或运算(^
)在两个二进制位不同时返回1
,相同时返回0
0 ^ 3 // 3
// 0和3每一个二进制位都不同,所以得到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
6. 左移运算符
左移运算符(<<
)表示将一个数的二进制值向左移动指定的位数,尾部补0
,即乘以2
的指定次方。向左移动的时候,最高位的符号位是一起移动的
// 4 的二进制形式为100,
// 左移一位为1000(即十进制的8)
// 相当于乘以2的1次方
4 << 1 // 8
-4 << 1 // -8
上面代码中,-4
左移一位得到-8
,是因为-4
的二进制形式是11111111111111111111111111111100
,左移一位后得到11111111111111111111111111111000
,该数转为十进制(减去1
后取反,再加上负号)即为-8
如果左移0位,就相当于将该数值转为32位整数,等同于取整,对于正数和负数都有效
13.5 << 0 // 13
-13.5 << 0 // 13
左移运算符用于二进制数值非常方便
使用左移运算符,将颜色的 RGB 值转为 HEX 值
var color = {r: 186, g: 218, b: 85};
// RGB to HEX
// (1 << 24)的作用为保证结果是6位数
var rgb2hex = function(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b)
.toString(16) // 先转成十六进制,然后返回字符串
.substr(1); // 去除字符串的最高位,返回后面六个字符串
}
rgb2hex(color.r, color.g, color.b)
// "#bada55"
7. 右移运算符
右移运算符(>>
)表示将一个数的二进制值向右移动指定的位数。如果是正数,头部全部补0
;如果是负数,头部全部补1
。右移运算符基本上相当于除以2
的指定次方(最高位即符号位参与移动)
4 >> 1 // 2
// 因为4的二进制形式为 00000000000000000000000000000100,
// 右移一位得到 00000000000000000000000000000010,
// 即为十进制的2
-4 >> 1 // -2
// 因为-4的二进制形式为 11111111111111111111111111111100,
// 右移一位,头部补1,得到 11111111111111111111111111111110,
// 即为十进制的-2
右移运算可以模拟 2 的整除运算
4 >> 1 // 2
// 相当于 4 / 2
8 >> 3 // 1
// 相当于 8 / 8
8. 头部补零的右移运算符
头部补零的右移运算符(>>>
)与右移运算符(>>
)只有一个差别,就是一个数的二进制形式向右移动时,头部一律补零,而不考虑符号位。所以,该运算总是得到正值。对于正数,该运算的结果与右移运算符(>>
)完全一致,区别主要在于负数
4 >>> 1 // 2
-4 >>> 1 // 2147483646
// 因为-4的二进制形式为11111111111111111111111111111100,
// 带符号位的右移一位,得到01111111111111111111111111111110,
// 即为十进制的2147483646
这个运算实际上将一个值转为32位无符号整数
查看一个负整数在计算机内部的储存形式,最快的方法就是使用这个运算符
-1 >>> 0 // 4294967295
上面代码表示,-1
作为32位整数时,内部的储存形式使用无符号整数格式解读,值为 4294967295(即(2^32)-1
,等于11111111111111111111111111111111
)
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
、D
三个开关,我们可以构造一个掩码变量
var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011
上面代码对A
、B
、D
三个变量进行二进制或运算,得到掩码值为二进制的1011
有了掩码,二进制或运算可以确保打开指定的开关
flags = flags | mask;
上面代码中,计算后得到的flags
变量,代表三个开关的二进制位都打开了
二进制与运算可以将当前设置中凡是与开关设置不一样的项,全部关闭
flags = flags & mask;
异或运算可以切换(toggle)当前设置,即第一次执行可以得到当前设置的相反值,再执行一次又得到原来的值
flags = flags ^ mask;
二进制否运算可以翻转当前设置,即原设置为0
,运算后变为1
;原设置为1
,运算后变为0
flags = ~flags;
10.参考链接
其他运算符,运算顺序
1. void 运算符
void
运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
void 0 // undefined
void(0) // undefined
上面是void
运算符的两种写法,都正确。建议采用后一种形式,即总是使用圆括号。因为void
运算符的优先性很高,如果不使用括号,容易造成错误的结果。比如,void 4 + 7
实际上等同于(void 4) + 7
下面是void
运算符的一个例子
var x = 3;
void (x = 5) //undefined
x // 5
这个运算符的主要用途是浏览器的书签工具(Bookmarklet),以及在超级链接中插入代码防止网页跳转
下面代码中,点击链接后,会先执行onclick
的代码,由于onclick
返回false
,所以浏览器不会跳转到 example.com
<script>
function f() {
console.log('Hello World');
}
</script>
<a href="http://example.com" onclick="f(); return false;">点击</a>
void
运算符可以取代上面的写法
<a href="javascript: void(f())">文字</a>
下面是一个更实际的例子,用户点击链接提交表单,但是不产生页面跳转
<a href="javascript: void(document.form.submit())">提交</a>
2. 逗号运算符
逗号运算符用于对两个表达式求值,并返回后一个表达式的值
在'a', 'b' // "b"
var x = 0;
var y = (x++, 10);
x // 1
y // 10
逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作
var value = (console.log('Hi!'), true);
// Hi!
// 先执行逗号前的操作,然后返回逗号后的值
value // true
3. 运算顺序
3.1 优先级
- 小括号
()
- 一元运算符
++
--
!
- 算数运算符 先
*
/
后+
-
- 关系运算符
>
>=
<
<=
- 相等运算符
==
!=
===
!==
- 逻辑运算符 先
&&
后||
- 赋值运算符
=
- 逗号运算符
,
3.2圆括号的作用
圆括号(()
)可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的表达式会第一个运算
(1 + 2) * 3 // 9
函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数
function f() {
return 1;
}
(f) // function f(){return 1;}
f() // 1
3.3 左结合与右结合
JavaScript 语言的大多数运算符是“左结合”
x + y + z
// 引擎解释如下
(x + y) + z
少数运算符是“右结合”,其中最主要的是赋值运算符(=
)和三元条件运算符(?:
)以及指数运算符(**
)
w = x = y = z;
q = a ? b : c ? d : e ? f : g;
2 ** 3 ** 2
// 上面代码的解释方式如下:
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));
2 ** (3 ** 2)