Skip to content

运算符

经典真题

下面代码中,a 在什么情况下会执行输出语句打印 1 ?

JavaScript
var a = ?;
if(a == 1 && a == 2 && a == 3){
  console.log(1);
}

算术运算符

JavaScript 共提供 10 个算术运算符,用来完成基本的算术运算。

  • 加法运算符:x + y
  • 减法运算符: x - y
  • 乘法运算符: x * y
  • 除法运算符:x / y
  • 指数运算符:x ** y
  • 余数运算符:x % y
  • 自增运算符:++x 或者 x++
  • 自减运算符:--x 或者 x--
  • 数值运算符: +x
  • 负数值运算符:-x

减法、乘法、除法运算法比较单纯,就是执行相应的数学运算。

下面介绍其他几个算术运算符,重点是加法运算符。

加法运算符

  1. 基本规则

    加法运算符( + )是最常见的运算符,用来求两个数值的和。

    JavaScript
    1 + 1; // 2

    JavaScript 允许非数值的相加。

    JavaScript
    true + true; // 2
    1 + true; // 2

    上面代码中,第一行是两个布尔值相加,第二行是数值与布尔值相加。这两种情况,布尔值都会自动转成数值,然后再相加。

    比较特殊的是,如果是两个字符串相加,这时加法运算符会变成连接运算符,返回一个新的字符串,将两个原字符串连接在一起。

    JavaScript
    'a' + 'bc'; // "abc"

    如果一个运算子是字符串,另一个运算子是非字符串,这时非字符串会转成字符串,再连接在一起。

    JavaScript
    1 + 'a'; // "1a"
    false + 'a'; // "falsea"

    加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为 重载(overload)。

    由于加法运算符存在重载,可能执行两种运算,使用的时候必须很小心。

    JavaScript
    '3' + 4 + 5; // "345"
    3 + 4 + '5'; // "75"

    上面代码中,由于从左到右的运算次序,字符串的位置不同会导致不同的结果。

    警告

    除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。

    JavaScript
    1 - '2'; // -1
    1 * '2'; // 2
    1 / '2'; // 0.5

    上面代码中,减法、除法和乘法运算符,都是将字符串自动转为数值,然后再运算。

  2. 对象相加

    如果运算子是对象,必须先转成原始类型的值,然后再相加。

    JavaScript
    var obj = { p: 1 };
    obj + 2; // "[object Object]2"

    上面代码中,对象 obj 转成原始类型的值是 [object Object] ,再加 2 就得到了上面的结果。

    对象转成原始值的规则,本文不再赘述,可以参考 数据类型的转换 一文。

    提示

    这里有一个特例,如果运算子是一个 Date 对象的实例,那么会优先执行 toString 方法。

    JavaScript
    var obj = new Date();
    obj.valueOf = function () {
    return 1;
    };
    obj.toString = function () {
    return 'hello';
    };
    
    obj + 2; // "hello2"

    上面代码中,对象 obj 是一个 Date 对象的实例,并且自定义了 valueOf 方法和 toString 方法,结果 toString 方法优先执行。

余数运算符

余数运算符( % )返回前一个运算子被后一个运算子除,所得的余数。

JavaScript
12 % 5; // 2

警告

较小的数字对较大的数字取余,结果总是等于较小的那个数字。

运算结果的正负号由第一个运算子的正负号决定。

JavaScript
-1 % 2; // -1
1 % -2; // 1

所以,为了得到负数的正确余数值,可以先使用绝对值函数。

JavaScript
// 错误的写法
function isOdd(n) {
  return n % 2 === 1;
}
isOdd(-5); // false
isOdd(-4); // false

// 正确的写法
function isOdd(n) {
  return Math.abs(n % 2) === 1;
}
isOdd(-5); // true
isOdd(-4); // false

余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。

JavaScript
6.5 % 2.1; // 0.19999999999999973

自增和自减运算符

自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上 1 或者减去 1 。它们会修改原始变量。

JavaScript
var x = 1;
++x; // 2
x; // 2

--x; // 1
x; // 1

