当前位置: 首页 > 文档资料 > JavaScript 闯关记 >

2.5 表达式和运算符

优质
小牛编辑
126浏览
2023-12-01

表达式

表达式是由数字、运算符、数字分组符号(如括号)、自由变量和约束变量等以能求得数值的有意义排列方法所得的组合。JavaScript 表达式主要有以下几种形式:

  • 原始表达式:常量、变量、保留字。
  • 对象、数组初始化表达式:var obj={a:1,b:2};var arr=[1,2,3];
  • 函数定义表达式:var fn=function(){}
  • 属性访问表达式:Math.abs
  • 调用表达式:alert('hello');
  • 对象创建表达式:new object();

运算符

JavaScript 中的运算符用于算术表达式、比较表达式、逻辑表达式、赋值表达式等。需要注意的是,大多数运算符都是由标点符号表示的,比如 +=。而另外一些运算符则是由关键字表示的,比如 typeofinstanceof,关键字运算符和标点符号都是正规的运算符。

下表列出了 JavaScript 中所有的运算符,并按照运算符的优先级排序的,前面的运算符优先级要高于后面的运算符优先级,被空行分隔开来的运算符具有不同的优先级。标题为 A 的列表示运算符的结合性(Associativity),L 表示从左至右、R 表示从右至左,标题为 N 的列表示操作数的个数(Number)。

运算符操作AN
++前/后增量R1
--前/后增量R1
-求反R1
+转换为数字R1
~按位求反R1
!逻辑非R1
delete删除属性R1
typeof检测类型R1
void返回undefinedR1
* / %乘,除,求模L2
+ -加,减L2
+字符串连接L2
<<左移位L2
>>有符号右移L2
>>>无符号右移L2
< <= > >=比较数字顺序L2
< <= > >=比较字母顺序L2
instanceof测试对象类L2
in测试属性是否存在L2
==判断相等L2
!=判断不等L2
===判断恒等L2
!==判断恒不等L2
&按位与L2
^按位异或L2
按位或L2
&&逻辑与L2
┃┃逻辑或L2
?:条件运算符R3
=赋值R2
*= /= %=
+= -= &=
<<= >>=
^= ┃= >>>=
运算且赋值R2
,忽略第一个操作数,
返回第二个操作数
L2

因为 | 是制表符,会导致格式混乱,所以表格中的 | 均以 代替。

一元运算符

delete 运算符

delete 运算符用来删除对象属性或者数组元素,如果删除成功或所删除的目标不存在,delete 将返回 true。然而,并不是所有的属性都可删除,一些内置核心和客户端属性是不能删除的,通过 var 语句声明的变量不能删除,通过 function 语句定义的函数也是不能删除的。例如:

var o = { x: 1, y: 2};          // 定义一个对象
console.log(delete o.x);        // true,删除一个属性
console.log(delete o.x);        // true,什么都没做,x 在已上一步被删除
console.log("x" in o);          // false,这个属性在对象中不再存在
console.log(delete o.toString); // true,什么也没做,toString是继承来的
console.log(delete 1);          // true,无意义

var a = [1,2,3];                // 定义一个数组
console.log(delete a[2]);       // true,删除最后一个数组元素
console.log(2 in a);            // false,元素2在数组中不再存在
console.log(a.length);          // 3,数组长度并不会因 delete 而改变
console.log(a[2]);              // undefined,元素2所在的位置被空了出来
console.log(delete a);          // false,通过 var 语句声明的变量不能删除

function f(args){}              // 定义一个函数
console.log(delete f);          // false,通过 function 语句声明的函数不能删除

void 运算符

void 运算符可以应用于任何表类型的表达式,表达式会被执行,但计算结果会被忽略并返回 undefined。例如:

void 0;
void "you are useless?";
void false;
void [];
void /(useless)/ig;
void function(){ console.log("you are so useless?"); }
// always return undefined

扩展阅读「谈谈 JavaScript 中的 void 运算符」
https://segmentfault.com/a/1190000000474941

