距离上一次写JavaScript基础篇笔记已经过去了好久好久,咕咕咕

传送门:JavaScript基础篇(一)

上次介绍到js中的数据类型 ,这篇就接着上次的继续

操作符

ECMA-262描述了一组用于操作数据值的操作符,包括算术操作符(如加号和减号)、位操作符、关系操作符和相等操作符

ECMAScript操作符的与众不同之处在于,它们能够适用于很多值,例如字符串、数字值、布尔值,甚至对象

不过,在应用于对象时,相应的操作符通常都会调用对象的 valueOf() 和(或)toString() 方法,以便取得可以操作的值

一元操作符

顾名思义,只能操作一个值的操作符叫做一元操作符

递增和递减操作符

递增和递减操作符直接借鉴自C,而且各有两个版本:前置型和后置型

顾名思义,前置型应该位于要操作的变量之前,而后置型则应该位于要操作的变量之后

例如:在使用前置递增操作符给一个数值加1时,要把两个加号(++)放在这个数值变量前面

1
2
3
var age = 20;
++age;
// age = 21

执行这个前置递增操作与执行以下操作的效果相同:

1
2
3
var age = 20;
age = age + 1;
// age = 21

前置递减操作的方法也类似:

1
2
3
var age = 20;
--age;
// age = 19

执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的

在计算机科学领域,这种情况通常被称作副效应

例如:

1
2
3
4
5
6
7
var age = 20;
var anotherAge = --age + 2;

console.log(age);
// 19
console.log(anotherAge);
// 21

变量anotherAge的初始值等于变量age的值前置递减之后加2。由于先执行了减法操作,age的值变成了19,所以再加上2的结果就是21

由于前置递增和递减操作与执行语句的优先级相等,因此整个语句会从左至右被求值

1
2
3
4
5
6
var num1 = 2;
var num2 = 20;
var num3 = --num1 + num2;
// 21
var num4 = num1 + num2;
// 21

在这里,num3之所以等于21是因为num1先减去了1才与num2相加。而变量num4也等于21是因为相应的加法操作使用了num1减去1之后的值

后置型递增和递减操作符的语法不变(仍然分别是++和–),只不过要放在变量的后面而不是前面

后置递增和递减与前置递增和递减有一个非常重要的区别,即递增和递减操作是在包含它们的语句被求值之后才执行的

例如把上面的那个例子反过来写:

1
2
3
4
5
6
var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2;
// 22
var num4 = num1 + num2;
// 21

在计算num3时使用了num1的原始值完成了加法计算,而num4则使用了递减后的值

所以要特别注意哦

这4个操作符对任何值都适用,也就是它们不仅适用于整数,还可以用于字符串、布尔值、浮点数值和对象

在应用于不同的值时,递增和递减操作符遵循下列规则:

  • 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减1的操作;字符串变量变成数值变量
  • 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为NaN;字符串变量变成数值变量
  • 在应用于布尔值false时,先将其转换为0再执行加减1的操作;布尔值变量变成数值变量
  • 在应用于布尔值true时,先将其转换为1再执行加减1的操作;布尔值变量变成数值变量
  • 在应用于浮点数值时,执行加减1的操作
  • 在应用于对象时,先调用对象的 valueOf() 方法以取得一个可供操作的值,然后对该值应用前述规则,如果结果是NaN,则在调用toString()方法后再应用前述规则;对象变量变成数值变量

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var s1 = "2";
var s2 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function () {
return -1;
}
};

s1++; // 变成数值3
s2++; // 变成NaN
b++; // 变成数值1
f--; // 变成0.10000000000000009 浮点舍入错误
o--; // 变成数值-2

一元加和减操作符

一元加操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响,例如

1
2
3
var age = 20;
age = +num;
// 20

在对非数值应用一元加操作符时,该操作符会像 Number() 转型函数一样对这个值执行转换

换句话说,布尔值false和true将被转换为0和1,字符串值会被按照一组特殊的规则进行解析,而对象是先调用它们的 valueOf() 和(或)toString() 方法,再转换得到的值

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var s1 = "01";
var s2 = "1.1";
var s3 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function () {
return -1;
}
};

s1 = +s1; // 值变成数值1
s2 = +s2; // 值变成数值1.1
s3 = +s3; // 值变成NaN
b = +b; // 值变成数值0
f = +f; // 值未变,仍然是1.1
o = +o; // 值变成数值-1

一元减操作符主要用于表示负数,例如将1转换成-1

1
2
3
var num = 1;
num = -num
// -1

在将一元减操作符应用于数值时,该值会变成负数(如上面的例子所示)

而当应用于非数值时,一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var s1 = "01";
var s2 = "1.1";
var s3 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function () {
return -1;
}
};

s1 = -s1; // 值变成数值-1
s2 = -s2; // 值变成数值-1.1
s3 = -s3; // 值变成NaN
b = -b; // 值变成数值0
f = -f; // 值变成-1.1
o = -o; // 值变成数值1

小提示:

一元加和减操作符主要用于基本的算术运算,也可以像前面示例所展示的一样用于转换数据类型

位操作符

位操作符用于在最基本的层次上,即按内存中表示数值的位来操作数值

ECMAScript中的所有数值都以IEEE-75464位格式存储,但位操作符并不直接操作64位的值,而是先将64位的值转换成32位的整数,然后执行操作,最后再将结果转换回64位

对于开发人员来说,由于64位存储格式是透明的,因此整个过程就像是只存在32位的整数一样

对于有符号的整数,32位中的前31位用于表示整数的值,第32位用于表示数值的符号:0表示正数,1表示负数,这个表示符号的位叫做符号位,符号位的值决定了其他位数值的格式

正数以纯二进制格式存储,31位中的每一位都表示2的幂