上面代码的变量 x 自增后,返回 2 ,再进行自减,返回 1 。这两种情况都会使得,原始变量 x 的值发生改变。

运算之后,变量的值发生变化,这种效应叫做 运算的副作用(side effect)。自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值。

自增和自减运算符有一个需要注意的地方,就是放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。

JavaScript
var x = 1;
var y = 1;

x++; // 1
++y; // 2

上面代码中,x 是先返回当前值,然后自增,所以得到 1;y 是先自增,然后返回新的值,所以得到 2。

数值运算符

数值运算符( + )同样使用加号,但它是一元运算符(只需要一个操作数),而加法运算符是二元运算符(需要两个操作数)。

数值运算符的作用在于可以将任何值转为数值(与 Number 函数的作用相同)。

JavaScript
+[] // 0
+{} // NaN
+false // 0
+true // 1
+null // 0

负数值运算符

负数值运算符( - ),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符。

JavaScript
var x = 1;
-x; // -1
-(-x); // 1

上面代码最后一行的圆括号不可少,否则会变成自减运算符。

数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值。

指数运算符

指数运算符( ** )完成指数运算,前一个运算子是底数,后一个运算子是指数。

JavaScript
2 ** 4; // 16

警告

注意,指数运算符是右结合,而不是左结合。即多个指数运算符连用时,先进行最右边的计算。

JavaScript
2 ** 3 ** 2; // 512

上面代码中,由于指数运算符是右结合,所以先计算第二个指数运算符,而不是第一个。

赋值运算符

赋值运算符(Assignment Operators)用于给变量赋值。

最常见的赋值运算符,当然就是等号( = )。

JavaScript
// 将 1 赋值给变量 x
var x = 1;

// 将变量 y 的值赋值给变量 x
var x = y;

赋值运算符还可以与其他运算符结合,形成变体。下面是与算术运算符的结合。

JavaScript
// 等同于 x = x + y
x += y;

// 等同于 x = x - y
x -= y;

// 等同于 x = x * y
x *= y;

// 等同于 x = x / y
x /= y;

// 等同于 x = x % y
x %= y;

// 等同于 x = x  y
x = y;

下面是与位运算符的结合。

JavaScript
// 等同于 x = x >> y
x >>= y;

// 等同于 x = x << y
x <<= y;

// 等同于 x = x >>> y
x >>>= y;

// 等同于 x = x & y
x &= y;

// 等同于 x = x | y
x |= y;

// 等同于 x = x ^ y
x ^= y;

这些复合的赋值运算符,都是先进行指定运算,然后将得到值返回给左边的变量。

比较运算符

比较运算符用于比较两个值的大小,然后返回一个布尔值,表示是否满足指定的条件。

JavaScript
2 > 1 // true

上面代码比较 2 是否大于 1 ,返回 true。

注意,比较运算符可以比较各种类型的值,不仅仅是数值。

JavaScript 一共提供了 8 个比较运算符。

  • > 大于运算符
  • < 小于运算符
  • <= 小于或等于运算符
  • >= 大于或等于运算符
  • == 相等运算符
  • === 严格相等运算符
  • != 不相等运算符
  • !== 严格不相等运算符

这八个比较运算符分成两类:相等比较和非相等比较。

提示

两者的规则是不一样的,对于非相等的比较,算法是先看两个运算子是否都是字符串,如果是的,就按照字典顺序比较(实际上是比较 Unicode 码点);否则,将两个运算子都转成数值,再比较数值的大小。

字符串的比较

字符串按照字典顺序进行比较。

JavaScript
'cat' > 'dog' // false
'cat' > 'catalog' // false

JavaScript 引擎内部首先比较首字符的 Unicode 码点。如果相等,再比较第二个字符的 Unicode 码点,以此类推。

JavaScript
'cat' > 'Cat' // true'

上面代码中,小写的 c 的 Unicode 码点( 99 )大于大写的 C 的 Unicode 码点( 67 ),所以返回 true 。

由于所有字符都有 Unicode 码点,因此汉字也可以比较。

JavaScript
'' > '' // false

非字符串的比较