typeof 运算符

请参见「变量和数据类型」-「数据类型」-「typeof 运算符」

++ -- 运算符

++ -- 递增递减运算符借鉴自 C 语言,它们分前置型和后置型,作用是改变一个变量的值。例如:

var a = 5;
console.log(a++);   // 5
console.log(++a);   // 7
console.log(a--);   // 7
console.log(--a);   // 5

+ - 运算符

+ - 作为一元运算符时,应用于数值,表示数值的正负。应用于非数值,先按 Number() 转型函数对这个值执行转换,再表示该值的正负。

~ ! 运算符

~ 按位非运算符,请参见下面「位运算符」
! 逻辑非运算符,请参见下面「逻辑运算符」

乘性运算符

JavaScript 定义了3个乘性运算符:乘法、除法和求模。这些运算符与 C 语言的相应运算符用途类似,只不过在操作数为非数值的情况下会执行自动的类型转换。如果参与乘法计算的某个操作数不是数值,后台会先使用 Number() 转型函数将其转换为数值。也就是说,空字符串将被当作 0,布尔值 true 将被当作 1

* 乘法运算符

用于计算两个数值的乘积,在处理特殊值的情况下,乘法运算符遵循下列特殊的规则:

  • 如果操作数都是数值,执行常规的乘法计算,即两个正数或两个负数相乘的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果乘积超过了 JavaScript 数值的表示范围,则返回 Infinity-Infinity
  • 如果有一个操作数是 NaN,则结果是 NaN
  • 如果是 Infinity0 相乘,则结果是 NaN
  • 如果是 Infinity 与非 0 数值相乘,则结果是 Infinity-Infinity,取决于有符号操作数的符号;
  • 如果是 InfinityInfinity 相乘,则结果是 Infinity; 如果有一个操作数不是数值,则在后台调用 Number()将其转换为数值,然后再应用上面的规则。

/ 除法运算符

用于计算两个数值的商,与乘法运算符类似,除法运算符对特殊的值也有特殊的处理规则。这些规则如下:

  • 如果操作数都是数值,执行常规的除法计算,即两个正数或两个负数相除的结果还是正数,而如果只有一个操作数有符号,那么结果就是负数。如果商超过了 JavaScript 数值的表示范围,则返回 Infinity-Infinity
  • 如果有一个操作数是 NaN,则结果是 NaN
  • 如果是 InfinityInfinity 除,则结果是 NaN
  • 如果是零被零除,则结果是 NaN
  • 如果是非零的有限数被零除,则结果是 Infinity-Infinity,取决于有符号操作数的符号;
  • 如果是 Infinity 被任何非零数值除,则结果是 Infinity-Infinity,取决于有符号操作数的符号;
  • 如果有一个操作数不是数值,则在后台调用 Number() 将其转换为数值,然后再应用上面的规则。

% 求模运算符

用于计算两个数值的余数,与另外两个乘性运算符类似,求模运算符会遵循下列特殊规则来处理特殊的值:

  • 如果操作数都是数值,执行常规的除法计算,返回除得的余数;
  • 如果被除数是无穷大值而除数是有限大的数值,则结果是 NaN
  • 如果被除数是有限大的数值而除数是零,则结果是 NaN
  • 如果是 InfinityInfinity 除,则结果是 NaN
  • 如果被除数是有限大的数值而除数是无穷大的数值,则结果是被除数;
  • 如果被除数是零,则结果是零;
  • 如果有一个操作数不是数值,则在后台调用 Number() 将其转换为数值,然后再应用上面的规则。

加性运算符

加法和减法这两个加性运算符应该说是编程语言中最简单的算术运算符了。但是在 JavaScript 中,这两个运算符却都有一系列的特殊行为。与乘性运算符类似,加性运算符也会在后台转换不同的数据类型。然而,对于加性运算符而言,相应的转换规则还稍微有点复杂。

+ 加法运算符

