跳到主要内容

概述

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 + 5NaN
Infinity + InfinityInfinity
Infinity + 5Infinity
-Infinity + -Infinity-Infinity
-Infinity + InfinityNaN
1 + 12
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 - 5NaN
Infinity - 5Infinity
Infinity - InfinityNaN
-Infinity - -InfinityNaN
-Infinity -Infinity-Infinity
2 - "1"1
2 - "a"NaN

*

valuen
NaN * nNaN
Infinity * nInfinity
Infinity *(-n)-Infinity
Infinity * 0NaN
Infinity *InfinityInfinity

/

valuen
NaN / nNaN
Infinity / nInfinity
-Infinity``
Infinity / InfinityNaN
n / 0Infinity
-Infinity-Infinity
n / -0-Infinity
InfinityInfinity

%

valuen
Infinity % nNaN
Infinity % InfinityNaN
n % Infinityn
0 % n0
0 % Infinity0
n % 0NaN
Infinity % 0NaN

逻辑运算

  • 逻辑与 &&
  • 逻辑或 ||
  • 逻辑非 !

关系运算符

  • 大小比较

  • <

  • >

  • <=

  • >=

  • 包含检测

  • 等值检测

in

归属检查, 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 运算符的删除不是清空值,而是释放空间
  • JavaScript 中有内置专门收垃圾的程序,所以不需要手动释放对象所占的空间
  • 灵活使用 deletein ,可方便的操作对象的检查、删除、更新

().

  • 中括号 不仅能储存数组元素的值,还可以存取对象的属性值
  • 点号 可存取对象的属性值,比中括号要灵活、方便
  • 中括号可以通过变量和字符串表达式来传递特定值,而 点号 不可以
  • 中括号可以对第 2 个元素的值执行运算

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 运算符

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 个步骤计算 得到:

  • 确定绝对值的二进制表示
  • 找到数值的一补数(或反码),换句话说,就是每个 0 都变成 1 ,每个 1 都变成 0
  • 结果加 1

按位非

按位非操作符用波浪符( ~ )表示,它的作用是返回数值的一补数。按位非是 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 时,右侧操作数不会被求值,而是直接返回左侧操作数。