如果两个运算子之中,至少有一个不是字符串,需要分成以下两种情况。

  1. 原始类型值

    如果两个运算子都是原始类型的值,则是先转成数值再比较。

    JavaScript
    5 > '4'; // true
    // 等同于 5 > Number('4')
    // 即 5 > 4
    
    true > false; // true
    // 等同于 Number(true) > Number(false)
    // 即 1 > 0
    
    2 > true; // true
    // 等同于 2 > Number(true)
    // 即 2 > 1

    上面代码中,字符串和布尔值都会先转成数值,再进行比较。

    这里需要注意与 NaN 的比较。任何值(包括 NaN 本身)与 NaN 使用非相等运算符进行比较,返回的都是 false。

    JavaScript
    1 > NaN; // false
    1 <= NaN; // false
    '1' > NaN; // false
    '1' <= NaN; // false
    NaN > NaN; // false
    NaN <= NaN; // false
  2. 对象

    如果运算子是对象,会转为原始类型的值,再进行比较。

    对象转换成原始类型的值,算法是先调用 valueOf 方法;如果返回的还是对象,再接着调用 toString 方法。

    JavaScript
    var x = [2];
    x > '11'; // true
    // 等同于 [2].valueOf().toString() > '11'
    // 即 '2' > '11'
    
    x.valueOf = function () {
    return '1';
    };
    x > '11'; // false
    // 等同于 [2].valueOf() > '11'
    // 即 '1' > '11'

    两个对象之间的比较也是如此。

    JavaScript
    [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]'

严格相等运算符

JavaScript 提供两种相等运算符:=====

简单说,它们的区别是相等运算符( == )比较两个值是否相等,严格相等运算符( === )比较它们是否为“同一个值”。如果两个值不是同一类型,严格相等运算符( === )直接返回 false ,而相等运算符( == )会将它们转换成同一个类型,再用严格相等运算符进行比较。

  1. 不同类型的值

    如果两个值的类型不同,直接返回 false。

    JavaScript
    1 === '1'; // false
    true === 'true'; // false

    上面代码比较数值的 1 与字符串的 1、布尔值的 true 与字符串 true,因为类型不同,结果都是 false。

  2. 同一类的原始类型值

    同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回 true ,值不同就返回 false。

    JavaScript
    1 === 0x1; // true

    上面代码比较十进制的 1 与十六进制的 1 ,因为类型和值都相同,返回 true。

    警告

    需要注意的是, NaN 与任何值都不相等(包括自身)。另外,正 0 等于负 0。

    JavaScript
    NaN === NaN // false
    +0 === -0; // true
  3. 复合类型值

    两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。

    JavaScript
    {} === {} // false
    [] === [] // false
    (function () {} === function () {}) // false

    上面代码分别比较两个空对象、两个空数组、两个空函数,结果都是不相等。原因是对于复合类型的值,严格相等运算比较的是,它们是否引用同一个内存地址,而运算符两边的空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是 false。

    如果两个变量引用同一个对象,则它们相等。

    JavaScript
    var v1 = {};
    var v2 = v1;
    v1 === v2; // true

    注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。

    JavaScript
    var obj1 = {};
    var obj2 = {};
    
    obj1 > obj2; // false
    obj1 < obj2; // false
    obj1 === obj2; // false

    上面的三个比较,前两个比较的是值,最后一个比较的是地址,所以都返回 false。

  4. undefined 和 null

    undefinednull 与自身严格相等。

    JavaScript
    undefined === undefined; // true
    null === null; // true

    由于变量声明后默认值是 undefined ,因此两个只声明未赋值的变量是相等的。

    JavaScript
    var v1;
    var v2;
    v1 === v2; // true

严格不相等运算符

严格相等运算符有一个对应的“严格不相等运算符”(!==),它的算法就是先求严格相等运算符的结果,然后返回相反值。

JavaScript
1 !== '1'; // true
// 等同于
!(1 === '1');

上面代码中,感叹号!是求出后面表达式的相反值。

相等运算符

相等运算符用来比较相同类型的数据时,与严格相等运算符完全一样。