如果两个运算符都是数值,执行常规的加法计算,然后根据下列规则返回结果:

  • 如果有一个操作数是 NaN,则结果是 NaN
  • 如果是 InfinityInfinity,则结果是 Infinity
  • 如果是 -Infinity-Infinity,则结果是 -Infinity
  • 如果是 Infinity 加- Infinity,则结果是 NaN
  • 如果是 +0+0,则结果是 +0
  • 如果是 -0-0,则结果是 -0
  • 如果是 +0-0,则结果是 +0;

如果有一个操作数不是数值,那么就要应用如下规则:

  • 如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来;
  • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来。
  • 如果有一个操作数是对象、数值或布尔值,则调用它们的 toString() 方法取得相应的字符串值,然后再应用前面关于字符串的规则。对于 undefinednull,则分别调用 String() 函数并取得字符串 "undefined""null"
  • 如果是 nullnull,则结果是 0;
  • 如果是 undefinedundefined,则结果是 NaN;

下面来举几个例子:

var result1 = 5 + 5;            // 两个数值相加
console.log(result1);           // 10

var result2 = 5 + "5";          // 一个数值和一个字符串相加
console.log(result2);           // "55"

var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + num1 + num2;
console.log(message);   // "The sum of 5 and 10 is 510",如何修改?

- 减法运算符

如果两个运算符都是数值,执行常规的减法计算,然后根据下列规则返回结果:

  • 如果有一个操作数是 NaN,则结果是 NaN
  • 如果是 InfinityInfinity,则结果是 NaN
  • 如果是 -Infinity-Infinity,则结果是 NaN
  • 如果是 Infinity-Infinity,则结果是 Infinity
  • 如果是 -InfinityInfinity,则结果是 -Infinity
  • 如果是 +0+0,则结果是 +0
  • 如果是 +0-0,则结果是 -0
  • 如果是 -0-0,则结果是 +0

如果有一个操作数不是数值,那么就要应用如下规则:

  • 如果有一个操作数是字符串、布尔值、nullundefined,则先在后台调用 Number() 函数将其转换为数值,然后再根据前面的规则执行减法计算。如果转换的结果是 NaN,则减法的结果就是 NaN
  • 如果有一个操作数是对象,则调用对象的 valueOf() 方法以取得表示该对象的数值。如果得到的值是 NaN,则减法的结果就是 NaN。如果对象没有 valueOf() 方法,则调用其 toString()方法并将得到的字符串转换为数值。
  • 如果是 nullnull,则结果是 0;
  • 如果是 undefinedundefined,则结果是 NaN;

下面来举几个例子:

var result1 = 5 - true;         // 4,因为true被转换成了1
var result2 = NaN - 1;          // NaN
var result3 = 5 - 3;            // 2
var result4 = 5 - "";           // 5,因为"" 被转换成了0
var result5 = 5 - "2";          // 3,因为"2"被转换成了2
var result6 = 5 - null;         // 5,因为null被转换成了0

等值运算符

确定两个变量是否相等是编程中的一个非常重要的操作。在比较简单数据类型之间的相等性时,问题还比较简单。但在涉及到对象之间的比较时,问题就变得复杂了。最早的 JavaScript 中的相等和不等运算符会在执行比较之前,先将对象转换成相似的类型。后来,有人提出了这种转换到底是否合理的质疑。最后,JavaScript 的解决方案就是提供两组运算符:相等和不相等(先转换再比较),恒等和不恒等(仅比较而不转换)。

== != 运算符

== != 这两个运算符都会先转换操作数(通常称为强制转型),然后再比较它们的相等性。在转换不同的数据类型时,相等和不相等运算符遵循下列基本规则:

  • 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值(false 转换为 0,而 true 转换为 1);
  • 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法,用得到的基本类型值按照前面的规则进行比较;
  • nullundefined 是相等的。 要比较相等性之前,不能将 nullundefined 转换成其他任何值。
  • 如果有一个操作数是 NaN,则相等运算符返回 false,而不相等运算符返回 true。重要提示:即使两个操作数都是 NaN,相等运算符也返回 false;因为按照规则,NaN 不等于 NaN
  • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等运算符返回 true;否则,返回 false

