- js提供了指数运算符:* 指数运算符:
x ** y
加法运算符
基本规则
- js允许非数值的相加:布尔值自动转化为数值
true + true // 2
1 + true // 2
- 两个字符串相加,变成连接运算符
'a' + 'bc' // "abc"
- 字符串+非字符串:非字符串转为字符串,在连接在一起
1 + 'a' // "1a"
false + 'a' // "falsea"
- 运算子的不同,导致了相加和连接的行为不同,这种现象称为重载。注意,由于从左到右的运算顺序,字符串位置不同,运算结果不同
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。
对象的相加
当运算子为对象时,必须先转换成原始类型的值,然后再相加
var obj = { p: 1 };
obj + 2 // "[object Object]2"
对象转换成原始类型的值的规则
- 首先调用对象的
valueOf
方法,返回对象自身
var obj = { p: 1 };
obj.valueOf() // { p: 1 }
- 然后调用对象的
toString
方法,将其转为字符串。注意,对象的toString
方法默认返回[object Object]
,所以就得到了最前面那个例子的结果。
var obj = { p: 1 ;}
obj.valueOf().toString() // "[object Object]"
得到规则之后,我们就可以自己定义valueOf
方法或toString
方法,得到想要的结果。
- 自定义valueOf
var obj = {
valueOf: function () {
return 1;
}
};
obj + 2 // 3
上面代码中,我们定义obj
对象的valueOf
方法返回1
,于是obj + 2
就得到了3
。这个例子中,由于valueOf
方法直接返回一个原始类型的值,所以不再调用toString
方法。
- 自定义toString
var obj = {
toString: function () {
return 'hello';
}
};
obj + 2 // "hello2"
上面代码中,对象obj
的toString
方法返回字符串hello
。前面说过,只要有一个运算子是字符串,加法运算符就变成连接运算符,返回连接后的字符串。
- 这里有一个特例,如果运算子是一个
Date
对象的实例,那么会优先执行toString
方法。
var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 2 // "hello2"
上面代码中,对象obj
是一个Date
对象的实例,并且自定义了valueOf
方法和toString
方法,结果toString
方法优先执行。
余数运算符
- 运算结果的正负号由第一个运算子正负号决定,不遵循算数法则
-1 % 2 // -1
1 % -2 // 1
因此,为了得到负数的正确余数值,可以先用绝对值函数
// 错误的写法
function isOdd(n) {
return n % 2 === 1;
}
isOdd(-5) // false
isOdd(-4) // false
// 正确的写法
function isOdd(n) {
return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false
数值运算符、负数值运算符
数值运算符(+
)同样使用加号,但它是一元运算符(只需要一个操作数),而加法运算符是二元运算符(需要两个操作数)。
数值运算符的作用在于可以将任何值转为数值(与Number
函数的作用相同)。
+true // 1
+[] // 0
+{} // NaN
上面代码表示,非数值经过数值运算符以后,都变成了数值(最后一行NaN
也是数值)。具体的类型转换规则,参见《数据类型转换》一章。
负数值运算符(-
),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符。
var x = 1;
-x // -1
-(-x) // 1
上面代码最后一行的圆括号不可少,否则会变成自减运算符。
数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值。
指数运算符
注意,指数运算符是右结合,而不是左结合。即多个指数运算符连用时,先进行最右边的计算。
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
比较运算符
比较运算符可以分为相等比较和非相等比较。对于非相等比较,先看两个运算子是否都是字符串,如果是,就按照字典顺序比较(实际上是比较 Unicode 码点);否则,将两个运算子都转成数值,再比较数值的大小
非相等运算符字符串的比较
若两个都是字符串,按照字典顺序进行比较,即比较Unicode码点
非相等运算符非字符串的比较
只要两个运算子有一个不是字符串,就属于非字符串比较
- 若都是原始类型,(字母、字符串、布尔值)先转换成数值再比较。这里注意的是,任何值与NaN比较,返回的都是false(包括NaN本身)
- 对象会先调用
valueOf
方法;如果返回的还是对象,再接着调用toString
方法,最终转换为原始类型的值。在进行比较
严格相等运算符
严格相等运算符(===)先比较类型,后比较值,只要一个不相等就返回false;而相等运算符当类型不同时,会先转换成相同类型,再比较
- NaN与任何值都不相等(包括本身),+0=-0
- 复合类型的数据比较时,不是比较值是否相等,而是比较是否指向同一个地址
如果两个变量引用同一个对象,则它们相等。
var v1 = {};
var v2 = v1;
v1 === v2 // true
注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。
var obj1 = {};
var obj2 = {};
obj1 > obj2 // false
obj1 < obj2 // false
obj1 === obj2 // false
上面的三个比较,前两个比较的是值,最后一个比较的是地址,所以都返回false
。
- undefined和null与自身严格相等。另外,由于变量声明后默认值为undefined,所以两个只声明未赋值的变量是相等的
var v1;
var v2;
v1 === v2 // true
相等运算符
- 原始类型的值转换成数值再比较
- 对象转换成原始函数的值再比较
- undefined和null相互比较为true,与其他比较为false
布尔运算符:将表达式转为布尔值
!取反运算符
除了以下6个值取反后为true,其余都是false:
undefined
null
false
0
NaN
- 空字符串(
''
)
若对一个值连续两次取反运算,等于转为对应的布尔值,与Boolean函数作用相同,是一种常见的类型转换写法
&&且运算符
运算规则:若第一个为true,则返回第二个的值;若第一个false,直接返回第一个的值,不再对第二个求值
't' && '' // ""
't' && 'f' // "f"
't' && (1 + 2) // 3
'' && 'f' // ""
'' && '' // ""
var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1
这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代if
结构,比如下面是一段if
结构的代码,就可以用且运算符改写
if (i) {
doSomething();
}
// 等价于
i && doSomething();
且运算符可以多个连用,结果会返回第一个为false的值;若所有表达式都为false,则返回最后一个表达式的值
||或运算符
运算规则:
如果第一个运算子的布尔值为true
,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false
,则返回第二个运算子的值。
或运算符可以多个连用,这时返回第一个布尔值为true
的表达式的值。如果所有表达式都为false
,则返回最后一个表达式的值。
或运算符常用于为一个变量设置默认值。
function saveText(text) {
text = text || '';
// ...
}
// 或者写成
saveText(this.text || '')
上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。
二进制运算符
位运算符只对整数起作用,若运算子不是整数,会自动转换为整数后再执行
i = i | 0;
上面这行代码的意思,就是将i
(不管是整数或小数)转为32位整数
二进制或运算符|
- 将小数与0进行二进制或运算,可用于去除小数的小数部分
二进制与运算符&
二进制否运算符~
~ 3 // -4
- 返回结果有时难以理解,因为涉及到就三级内部的数值表示机制
3
的32位整数形式是00000000000000000000000000000011
,二进制否运算以后得到11111111111111111111111111111100
。由于第一位(符号位)是1,所以这个数是一个负数。JavaScript 内部采用补码形式表示负数,即需要将这个数减去1,再取一次反,然后加上负号,才能得到这个负数对应的10进制值。这个数减去1等于11111111111111111111111111111011
,再取一次反得到00000000000000000000000000000100
,再加上负号就是-4
。考虑到这样的过程比较麻烦,可以简单记忆成,一个数与自身的取反值相加,等于-1。
- 对一个整数连续两次二进制否运算,得到本身。对小数连续两次二进制否运算,达到取整效果
- 当对字符串等其他类型的值进行二进制否运算时,js会先调用Number函数,将其转化为数值
异或运算符
- “异或运算”有一个特殊运用,连续对两个数
a
和b
进行三次异或运算,a^=b; b^=a; a^=b;
,可以互换它们的值。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值。 - 异或运算也可以用来取整。
12.9 ^ 0 // 12
应用:开关作用
位运算符可以用作设置对象属性的开关。
假定某个对象有四个开关,每个开关都是一个变量。那么,可以设置一个四位的二进制数,它的每个位对应一个开关。
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;
void运算符
- void运算符的作用是执行一个表达式,然后不返回任何值。
var x = 3;
void (x = 5) //undefined
x // 5
- 注意的是,我们建议使用void运算符时用圆括号括起来,因为void运算符优先级很高,避免错误结果。比如,
void 4 + 7
实际上等同于(void 4) + 7
。 - 这个运算符主要用于做浏览器的书签工具,以及在超级链接中插入代码防止页面跳转
<a href="javascript: void(document.form.submit())">
提交
</a>
如上边,用户点击链接提交表单,但是不产生页面跳转
,逗号运算符
- 逗号运算符用于对两个表达式求值,并返回后一个表达式的值
var x = 0;
var y = (x++, 10);
x // 1
y // 10
- 用途:在返回一个值前,进行一些辅助操作
var value = (console.log('Hi!'), true);
// Hi!
value // true
运算顺序
- 注意括号的使用,来避免运算顺序与预期不同
- 左结合和右结合。大部分都是左结合,但是赋值运算符、三元运算符和指数运算符是右结合
w = x = y = z;
q = a ? b : c ? d : e ? f : g;
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));
2 ** 3 ** 2
// 相当于 2 ** (3 ** 2)
// 512
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。