JavaScript
1 == 1.0;
// 等同于
1 === 1.0;

比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。下面分成几种情况,讨论不同类型的值互相比较的规则。

  1. 原始类型值

    原始类型的值会转换成数值再进行比较。

    JavaScript
    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. 对象与原始类型值比较

    对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较。

    具体来说,先调用对象的 valueOf() 方法,如果得到原始类型的值,就按照上一小节的规则,互相比较;如果得到的还是对象,则再调用 toString() 方法,得到字符串形式,再进行比较。

    下面是数组与原始类型值比较的例子。

    JavaScript
    // 数组与数值的比较
    [1] == 1 // true
    
    // 数组与字符串的比较
    [1] == '1' // true
    [1, 2] == '1,2' // true
    
    // 对象与布尔值的比较
    [1] == true // true
    [2] == true // false

    上面例子中,JavaScript 引擎会先对数组 [1] 调用数组的 valueOf() 方法,由于返回的还是一个数组,所以会接着调用数组的 toString() 方法,得到字符串形式,再按照上一小节的规则进行比较。

    下面是一个更直接的例子。

    JavaScript
    const obj = {
    valueOf: function () {
        console.log('执行 valueOf()');
        return obj;
    },
    toString: function () {
        console.log('执行 toString()');
        return 'foo';
    }
    };
    
    obj == 'foo';
    // 执行 valueOf()
    // 执行 toString()
    // true

    上面例子中, obj 是一个自定义了 valueOf() 和 toString() 方法的对象。这个对象与字符串 foo 进行比较时,会依次调用 valueOf() 和 toString() 方法,最后返回 foo ,所以比较结果是 true 。

  3. undefined 和 null

    undefinednull只有与自身比较,或者互相比较时,才会返回 true ;与其他类型的值比较时,结果都为 false

    JavaScript
    undefined == undefined; // true
    null == null; // true
    undefined == null; // true
    
    false == null; // false
    false == undefined; // false
    
    0 == null; // false
    0 == undefined; // false
  4. 相等运算符的缺点

    相等运算符隐藏的类型转换,会带来一些违反直觉的结果。

    JavaScript
    0 == ''; // true
    0 == '0'; // true
    
    2 == true; // false
    2 == false; // false
    
    false == 'false'; // false
    false == '0'; // true
    
    false == undefined; // false
    false == null; // false
    null == undefined; // true
    
    ' \t\r\n ' == 0; // true

    上面这些表达式都不同于直觉,很容易出错。因此建议不要使用相等运算符(==),最好只使用严格相等运算符(===)。

不相等运算符

相等运算符有一个对应的“不相等运算符”(!=),它的算法就是先求相等运算符的结果,然后返回相反值。

JavaScript
1 != '1'; // false

// 等同于
!(1 == '1');

布尔运算符(逻辑运算符)

布尔运算符用于将表达式转为布尔值,一共包含四个运算符。

  • 取反运算符:!
  • 且(并)运算符:&&
  • 或运算符:||
  • 三元运算符:?:

取反运算符(!)

取反运算符是一个感叹号,用于将布尔值变为相反值,即 true 变成 false,false 变成 true。

JavaScript
!true; // false
!false; // true