列出了一些特殊情况及比较结果:

null == undefined   // true
"NaN" == NaN        // false
5 == NaN            // false
NaN == NaN          // false
NaN != NaN          // true
false == 0          // true
true == 1           // true
true == 2           // false
undefined == 0      // false
null == 0           // false
"5" == 5            // true

=== !== 运算符

除了在比较之前不转换操作数之外,恒等和不恒等运算符与相等和不相等运算符没有什么区别。它只在两个操作数未经转换就相等的情况下返回 true,如下面的例子所示:

var result1 = ("55" == 55);     // true,因为转换后相等
var result2 = ("55" === 55);    // false,因为不同的数据类型不相等
var result3 = (null == undefined)   // true,因为它们是类似的值
var result4 = (null === undefined)  // false,因为它们是不同类型的值

关系运算符

< > <= >= 运算符

< 小于、> 大于、<= 小于等于、 >= 大于等于 这几个关系运算符用于对两个值进行比较返回一个布尔值。与 JavaScript 中的其他运算符一样,当关系运算符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作。以下就是相应的规则。

  • 如果两个操作数都是数值,则执行数值比较。
  • 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值(可以通过字符串的 charCodeAt() 函数获取字符编码值)。
  • 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
  • 如果一个操作数是对象,则调用这个对象的 valueOf() 方法,用得到的结果按照前面的规则执行比较。如果对象没有 valueOf()方法,则调用 toString()方法,并用得到的结果根据前面的规则执行比较。
  • 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。

请思考下面几个例子的结果是如何得出的:

var result1 = "Brick" < "alphabet";     // true
var result2 = "brick" < "alphabet";     // false
var result3 = "23" < "3";   // true
var result4 = "23" < 3;     // false
var result5 = "a" < 3;      // false
var result6 = NaN < 3;      // false
var result7 = NaN >= 3;     // false

in 运算符

in 运算符希望它的左操作数是一个字符串或可以转换为字符串,希望它的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数值的属性名,那么表达式返回 true,例如:

var point = { x:1, y:1 };       // 定义一个对象
"x" in point                    // true,对象有一个名为"x"的属性
"z" in point                    // false,对象中不存在名为"z"的属性
"toString" in point             // true,对象继承了toString()方法

var data = [7,8,9];             // 拥有三个元素的数组
"0" in data                     // true,数组包含元素"0"
1 in data                       // true,数字转换为字符串
3 in data                       // false,没有索引为3的元素

instanceof 运算符

instanceof 运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回 true;否则返回 false。后面会讲 JavaScript 中对象的类是通过初始化它们的构造函数来定义的。这样的话,instanceof 的右操作数应当是一个函数。比如:

var d = new Date();     // 通过 Date() 构造函数来创建一个新对象
d instanceof Date;      // true,d 是由 Date() 创建的
d instanceof Object;    // true,所有的对象都是 Object 的实例
d instanceof Number;    // false,d 不是一个 Number 对象

var a = [1, 2, 3];      // 通过数组字面量的写法创建一个数组
a instanceof Array;     // true,a 是一个数组
a instanceof Object;    // true,所有的数组都是对象
a instanceof RegExp;    // false,数组不是正则表达式

需要注意的是,所有的对象都是 Object 的实例。当通过 instanceof 判断一个对象是否是一个类的实例的时候,这个判断也会包含对「父类」的检测。如果 instanceof 的左操作数不是对象的话,instanceof 返回 false。如果右操作数不是函数,则抛出一个类型错误异常。

逻辑运算符

逻辑运算符是对操作数进行布尔算术运算,经常和关系运算符一起配合使用,逻辑运算符将多个关系表达式组合起来组成一个更复杂的表达式。

&& 逻辑与

逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。在有一个操作数不是布尔值的情况下,逻辑与操作不一定返回布尔值;此时,它遵循下列规则:

  • 如果第一个操作数是对象,则返回第二个操作数;
  • 如果第二个操作数是对象,则只有在第一个操作数的求值结果为 true 的情况下才会返回该对象;
  • 如果两个操作数都是对象,则返回第二个操作数;
  • 如果有一个操作数是 null,则返回 null
  • 如果有一个操作数是 NaN,则返回 NaN
  • 如果有一个操作数是 undefined,则返回 undefined

逻辑与操作属于短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。对于逻辑与操作而言,如果第一个操作数是 false,无论第二个操作数是什么值,结果都不再可能是 true 了。

|| 逻辑或

与逻辑与操作相似,如果有一个操作数不是布尔值,逻辑或也不一定返回布尔值;此时,它遵循下列规则:

  • 如果第一个操作数是对象,则返回第一个操作数;
  • 如果第一个操作数的求值结果为 false,则返回第二个操作数;
  • 如果两个操作数都是对象,则返回第一个操作数;
  • 如果两个操作数都是 null,则返回 null
  • 如果两个操作数都是 NaN,则返回 NaN
  • 如果两个操作数都是 undefined,则返回 undefined

与逻辑与运算符相似,逻辑或运算符也是短路运算符。也就是说,如果第一个操作数的求值结果为 true,就不会对第二个操作数求值了。

! 逻辑非

逻辑非操作可以应用于任何类型的操作数,无论这个值是什么数据类型,这个运算符都会返回一个布尔值。逻辑非运算符首先会将它的操作数转换为一个布尔值,然后再对其求反。逻辑非运算符遵循下列规则:

  • 如果操作数是一个对象,返回 false
  • 如果操作数是一个空字符串,返回 true
  • 如果操作数是一个非空字符串,返回 false
  • 如果操作数是数值 0,返回 true
  • 如果操作数是任意非 0 数值(包括 Infinity),返回 false
  • 如果操作数是 null,返回 true
  • 如果操作数是 NaN,返回 true
  • 如果操作数是 undefined,返回 true

下面几个例子展示了应用上述规则的结果:

console.log(!false);          // true
console.log(!"blue");         // false
console.log(!0);              // true
console.log(!NaN);            // true
console.log(!"");             // true
console.log(!12345);          // false

逻辑非运算符也可以用于将一个值转换为与其对应的布尔值。而同时使用两个逻辑非运算符,实际上就会模拟 Boolean() 转型函数的行为。其中,第一个逻辑非操作会基于无论什么操作数返回一个布尔值,而第二个逻辑非操作则对该布尔值求反,于是就得到了这个值真正对应的布尔值。当然,最终结果与对这个值使用 Boolean() 函数相同,例如:

console.log(!!"blue");        //true
console.log(!!0);             //false
console.log(!!NaN);           //false
console.log(!!"");            //false
console.log(!!12345);         //true

位运算符

在 JavaScript 中,当对数值应用位运算符时,后台会发生如下转换过程:64位的数值被转换成32位数值,然后执行位操作,最后再将32位的结果转换回64位数值。这个转换过程导致了一个严重的副效应,即在对特殊的 NaNInfinity 值应用位操作时,这两个值都会被当成 0 来处理。如果对非数值应用位运算符,会先使用 Number() 函数将该值转换为一个数值,然后再应用位操作,得到的结果将是一个数值。

~ 按位非

简单的理解,对任一数值 x 进行按位非操作的结果为 -(x+1)。例如:

console.log(~null);         // -1
console.log(~undefined);    // -1
console.log(~0);            // -1
console.log(~{});           // -1
console.log(~[]);           // -1
console.log(~(1/0));        // -1
console.log(~false);        // -1
console.log(~true);         // -2
console.log(~1.2543);       // -2
console.log(~4.9);          // -5
console.log(~(-2.999));     // 1

& 按位与

按位与操作就是将两个数值的每一位对齐,两个数值的对应位都是 1 时才返回 1,任何一位是 0,结果都是 0。如下表所示:

第一个数值的位第二个数值的位结果
111
100
010
000

