JavaScript 有 51 个运算符 。
运算符 | 说明 | 优先级 | 操作类型 | 运算顺序 |
---|---|---|---|---|
. | 读写对象的属性 | 15 | 对象 . 标识符 | 从左到右 |
[] | 数组下标 | 15 | 数组 [ 整数 ] | 从左到右 |
() | 调用函数 | 15 | 函数 ( 参数 ) | 从左到右 |
new | 创建新对象 | 15 | 构造函数调用 | 从右到左 |
++ | 先递增或后递增 | 14 | 变量、对象的属性、数组的元素 | 从右到左 |
-- | 先递减或后递减运算 | 14 | 变量、对象的属性、数组的元素 | 从右到左 |
- | 一元减法运算 ( 或取负、取反 ) | 14 | 数字 | 从右到左 |
+ | 一元加法运算 | 14 | 数字 | 从右到左 |
~ | 按位取反操作 | 14 | 整数 | 从右到左 |
! | 逻辑取反操作 | 14 | 布尔值 | 从右到左 |
delete | 删除对象的一个自定义属性 | 14 | 变量、对象的属性、数组的元素 | 从右到左 |
typeof | 返回数据类型 | 14 | 任意 | 从右到左 |
void | 返回未定义的值 | 14 | 任意 | 从右到左 |
* ) | 乘法运算 | 13 | 数字 | 从左到右 |
** | 指数运算符 | 13 | 数字 | 从左到右 |
/ | 除法运算 | 13 | 数字 | 从左到右 |
% | 取余 | 13 | 数字 | 从左到右 |
+ | 加法运算 | 12 | 数字 | 从左到右 |
- | 减法运算 | 12 | 数字 | 从左到右 |
+ | 连接字符串操作 | 12 | 字符串 | 从左到右 |
<< | 小于 | 10 | 数字 " + " 、字符串 | 从左到右 |
<<< | 左移 | 11 | 整数 | 从左到右 |
>> | 带符号右移 | 11 | 整数 | 从左到右 |
>>> | 不带符号右移 | 11 | 整数 | 从左到右 |
<= | 小于等于 | 10 | 数字 " + " 、字符串 | 从左到右 |
> | 大于 | 10 | 数字 " + " 、字符串 | 从左到右 |
>= | 大于等于 | 10 | 数字、字符串 | 从左到右 |
instanceof | 检查对象类型 | 10 | 对象 构造函数 | 从左到右 |
in | 检查一个属性是否存在 | 10 | 字符串 in 对象 | 从左到右 |
== | 比较是否相等 | 9 | 任意 | 从左到右 |
!= | 比较是否不相等 | 9 | 任意 | 从左到右 |
=== | 比较是否等同 ( 同值同类 ) | 9 | 任意 | 从左到右 |
!== | 比较是否不全等 ( 不同值同类 ) | 9 | 任意 | 从左到右 |
& | 按位与操作 | 8 | 整数 | 从左到右 |
^ | 按位异或操作 | 7 | 整数 | 从左到右 |
\| | 按位或操作 | 6 | 整数 | 从左到右 |
&& | 逻辑与操作 | 5 | 整数 | 从左到右 |
\|\| | 逻辑或操作 | 4 | 整数 | 从左到右 |
?: | 条件运算符 ( 包含三个运算数 ) | 3 | 布尔值 ? 任意 : 任意 ; | 从右到左 |
= | 赋值运算 | 2 | 变量、对象的属性、数组的元素 " + "= 任意 | 从右到左 |
*= | 附带乘法操作的赋值运算 | 2 | 变量、对象的属性、数组的元素 " + "_= 任意 | 从右到左 |
/= | 附带除法操作的赋值运算 | 2 | 变量、对象的属性、数组的元素 " + " /= 任意 | 从右到左 |
%= | 附带取余的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " %= 任意 | 从右到左 |
+= | 附带加法的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " += 任意 | 从右到左 |
-= | 附带减法的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " -= 任意 | 从右到左 |
<<= | 附带左移操作的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " <<= 任意 | 从右到左 |
>>= | 附带带符号的右移操作的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " >>= 任意 | 从右到左 |
>>>= | 附带不带符号的右移操作的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " >>>= 任意 | 从右到左 |
&= | 附带按位与操作的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " &= 任意 | 从右到左 |
^= | 附带按位与操作的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " ^= 任意 | 从右到左 |
\|= | 附带按位或操作的赋值操作 | 2 | 变量、对象的属性、数组的元素 " + " |= 任意 | 从右到左 |
. | 多重计算操作 | 1 | 任意 | 从左到右 |
一般来说 , 运算符与运算数配合才能使用。其中运算符指定执行运算的方式,运算数明确运算符要操作的对象。
根据操作运算数的数量,运算符可以可以分为以下三类。
一元运算符
1 个运算符只对 1 个运算数进行某种运算,如值取反、位移、获取值类型、删除属性定义。
二元运算符
1 个运算符必须包含 2 个运算数。例如,相加,比大小 。大部分运算都是对两个运算数执行运算。
三元运算
1 个运算符必须包含三个运算数。 JavaScript 仅有一个三元运算符(?: 运算符),该运算符就是条件运算符,它是 if 语句的简化版。
alert(n = 5 - 2 * 2);// 返回 1
alert(n = ( 5 - 2 ) * 2);// 返回 6
alert((n = 5 - 2) * 2);//返回 6
alert((1 + n = 5 - 2) * 2);// 报错
alert(typeof typeof 5);// 返回 string
alert("10" - "20");// 返回 -10
alert(0 ? 1 : 2);// 返回 2
alert(3 > "5");// 返回 false
alert(3 > "2");// 返回 true
alert("string" > 5);// 返回 false
alert(10 + 20 );// 返回 30
alert("10" + "20");// 返回 "1020
alert(true * 5);// 返回 5
运算符只能在操作特定类型的数据,运算返回的也是特定类型的数据。
运算符一般不会对运算数本身产生影响,如算术运算符、比较运算符、条件运算符、取逆运算符、位与运算符。
+
-
*
/
%
-
++
--
+
根据操作数的数据类型,进行判断是相加还是相连操作。
NaN + 5 | NaN |
Infinity + Infinity | Infinity |
Infinity + 5 | Infinity |
-Infinity + -Infinity | -Infinity |
-Infinity + Infinity | NaN |
1 + 1 | 2 |
1 + "1" | "11" |
"1" + "1" | "11" |
3.0 + 4.3 +"" | "7.3" |
3.0 + "" + 4.3 | '34.3' |
-
在减法中,如果数字为字符串,先尝试转化为数值,再进行计算。如果有一个操作值不是数字,则返回 NaN 。
使用值减去 0 ,可快速转化为数值类型。
对于布尔值来说, parseFloat() 方法可以将 true 转化为 1 ,把 false 转化为 0 ,二减号将其视为 NaN 。
NaN - 5 | NaN |
Infinity - 5 | Infinity |
Infinity - Infinity | NaN |
-Infinity - -Infinity | NaN |
-Infinity -Infinity | -Infinity |
2 - "1" | 1 |
2 - "a" | NaN |
*
value | n |
---|---|
NaN * n | NaN |
Infinity * n | Infinity |
Infinity *(-n) | -Infinity |
Infinity * 0 | NaN |
Infinity *Infinity | Infinity |
/
value | n |
---|---|
NaN / n | NaN |
Infinity / n | Infinity |
-Infinity | `` |
Infinity / Infinity | NaN |
n / 0 | Infinity |
-Infinity | -Infinity |
n / -0 | -Infinity |
Infinity | Infinity |
value | n |
---|---|
Infinity % n | NaN |
Infinity % Infinity | NaN |
n % Infinity | n |
0 % n | 0 |
0 % Infinity | 0 |
n % 0 | NaN |
Infinity % 0 | NaN |
&&
||
!
大小比较
<
>
<=
>=
归属检查, in 运算符能够检测左侧的数是否为右边操作数的成员。其中左侧操作数是一个字符串,或是可转化为字符串的表达式 , 右侧是一个对象或数组。
如果,左侧的操作数不是对象,或右侧不是类型函数,则返回 false ,如果右侧不是
赋值时一种运算,但习惯上,把赋值独立成行,故称之为赋值语句。
赋值运算符的左侧运算符必须是变量、对象属性、数组元素。
简单的赋值运算符,就是把右侧的运算数的值直接赋值给左侧的变量。附加的赋值运算符,就是赋值之前还要对右侧的运算数执行某种操作,然后再赋值。
var a;
alert(
(a =
6 &&
(b = function () {
return a;
})()),
); // 返回 undefined
在上例中,逻辑与左侧的运算数是一个赋值表达式,右侧的运算数也是赋值表达式。但是左侧仅是一个简单的数字赋值,而右侧把一个函数的对象赋值给了变量 b 。在逻辑运算中,左侧赋值并没有真正的赋值给变量 a ,当逻辑与运算执行右侧的表达式时,该表达式把一个函数赋值给变量 b ,然后利用小括号运算符调用这个函数,返回变量 a 的值,结果并没有返回变量 a 的值为 6 ,而是 undefined 。
由于赋值运算作为表达式会产生副作用,即它能够改变变量的值,因此在使用时慎重,确保不引发潜在的危险。
var a = 6;
var b = function () {
return a;
};
alert(a && b()); // 返回 6
new
new 运算符可以构造一个新的对象 , 并初始化该对象。
new constructor(arguments);
constructor 必须是一个构造函数表达式,其后面跟着利用小括号包含的参数列表,参数可有可无,参数之间通过逗号分开。如函数调用没有参数,可以省略小括号。
new
运算符在被执行时,首先创造一个新对象,接着 new 运算符调用指定的构造函数(类)。
var a = new Array();
// 创建数组结构的对象,省略小括号
var b = new Array();
// 创建数组对象的结构
var c = new Array(1, 2, 3); // 创建新的数组,并初始化
alert(c[2]); // 3
对于自定义的类,只能够通过 new 运算符进行实例化。
var a = function () {
this.x = 1;
this.y = 2;
};
var b = new a();
alert(b.x);
// 返回 1
var a = function () {
this.x = 1;
this.y = 2;
};
var b = a;
alert(b.x); // 返回
undefined;
var a = {
x: 1,
y: 2,
};
var b = a;
alert(b.x); // 返回 1
delete
delete
能够删除指定的对象的属性、数组元素、变量。
不是所有的对象成员或变量可以删除,某些内置对象的预定义成员和客户端成员,以及使用 var 语句声明的变量都不可被删除。
在删除不存在的对象成员时,或者非对象成员、数组元素、变量时,也会返回 true ,应与成功删除相区分。
delete
只能删除值类型数据 , 不影响变量、属性、数组元素储存的原引用对象delete
运算符的删除不是清空值,而是释放空间delete
和 in
,可方便的操作对象的检查、删除、更新()
和 .
typeof
typeof 运算符返回它的操作数当前所容纳的数据的类型,这对于判断一个变量是否已被定义特别有用。
instanceof
运算符instanceof
运算符用来检测表达式是否是指定类的实例。
var myBlog = new String('lmssee.cn');
alert(myBlog instanceof String); // 返回 true
因为 Object 是一切类的基类,所以,任何对象都是 Object 的实例:
var myBlog = new String('lmssee.cn');
alert(myBlog instanceof Object); // 返回 true
expr1 ? expr2 : expr3;
逗号运算符是二元运算符,它能够先执行运算符左侧的运算数,然后再执行右侧的运算数,最后仅把右侧的运算值作为结果返回。
a = ((b = 1), (c = 2));
alert(a); // 2
alert(b); // 1
alert(c); // 2
(a = b = 1), (c = 2);
alert(a); // 1
alert(b); // 1
alert(c); // 2
void 运算符计算表达式,然后放弃其值,返回 undefined 。
void 多用于 URL 中执行 Javascript 表达式,但不需要表达式的计算结果。
表达式运算本质上是值运算,即求值运算。任何复杂的对象(如: object 、 Function 、 Array )等,从运算的角度来分析,其实就是系统对值的一种理解。由于运算值产生值,因此可以把所有的命令式语句来转化为表达式,并求值。
var i = 1;
(function () {
console.log(i + '');
i++ < 100 && arguments.callee();
// 逻辑判断并递归
})();
使用函数来转换循环结构,会存在内存溢出的风险,这是一种低效策略。由于函数递归运算每次都需要为每次函数调用保留私有空间,因此会耗费大量的系统资源。不过尾递归可以避免此类问题。
不用循环和分支结构,其它语句也就没有价值,如流程控制的子句 return 和 continue ,以及标签语句等。同时,函数式语言可以不使用寄存器,只需要值声明,而不需要变量声明,所以在函数式语言中,变量声明语句中也是不需要的。总之,在函数式语言中,除了值声明和函数中的返回子句外,其它语句都可以省略。
在表达式运算中,求值是运算的核心。函数作为表达式中的一个运算元,也具有值的含义。不管函数内部结构多么复杂,最终返回的只是一个值。
应当养成良好的编程习惯,良好的结构和代码组织能够降低代码的复杂度。对于函数式编程来说,实现代码的良好组织,使用函数应该是最有效的方法之一。
对于长表达式,特别是逻辑结构非常明显的表达式,应该对其进行格式化。从语义上分析,函数的调用实际上是表达式求值的过程。从这一点来说,在函数式编程中,函数是一种高效的连续运算的工具。对于循环结构来说,使用函数递归运算会存在很大的系统消耗,但是如果把循环语句封装在函数结构中,然后把函数当成值参与表达式的运算,实际上也是高效实现循环结构表达式。
接下来要介绍的操作符用于数值的底层操作,也就是操作内存中表示数据的比特(位)。 ECMAScript 中的所有数值都以 IEEE 754 64 位格式存储,但位操作并不直接应用到 64 位表示,而是先把值转换为 32 位整数,再进行位操作,之后再把结果转换为 64 位。对开发者而言,就好像只有 32 位整数一样,因 为 64 位整数存储格式是不可见的。既然知道了这些,就只需要考虑 32 位整数即可。
有符号整数使用 32 位的前 31 位表示整数值。第 32 位表示数值的符号,如 0 表示正, 1 表示负。这 一位称为符号位( sign bit ),它的值决定了数值其余部分的格式。正值以真正的二进制格式存储,即 31 位中的每一位都代表 2 的幂。第一位(称为第 0 位)表示 20 ,第二位表示 21 ,依此类推。
负值以一种称为二补数(或补码)的二进制编码存储。
一个数值的二补数通过如下 3 个步骤计算 得到:
按位非操作符用波浪符( ~ )表示,它的作用是返回数值的一补数。按位非是 ECMAScript 中为数 不多的几个二进制数学操作符之一。
let num1 = 25; // 二进制 00000000000000000000000000011001
let num2 = ~num1; // 二进制 11111111111111111111111111100110
console.log(num2); // -26
这里,按位非操作符作用到了数值 25 ,得到的结果是 26 。由此可以看出,按位非的最终效果是对 数值取反并减 1 ,就像执行如下操作的结果一样:
let num1 = 25;
let num2 = -num1 - 1;
console.log(num2); // "-26"
实际上,尽管两者返回的结果一样,但位操作的速度快得多。这是因为位操作是在数值的底层表示 上完成的。
按位与操作符用和号( & )表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐, 然后基于真值表中的规则,对每一位执行相应的与操作。
let result = 25 & 3;
console.log(result); //1
按位或操作符用管道符( | )表示,同样有两个操作数。
let result = 25 | 3;
console.log(result); //27
按位异或用脱字符( ^ )表示,同样有两个操作数。按位异或与按位或的区别是,它只在一位上是 1 的时候返回 1 (两位都是 1 或 0 ,则返回 0 )。
let result = 25 ^ 3;
console.log(result); //26
左移操作符用两个小于号( << )表示,会按照指定的位数将数值的所有位向左移动。比如,如果数 值 2 (二进制 10 )向左移 5 位,就会得到 64 (二进制 1000000 )。
let oldValue = 2; // 等于二进制 10
let newValue = oldValue << 5; // 等于二进制 1000000 ,即十进制 64
左移会保留它所操作数值的符号。比如,如果 2 左移 5 位,将得到负 64 ,而不是正 64 。
有符号右移由两个大于号( >> )表示,会将数值的所有 32 位都向右移,同时保留符号(正或负)。 有符号右移实际上是左移的逆运算。比如,如果将 64 右移 5 位,那就是 2 。
无符号右移用 3 个大于号表示( >>> ),会将数值的所有 32 位都向右移。对于正数,无符号右移与 有符号右移结果相同。仍然以前面有符号右移的例子为例, 64 向右移动 5 位,会变成 2 。
但对负数来说,结果就差太多了。无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。因为负数是其绝对值的二补数,所以右移之后结果变 得非常之大。
当尝试访问对象属性时,如果对象的值为 undefined 或 null ,那么属性访问将产生错误。为了提高程序的健壮性,在访问对象属性时通常需要检查对象是否已经初始化,只有当对象不为 undefined 和 null 时才去访问对象的属性。可选链运算符旨在帮助开发者省去冗长的 undefined 值和 null 值检查代码,增强了代码的表达能力。
可选链运算符由一个问号和一个点号组成,即“ ?.”。可选链运算符有以下三种语法形式:
obj?.prop
在该语法中,如果 obj 的值为 undefined 或 null ,那么表达式的求值结果为 undefined ;否则,表达式的求值结果为 obj.prop 。
obj?.[expr]
在该语法中,如果 obj 的值为 undefined 或 null ,那么表达式的求值结果为 undefined ;否则,表达式的求值结果为 obj[expr]。
fn?.()
在该语法中,如果 fn 的值为 undefined 或 null ,那么表达式的求值结果为 undefined ;否则,表达式的求值结果为 fn()。
如果可选链运算符左侧操作数的求值结果为 undefined 或 null ,那么右侧的操作数不会再被求值,我们将这种行为称作短路求值。
let x = 0;
let a = undefined;
a?.[++x]; // undefined
x; // 0
空值合并运算符是一个新的二元逻辑运算符,它使用两个问号“ ??”作为标识。空值合并运算符的语法如下所示:
a ?? b;
该语法中,当且仅当“ ??”运算符左侧操作数 a 的值为 undefined 或 null 时,返回右侧操作数 b ;否则,返回左侧操作数 a 。
空值合并运算符与可选链运算符一样都具有短路求值的特性。当空值合并运算符左侧操作数的值不为 undefined 和 null 时,右侧操作数不会被求值,而是直接返回左侧操作数。