对于非布尔值,取反运算符会将其转为布尔值。可以这样记忆,以下六个值取反后为 true,其他值都为 false。

  • undefined
  • null
  • false
  • 0
  • NaN
  • 空字符串(''
JavaScript
!undefined; // true
!null; // true
!0; // true
!NaN; // true
!''; // true

!54; // false
!'hello'; // false
![]; // false
!{}; // false

上面代码中,不管什么类型的值,经过取反运算后,都变成了布尔值。

如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与 Boolean 函数的作用相同。这是一种常用的类型转换的写法。

JavaScript
!!x;
// 等同于
Boolean(x);

上面代码中,不管 x 是什么类型的值,经过两次取反运算后,变成了与 Boolean 函数结果相同的布尔值。所以,两次取反就是将一个值转为布尔值的简便写法。

且运算符(&&)

且运算符(&&)往往用于多个表达式的求值。

它的运算规则是:如果第一个运算子的布尔值为 true,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为 false,则直接返回第一个运算子的值,且不再对第二个运算子求值。

JavaScript
't' && ''; // ""
't' && 'f'; // "f"
't' && 1 + 2; // 3
'' && 'f'; // ""
'' && ''; // ""

var x = 1;
1 - 1 && (x += 1); // 0
x; // 1

上面代码的最后一个例子,由于且运算符的第一个运算子的布尔值为 false,则直接返回它的值 0,而不再对第二个运算子求值,所以变量 x 的值没变。

这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代 if 结构,比如下面是一段 if 结构的代码,就可以用且运算符改写。

JavaScript
if (i) {
  doSomething();
}

// 等价于
i && doSomething();

上面代码的两种写法是等价的,但是后一种不容易看出目的,也不容易除错,建议谨慎使用。

提示

运算符可以多个连用,这时返回第一个布尔值为 false 的表达式的值。如果所有表达式的布尔值都为 true,则返回最后一个表达式的值。

JavaScript
true && 'foo' && '' && 4 && 'foo' && true;
// ''

1 && 2 && 3;
// 3

上面代码中,例一里面,第一个布尔值为 false 的表达式为第三个表达式,所以得到一个空字符串。例二里面,所有表达式的布尔值都是 true ,所以返回最后一个表达式的值 3。

或运算符(||)

或运算符(||)也用于多个表达式的求值。它的运算规则是:如果第一个运算子的布尔值为 true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为 false,则返回第二个运算子的值。

JavaScript
't' || ''; // "t"
't' || 'f'; // "t"
'' || 'f'; // "f"
'' || ''; // ""

短路规则对这个运算符也适用。

JavaScript
var x = 1;
true || (x = 2); // true
x; // 1

上面代码中,或运算符的第一个运算子为 true,所以直接返回 true,不再运行第二个运算子。所以,x 的值没有改变。这种只通过第一个表达式的值,控制是否运行第二个表达式的机制,就称为“短路”(short-cut)。

提示

或运算符可以多个连用,这时返回第一个布尔值为 true 的表达式的值。如果所有表达式都为 false ,则返回最后一个表达式的值。

JavaScript
false || 0 || '' || 4 || 'foo' || true;
// 4

false || 0 || '';
// ''

上面代码中,例一里面,第一个布尔值为 true 的表达式是第四个表达式,所以得到数值 4。例二里面,所有表达式的布尔值都为 false,所以返回最后一个表达式的值。

或运算符常用于为一个变量设置默认值。

JavaScript
function saveText(text) {
  text = text || '';
  // ...
}

// 或者写成
saveText(this.text || '');

上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。

三元条件运算符(?:)

三元条件运算符由问号(?)和冒号(:)组成,分隔三个表达式。它是 JavaScript 语言唯一一个需要三个运算子的运算符。如果第一个表达式的布尔值为true,则返回第二个表达式的值,否则返回第三个表达式的值。

JavaScript
't' ? 'hello' : 'world'; // "hello"
0 ? 'hello' : 'world'; // "world"

上面代码的 t 和 0 的布尔值分别为 true 和 false ,所以分别返回第二个和第三个表达式的值。

通常来说,三元条件表达式与 if...else 语句具有同样表达效果,前者可以表达的,后者也能表达。但是两者具有一个重大差别,if...else 是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用 if..else

JavaScript
console.log(true ? 'T' : 'F');

上面代码中,console.log 方法的参数必须是一个表达式,这时就只能使用三元条件表达式。如果要用 if...else 语句,就必须改变整个代码写法了。

位运算符

按位运算符是将操作数换算成 32 位的二进制整数,然后按每一位来进行运算。例如:

5 的 32 位为:

JavaScript
00000000000000000000000000000101

100 的 32 位为:

JavaScript
00000000000000000000000001100100

15 的 32 位为:

JavaScript
00000000000000000000000000001111
  1. 按位非

    按位非运算符 ~ 会把数字转为 32 位二进制整数,然后反转每一位。所有的 1 变为 0,所有的 0 变为 1

    例如:

    5 的 32 位为:

    JavaScript
    00000000000000000000000000000101

    ~5 的 32 位为:

    JavaScript
    11111111111111111111111111111010

    转换出来就为 -6

    按位非,实质上是对操作数求负,然后减去 1。

  2. 按位与

    按位与运算符 & 会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位与运算。按位与的规则如下表:

    第一个数字第二个数字结果
    111
    100
    010
    000

    具体示例:

    JavaScript
    console.log(12 & 10); // 8

    12 的 32 位二进制表示为:1100 10 的 32 位二进制表示为:1010

    按位与的结果为:1000

  3. 按位或

    按位或运算符 | 会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位或运算。按位或的规则如下表:

    第一个数字第二个数字结果
    111
    101
    011
    000

    具体示例:

    JavaScript
    console.log(12 | 10); // 14

    12 的 32 位二进制表示为:1100 10 的 32 位二进制表示为:1010

    按位或的结果为:1110

  4. 按位异或

    按位或运算符 ^ 会把两个数字转为 32 位二进制整数,并对两个数的每一位执行按位异或运算。运算规则为两位不同返回 1,两位相同返回 0,如下表:

    第一个数字第二个数字结果
    110
    101
    011
    000

    具体示例:

    JavaScript
    console.log(12 ^ 10); // 6

    12 的 32 位二进制表示为:1100 10 的 32 位二进制表示为:1010

    按位异或的结果为:0110

    按位异或如果是非整数值,如果两个操作数中只有一个为真,就返回 1,如果两个操作数都是真,或者都是假,就返回 0,示例如下:

    JavaScript
    console.log(true ^ 'Hello'); // 1
    console.log(false ^ 'Hello'); // 0
    console.log(true ^ true); // 0
    console.log('Hello' ^ 'Hello'); // 0
    console.log(false ^ false); // 0
    console.log(true ^ false); // 1

    注意这里的 Hello 被转换为了 NaN。

  5. 按位移位

    按位移位运算符 <<>> 会将所有位向左或者向右移动指定的数量,实际上就是高效率地将数字乘以或者除以 2 的指定数的次方。

    <<:乘以 2 的指定数次方

    JavaScript
    console.log(2 << 2); // 8

    2 乘以 2 的 2 次方

    00000010 转换为 00001000

    >>:除以 2 的指定数次方

    JavaScript
    console.log(16 >> 1); // 8

    16 除以 2 的 1 次方

    00010000 转换为 00001000

其他运算符

void 运算符

void 运算符的作用是执行一个表达式,然后不返回任何值,或者说返回 undefined。

JavaScript
void 0; // undefined
void (0); // undefined

上面是 void 运算符的两种写法,都正确。建议采用后一种形式,即总是使用圆括号。

因为 void 运算符的优先性很高,如果不使用括号,容易造成错误的结果。

比如,“void 4 + 7” 实际上等同于 “(void 4) + 7”。

下面是 void 运算符的一个例子。

JavaScript
var x = 3;
void (x = 5); //undefined
x; // 5

这个运算符的主要用途是浏览器的书签工具(Bookmarklet),以及在超级链接中插入代码防止网页跳转。

请看下面的代码。

JavaScript
<script>
function f() {
  console.log('Hello World');
}
</script>
<a href="http://example.com" onclick="f(); return false;">点击</a>

上面代码中,点击链接后,会先执行 onclick 的代码,由于 onclick 返回 false,所以浏览器不会跳转到 example.com。

void 运算符可以取代上面的写法。

JavaScript
<a href="JavaScript: void(f())">文字</a>

下面是一个更实际的例子,用户点击链接提交表单,但是不产生页面跳转。

JavaScript
<a href="JavaScript: void(document.form.submit())">提交</a>

逗号运算符

逗号运算符用于对两个表达式求值,并返回后一个表达式的值。

JavaScript
'a', 'b'; // "b"

var x = 0;
var y = (x++, 10);
x; // 1
y; // 10

上面代码中,逗号运算符返回后一个表达式的值。

逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作。

JavaScript
var value = (console.log('Hi!'), true);
// Hi!

value; // true

上面代码中,先执行逗号之前的操作,然后返回逗号后面的值。

运算顺序

优先级

JavaScript 各种运算符的优先级别(Operator Precedence)是不一样的。优先级高的运算符先执行,优先级低的运算符后执行。

JavaScript
4 + 5 * 6; // 34

如果多个运算符混写在一起,常常会导致令人困惑的代码。

JavaScript
var x = 1;
var arr = [];

var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];