| 按位或

按位或操作就是将两个数值的每一位对齐,两个数值只要有一个位是 1 就返回 1,只在两个位都是 0 的情况下才返回 0。如下表所示:

第一个数值的位第二个数值的位结果
111
101
011
000

^ 按位异或

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

第一个数值的位第二个数值的位结果
110
101
011
000

<< 左移

这个运算符会将数值的所有位向左移动指定的位数。例如:

var oldValue = 2;                       // 等于二进制的 10
var newValue = oldValue << 5;           // 等于二进制的 1000000,十进制的 64

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

>> 有符号的右移

这个运算符会将数值向右移动,但保留符号位(即正负号标记)。

var oldValue = 64;                      // 等于二进制的 1000000
var newValue = oldValue >> 5;           // 等于二进制的 10 ,即十进制的 2

>>> 无符号的右移

这个运算符会将数值的所有32位都向右移动。对正数来说,无符号右移的结果与有符号右移相同。

var oldValue = 64;                      // 等于二进制的 1000000
var newValue = oldValue >>> 5;          // 等于二进制的 10 ,即十进制的 2

无符号右移运算符会把负数的二进制码当成正数的二进制码。而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后的结果非常之大。

var oldValue = -64;                     // 等于二进制的 11111111111111111111111111000000
var newValue = oldValue >>> 5;          // 等于十进制的 134217726

赋值运算符

简单的赋值运算符由等于号 = 表示,其作用就是把右侧的值赋给左侧的变量,如下面的例子所示:

var num = 10;

如果在等于号 = 前面再添加乘性运算符、加性运算符或位运算符,就可以完成复合赋值操作。这种复合赋值操作相当于是对下面常规表达式的简写形式:

var num = 10;
num += 10;      // 等同于 num = num + 10;

每个主要算术运算符(以及个别的其他运算符)都有对应的复合赋值运算符。这些运算符如下所示:

  • 乘/赋值 *=
  • 除/赋值 /=
  • 模/赋值 %=
  • 加/赋值 +=
  • 减/赋值 -=
  • 左移/赋值 <<=
  • 有符号右移/赋值 >>=
  • 无符号右移/赋值 >>>=

设计这些运算符的主要目的就是简化赋值操作,使用它们不会带来任何性能的提升。

条件运算符

? : 条件运算符应该算是 JavaScript 中最灵活的一种运算符了,而且它遵循与 Java 中的条件运算符相同的语法形式,如下面的例子所示:

variable = boolean_expression ? true_value : false_value;

逗号运算符

逗号运算符多用于声明多个变量;但除此之外,逗号运算符还可以用于赋值。在用于赋值时,逗号运算符总会返回表达式中的最后一项,如下面的例子所示:

var num = (5, 1, 4, 8, 0); // num 的值为 0

由于 0 是表达式中的最后一项,因此 num 的值就是 0。虽然逗号的这种使用方式并不常见,但这个例子可以帮我们理解逗号的这种行为。

关卡

// 挑战一
var x=1;
if(!!function f(){}){
    x+=typeof f;
}
console.log(x);     // ???
// 挑战二
(function f(f){
    console.log(typeof f());    // ???
})(function(){return 1;});
// 挑战三
console.log(typeof 2*3);    // ???
console.log(typeof 2+3);    // ???
// 挑战四
var a=0,b=0;
console.log(a+++b);     // ???
console.log(a);         // ???
console.log(b);         // ???
// 挑战五
var a,b,c;
a=b==c;
console.log(a);     // ???
// 挑战六
console.log(1 && 3);            // ???
console.log(1 && "foo" || 0);   // ???
console.log(1 || "foo" && 0);   // ???
// 挑战七
var a=1;
var b=(a=(2,4,6))+a++
console.log(b);     // ???
// 挑战八
if (!("a" in window)) {
    var a = 1;
}
console.log(a);     // ???
// 挑战九
var val = 'smtg'; 
console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing');  // ???
// 挑战九
console.log(1 + - + + + - + 1);