第一位(叫做位0)表示20,第二位表示21,以此类推。没有用到的位以0填充,即忽略不计

例如,数值18的二进制表示是00000000000000000000000000010010简写为10010

负数同样以二进制码存储,但使用的格式是二进制补码

计算一个数值的二进制补码,需要经过下列3个步骤:

  1. 求这个数值绝对值的二进制码
  2. 求二进制反码,即将0替换为1,将1替换为0
  3. 得到的二进制反码加1

例如,要求-18的二进制补码,先求18的二进制码:

1
00000000000000000000000000010010

然后,求其二进制反码,即0和1互换:

1
111111111111111111111111101101

最后,二进制反码加1:

1
111111111111111111111111101110

这就是-18的二进制表示

要注意的是,在处理有符号整数时,是不能访问位31的

ECMAScript会尽力向我们隐藏所有这些信息,换句话说,在以二进制字符串形式输出一个负数时,我们看到的只是这个负数绝对值的二进制码前面加上了一个负号

1
2
3
var num = -18;
alert(num.toString(2));
// "-10010"

要把数值-18转换成二进制字符串时,得到的结果是”-10010”,这说明转换过程理解了二进制补码并将其以更合乎逻辑的形式展示了出来

默认情况下,ECMAScript中的所有整数都是有符号整数。不过,当然也存在无符号整数。对于无符号整数来说,第32位不再表示符号,因为无符号整数只能是正数。而且,无符号整数的值可以更大,因为多出的一位不再表示符号,可以用来表示数值

在ECMAScript中,当对数值应用位操作符时,转换32位再转换64会带来一个严重的副效应,即在对特殊的NaN和Infinity值应用位操作时,这两个值都会被当成0来处理

如果对非数值应用位操作符,会先使用 Number() 函数将该值转换为一个数值(自动完成),然后再应用位操作,得到的结果将是一个数值

按位非(NOT)

按位非操作符由一个波浪线(~)表示,执行按位非的结果就是返回数值的反码,按位非是ECMAScript操作符中少数几个与二进制计算有关的操作符之一

1
2
3
4
var num1 = 25;	// 二进制 00000000000000000000000000011001
var num2 = ~num1; // 二进制 11111111111111111111111111100110
alert(num2);
// -26

对25执行按位非操作,结果得到了-26

按位非操作的本质:操作数的负值减1

所以等同于:

1
2
3
4
var num1 = 25;
var num2 = -num1 - 1;
alert(num2);
// -26

虽然以上代码也能返回同样的结果,但由于按位非是在数值表示的最底层执行操作,因此速度更快

按位与(AND)

按位与操作符由一个和号字符(&)表示,它有两个操作符数

从本质上讲,按位与操作就是将两个数值的每一位对齐,然后根据下表中的规则,对相同位置上的两个数执行AND操作:

第一个数值的位 第二个数值的位 结果
1 1 1
1 0 0
0 1 0
0 0 0

简而言之,按位与操作只在两个数值的对应位都是1时才返回1,任何一位是0,结果都是0

这tm让我想起了我挂科的逻辑电路,,,

例如:

1
2
3
var result = 25 & 3;
alert(result);
// 1

原理:

1
2
3
00000000000000000000000000011001 = 25
00000000000000000000000000000011 = 3
00000000000000000000000000000001 = 25 & 3

25和3的二进制码对应位上只有一位同时是1

按位或(OR)

按位或操作符由一个竖线符号(|)表示,同样也有两个操作数。按位或操作遵循下面这个真值表

艹,又是真值表

第一个数值的位 第二个数值的位 结果
1 1 1
1 0 1
0 1 1
0 0 0

按位或操作在有一个位是1的情况下就返回1,而只有在两个位都是0的情况下才返回0

1
2
3
var res = 25 | 3;
alert(res);
// 27

原理:

1
2
3
00000000000000000000000000011001 = 25
00000000000000000000000000000011 = 3
00000000000000000000000000011011 = 25 | 3

这两个数值的都包含4个1,因此可以把每个1直接放到结果中,二进制码11011等于十进制值27

按位异或(XOR)

按位异或操作符由一个插入符号(^)表示,也有两个操作数。以下是按位异或的真值表

第一个数值的位 第二个数值的位 结果
1 1 0
1 0 1
0 1 1
0 0 0

按位异或与按位或的不同之处在于,这个操作在两个数值对应位上只有一个1时才返回1,如果对应的两位都是1或都是0,则返回0

1
2
3
var res = 25 ^ 3;
alert(res);
// 26

原理:

1
2
3
00000000000000000000000000011001 = 25
00000000000000000000000000000011 = 3
00000000000000000000000000011010 = 25 | 3

这两个数值都包含4个1,但第一位上则都是1,因此结果的第一位变成了0。而其他位上的1在另一个数值中都没有对应的1,可以直接放到结果中。二进制码11010等于十进制值26(注意这个结果比执行按位或时小1)

左移

左移操作符由两个小于号(<<)表示,这个操作符会将数值的所有位向左移动指定的位数

例如:

1
2
3
4
var oldNum = 2;
// 二进制就是10
var newNum = oldNum << 5;
// 现在变成了1000000,也就是十进制的64

在向左移位后,原数值的右侧多出了5个空位。左移操作会以0来填充这些空位,以便得到的结果是一个完整的32位二进制数

注意,左移不会影响操作数的符号位。换句话说,如果将-2向左移动5位,结果将是-64,而非64

有符号的右移

有符号的右移操作符由两个大于号(>>)表示,这个操作符会将数值向右移动,但保留符号位(即正负号标记)

1
2
3
4
var oldNum = 64;
// 二进制100000
var newNum = oldNum >> 5;
// 二进制10 十进制2