JavaScript中的运算符易错点
ES5的运算中,要理解和记忆运算这方面的知识有点小繁杂,如:在一些情况下,JS会自动把非数值转化为数值,或是把数值转化为非数值,或是不转化。
算术符
JavaScript允许非数值的相加
//布尔会自动转化为字符
1+true//2;
//字符串相加为字符串连接起来
'a'+'bcd'//'abcd';
一个运算子为字符串,一个为非字符串,先把非字符串转化为字符串再按上一行的做法做加法
1 + 'a' // "1a"
false + 'a' // "falsea"
不同的运算顺序对结果也会有影响
//只有加法运算会发生以下此类重载,减法、乘法、除法不会(它们的做法是将所有的子运算转化为数字再做运算)
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75" 运算顺序由左边的3+4开始,再将所得结果与‘5’字符串相加
JavaScript允许对象的相加。先将对象转化原始类型的值,再与其他数相加。如下例子中object的原始类型为[object Object]。这个转化过程有一定的规则:首先,自动调用对象的valueOf方法,这时总是返回对象自身,这时再自动调用对象的toString方法,将其转为字符串。
var object={a:6};
object+1 //"[object Object]1"
var obj = { a: 1 };
obj.valueOf() // { p: 1 } 返回了对象自身
var obj = { p: 1 };
obj.valueOf().toString() // "[object Object]" 再返回对象自身的基础上转化为其字符串
此外,valueOf()方法和toString()方法可以自定义,如下:
var object={
valueOf:function(){return 1;}
}
object+2=3;
var object={
toString:function(){return "Hello";}
}
object+2="Hello2";
Date类型在这此为特例。它的转化过程为先调用toString方法再调用valueOf方法。如下所示:
var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 2 // "hello2"
余数运算符:在浮点运算中应用较少。其结果取决于第一个运算子。为了得到负数的正确余数的话,不能直接使用该运算符。
function isOdd(n) {
return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false
自增、自减运算与C++等编程语言中完全相同。
var x = 1;
++x // 2
x // 2
--x // 1
x // 1
数值运算符(+)、负数值运算符(-)都有将非数值转化为数值的作用。
+true // 1
+[] // 0
+{} // NaN
指数运算符(运算顺序从右往左)
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
赋值运算符:与C++等编程语言中常见用法完全相同。该运算符可以与其他算术运算符或位运算符结合使用。格式为:a X =b
;(X为加或减,乘或除,取余或取指数,左移或右移,X也可以是&、|、^)
比较运算符
一、非相等运算符:字符串的比较 字符串按照字典顺序进行比较
'cat' > 'dog' // false
'cat' > 'catalog' // false
'cat' > 'Cat' // true'
'大' > '小' // false 由于所有字符都有 Unicode 码点,因此汉字也可以比较。
二、非相等运算符:非字符串的比较
(1)原始类型值 如果两个运算子都是原始类型的值,先转成数值再比较
true>0;//true bool值转化为数值
15<'16'//true 字符串转化为数值
true>false;//true bool值转化为数值
注意任何值与NaN比较都是false(NaN与NaN比较也是)
NaN<1;//false
NaN>=1;//false
NaN<=NaN;//false
NaN<'1'//false
(2)对象
如果运算子是对象,会转为原始类型的值,再进行比较
。
对象转换成原始类型的值,算法是先调用valueOf方法
;如果返回的还是对象,再接着调用toString方法
。
①一个运算子为对象的实例如下:
var a = [3];
a>'11';//true 此处的比较的是3与1的比较
a.valueOf = function () { return '3' };
a > '11' // false
// 等同于 [3].valueOf() > '11'
// 即 '3' > '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]'
三、严格相等运算符
(1)不同类型的值,一定返回false
;
(2)同一类的原始类型值。只要值也相等就返回true
。反例如下:
NaN === NaN // false
易出错的有下面这种情况。十进制和十六进制的数类型其实是相同的。
1 === 0x1 // true
(3)复合类型值
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址
。因此,比较两个空对象、两个空数组、两个空函数,结果都是不相等。
{} === {} // false 对象不完全相等
[] === [] // false数组不完全相等
(function () {} === function () {}) // false函数不完全相等
指向同一个地址的严格相等的情况如下:
var a1={};
var a2=a1;
a2===a1;//true
对于对象的大于或小于的比较,比较的值,而不是地址。
var object1={};
var object2={};
object1<objecr2;//false
object1===objecr2;//false
(4)undefined 和 null
undefined和null与自身严格相等。
null===null;//true
undefined===undefined;//true
var v1;
var v2;
v1===v2;//true
四、相等运算符,有一下四种情况:
(1)原始类型值原始类型的值会转换成数值再进行比较
。
其中bool转化为0或1,含字母的字符串转化为NaN,空字符转化为0。
1 == true // true
// 等同于 1 === Number(true)
0 == false // true
// 等同于 0 === Number(false)
2 == true // false
// 等同于 2 === Number(true)
2 == false // false
// 等同于 2 === Number(false)
'true' == true // false
// 等同于 Number('true') === Number(true)
// 等同于 NaN === 1
'' == 0 // true
// 等同于 Number('') === 0
// 等同于 0 === 0
'' == false // true
// 等同于 Number('') === Number(false)
// 等同于 0 === 0
'1' == true // true
// 等同于 Number('1') === Number(true)
// 等同于 1 === 1
'\n 123 \t' == 123 // true
// 因为字符串转为数字时,省略前置和后置的空格
(2)对象与原始类型值比较
①对象与数值将对象转化为数值
②对象与字符串将对象转化为字符串
③对象与Bool类型将对象转化为数值
// 对象与数值比较时,对象转为数值
[1] == 1 // true
// 等同于 Number([1]) == 1
// 对象与字符串比较时,对象转为字符串
[1] == '1' // true
// 等同于 String([1]) == '1'
[1, 2] == '1,2' // true
// 等同于 String([1, 2]) == '1,2'
// 对象与布尔值比较时,两边都转为数值
[1] == true // true
// 等同于 Number([1]) == Number(true)
[2] == true // false
// 等同于 Number([2]) == Number(true)
(3)undefined 和 null
undefined和null与其他类型的值比较时,结果都为false
,它们互相比较时结果为true
。
五、不相等运算符
先求得相等运算的结果再返回其相反数即可。
布尔运算符
一、取反运算符(!)
以下六个值取反后得到true,其余得到false.
undefined
NaN
' '
false
null
0
以下做法与Boolean函数相同作用:
var a =1;
!!a;//将a转化为bool类型
二、且运算符(&&)
与C++等编程语言中的“且”的规则不太一样:
(1)左运算子为false.返回第一个运算子的值
(而不是bool值),且完全不理会第二个运算子。这种跳过第二个运算子的机制,被称为“短路”。
var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1
(2)左运算子为true.返回第二个运算子的值
(而不是bool值)。
(3)有多个运算子的运算.返回第一个bool值为false的运算子的值
,若不存在这样的运算子,则返回最后一个运算子的值
。
三、或运算符(||)
与&&的运算类似。
(1)左运算子为false.返回第二个运算子的值
(而不是bool值)。
(2)左运算子为true.返回第一个运算子的值
(而不是bool值),且完全不理会第二个运算子。此处发生了“短路”。
var x = 1;
true || (x = 2) // true
x // 1
(3)有多个运算子的运算.返回第一个bool值为true的运算子的值
,若不存在这样的运算子,则返回最后一个运算子的值
。
四、三元运算符(?:)
与if…else语句类似,但略有不同:
if…else是语句,没有返回值;三元条件表达式是表达式,具有返回值。
二进制运算符
二进制运算是32位带符号的整数间的运算。如果运算子不是32位带符号的整数,则自动将其转化为32位带符号的整数。
利用这一特性,可以写如下几个将任意数转化为32位带符号整数的函数:
function v1(x)
{return x|0;}
function v2(x)
{return x<<0;}
function v3(x)
{return x^0;}
function v4(x)
{return (x^1)^1;}
function v5(x)
{return ~~x;}
function v6(x)
{return x>>0;}
一、二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1。
0 | 3 // 3
二、二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0。
0 & 3 // 0
三、二进制否运算符(not):符号为~,表示对一个二进制位取反。
①对Number类型的取反:先将一个数写成有符号32位二进制的形式,再将每一位取反,再将该有符号32位二进制转化为整数。
var a=~5
-6
5的32位二进制为:0000 0000 0000 0000 0000 0000 0000 0101
每一位取反之后得1111 1111 1111 1111 1111 1111 1111 1010
该数减一取反后为:0000 0000 0000 0000 0000 0000 0000 1001 ,为6。因此~5为-6。
可以简单地记为:一个数与自身的取反相加为-1.
②对字符串类型的取反:先将字符串转化为数值。
~'12'//-13
~'0032'//-33
~'Hello'//-1
~'33www'//-1
~'0xFFFFFFF0'//16
③对其他类型的取反:先将该类型转化为数值。
// 相当于 ~Number([])
~[] // -1
// 相当于 ~Number(NaN)
~NaN // -1
// 相当于 ~Number(null)
~null // -1
注意将前三种位运算与布尔运算中的取反(!)、且(||)、或(&&)区别开来。
四、异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0。
交换两个数的值的过程如下:
var a = 10;
var b = 99;
a ^= b, b ^= a, a ^= b;
a // 99
b // 10
这种方法的好处是不需要另外设临时变量。
五、左移运算符(left shift):符号为<<。
向左移,在尾部补零。
4 << 1
// 8
-4 << 1
// -8
将颜色的 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"
六、右移运算符(right shift):符号为>>。
如果是正数,头部全部补0;如果是负数,头部全部补1。右移运算符基本上相当于除以2的指定次方(最高位即符号位参与移动)。
4>>1//2
-4>>2//-1
-4>>1//-2
32>>4//2
21>>2//5
21.5>>2//5
七、头部补零的右移运算符(zero filled right shift):符号为>>>。
对于正数,头部补零与不补零的右移运算结果一样。对于负数,由于一个补的是零一个补的是1,结果很不一样。
4 >>> 1
// 2
-4 >>> 1
// 2147483646
/*
// 因为-4的二进制形式为11111111111111111111111111111100,
// 带符号位的右移一位,得到01111111111111111111111111111110,
// 即为十进制的2147483646。
*/
查看一个负整数在计算机内部的储存形式,最快的方法就是使用这个运算符。
-1 >>> 0 // 4294967295
其他运算符、运算顺序
一、void运算符
执行一个表达式,并返回undefined
因为void运算符的优先性很高,如果不使用括号,容易造成错误的结果。
用户点击链接提交表单,但是不产生页面跳转,如下:
<a href="javascript: void(document.form.submit())">
提交
</a>
二、逗号运算符
逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作。
var value = (console.log('Hi!'), true);
// Hi!
value // true
三、运算顺序
①优先级
五个运算符的优先级从高到低依次为:小于等于(<=)
、严格相等(===)
、或(||)
、三元(?:)
、等号(=)
。
②圆括号的作用
作用有二:一是把表达式放在圆括号之中,提升运算的优先级;另一是跟在函数的后面,作用是调用函数。
注意 :因为圆括号不是运算符,所以不具有求值作用,只改变运算的优先级。
var x = 1;
(x) = 2;
//如果圆括号具有求值作用,那么就会变成1 = 2,这是会报错了。
//但是,上面的代码可以运行,这验证了圆括号只改变优先级,不会求值。
圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错。
(var a = 1)
// SyntaxError: Unexpected token var
③左结合和右结合
绝大部分运算都是左结合,以下为三种右结合的特例:
a=b=c=d;//a=(b=(c=d))
a=b?c:d?e:f?g:h;//b?c:(d?e:(f?g:h))