上面代码中,变量 y 的值就很难看出来,因为这个表达式涉及 5 个运算符,到底谁的优先级最高,实在不容易记住。

根据语言规格,这五个运算符的优先级从高到低依次为:小于等于( <= )、严格相等( === )、或( || )、三元( ?: )、等号( = )。因此上面的表达式,实际的运算顺序如下。

记住所有运算符的优先级,是非常难的,也是没有必要的。

圆括号的作用

圆括号可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的表达式会第一个运算。

JavaScript
(4 + 5) * 6; // 54

上面代码中,由于使用了圆括号,加法会先于乘法执行。

运算符的优先级别十分繁杂,且都是硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要。

顺便说一下,圆括号不是运算符,而是一种语法结构。它一共有两种用法:一种是把表达式放在圆括号之中,提升运算的优先级;另一种是跟在函数的后面,作用是调用函数。

注意,因为圆括号不是运算符,所以不具有求值作用,只改变运算的优先级。

JavaScript
var x = 1;
(x) = 2;

上面代码的第二行,如果圆括号具有求值作用,那么就会变成 1 = 2,这是会报错了。但是,上面的代码可以运行,这验证了圆括号只改变优先级,不会求值。

这也意味着,如果整个表达式都放在圆括号之中,那么不会有任何效果。

JavaScript
(expression);
// 等同于
expression;

函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数。

JavaScript
function f() {
  return 1;
}

(f); // function f(){return 1;}
f(); // 1

上面代码中,函数放在圆括号之中会返回函数本身,圆括号跟在函数后面则是调用函数。

圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错。

JavaScript
(var a = 1)
// SyntaxError: Unexpected token var

左结合和右结合

对于优先级别相同的运算符,同时出现的时候,就会有计算顺序的问题。

JavaScript
a OP b OP c

上面代码中,OP 表示运算符。它可以有两种解释方式。

JavaScript
// 方式一
(a OP b) OP c

// 方式二
a OP (b OP c)

上面的两种方式,得到的计算结果往往是不一样的。

方式一是将左侧两个运算数结合在一起,采用这种解释方式的运算符,称为“左结合”(left-to-right associativity)运算符;

方式二是将右侧两个运算数结合在一起,这样的运算符称为“右结合”运算符(right-to-left associativity)。

JavaScript 语言的大多数运算符是“左结合”,请看下面加法运算符的例子。

JavaScript
x + y + z;
// 引擎解释如下
(x + y) + z;

上面代码中,x 与 y 结合在一起,它们的预算结果再与 z 进行运算。

少数运算符是“右结合”,其中最主要的是赋值运算符( = )和三元条件运算符( ?: )。

JavaScript
w = x = y = z;
q = a ? b : c ? d : e ? f : g;

上面代码的解释方式如下。

JavaScript
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));

上面的两行代码,都是右侧的运算数结合在一起。

另外,指数运算符(**)也是右结合。

JavaScript
2 ** (3 ** 2);
// 相当于 2  (3  2)
// 512

真题解答

  • 下面代码中,a 在什么情况下会执行输出语句打印 1 ?
JavaScript
var a = ?;
if(a == 1 && a == 2 && a == 3){
  console.log(1);
}

参考答案:

方法一:利用 toString( ) 方法

JavaScript
var a = {
  i: 1,
  toString() {
    return a.i++;
  }
};
if (a == 1 && a == 2 && a == 3) {
  console.log('1');
}

方法二:利用 valueOf( ) 方法

JavaScript
var a = {
  i: 1,
  valueOf() {
    return a.i++;
  }
};
if (a == 1 && a == 2 && a == 3) {
  console.log('1');
}