算术运算符

加法运算符

  1. 基本规则
    1 + 1 // 2
    
    true + true // 2
    1 + true // 2
    
    'a' + 'bc' // "abc"
    1 + 'a' // "1a"
    false + 'a' // "falsea"
    
    '3' + 4 + 5 // "345"
    3 + 4 + '5' // "75"
    
  2. 对象相加,如果运算子是对象,必须先转成原始类型的值,然后再相加(先调用 valueOf,再调用 toString);
    var obj = { p: 1 };
    obj + 2 // "[object Object]2"
    
  3. 对象相加,如果运算子是 Date 对象,并且自定义了 valueOf 方法和 toString 方法,结果 toString 方法优先执行;
    var obj = new Date();
    obj.valueOf = function () { return 1 };
    obj.toString = function () { return 'hello' };
    
    obj + 2 // "hello2"
    

余数运算符

  1. 余数运算符返回前一个运算子被后一个运算子除,所得的余数;

    12 % 5 // 2
    
  2. 需要注意的是,运算结果的正负号由第一个运算子的正负号决定;

    -1 % 2 // -1
    1 % -2 // 1
    
  3. 所以,为了得到负数的正确余数值,可以先使用绝对值函数;

    function isOdd(n) {
      return Math.abs(n % 2) === 1;
    }
    isOdd(-5) // true
    isOdd(-4) // false
    
  4. 余数运算符还可以用于浮点数的运算,但是,由于浮点数不是精确的值,无法得到完全准确的结果;

    6.5 % 2.1
    // 0.19999999999999973
    

自增和自减运算符

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

    var x = 1;
    ++x // 2
    x // 2
    
    --x // 1
    x // 1
    
  2. 运算之后,变量的值发生变化,这种效应叫做运算的副作用(side effect),自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值;

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

数值运算符,负数值运算符

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

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

    +true // 1
    +[] // 0
    +{} // NaN
    
  3. 负数值运算符,也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符;

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

指数运算符

  1. 指数运算符完成指数运算,前一个运算子是底数,后一个运算子是指数;

    2 ** 4 // 16
    
  2. 注意,指数运算符是右结合,而不是左结合(即多个指数运算符连用时,先进行最右边的计算);

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

赋值运算符

  1. 赋值运算符用于给变量赋值,最常见的赋值运算符;

    // 将 1 赋值给变量 x
    var x = 1;
    
    // 将变量 y 的值赋值给变量 x
    var x = y;
    
  2. 赋值运算符还可以与其他运算符结合,形成变体;

    // 等同于 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
    
  3. 下面是与位运算符的结合;

    // 等同于 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
    

比较运算符

非相等运算符:字符串的比较

  1. 字符串按照字典顺序进行比较;

    'cat' > 'dog' // false
    'cat' > 'catalog' // false
    
  2. JavaScript 引擎内部首先比较首字符的 Unicode 码点,如果相等,再比较第二个字符的 Unicode 码点,以此类推;

    'cat' > 'Cat' // true'
    
  3. 由于所有字符都有 Unicode 码点,因此汉字也可以比较;

    '大' > '小' // false
    

非相等运算符:非字符串的比较

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

    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
    
  2. 任何值(包括 NaN 本身)与 NaN 使用非相等运算符进行比较,返回的都是 false

    1 > NaN // false
    1 <= NaN // false
    '1' > NaN // false
    '1' <= NaN // false
    NaN > NaN // false
    NaN <= NaN // false
    
  3. 如果运算子是对象,会转为原始类型的值,再进行比较,算法是先调用 valueOf 方法;如果返回的还是对象,再接着调用 toString 方法;

    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'
    
  4. 两个对象之间的比较也是如此;

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

严格相等运算符

  1. 严格相等运算符比较它们是否为 同一个值

  2. 不同类型的值,直接返回 false

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

    1 === 0x1 // true
    
  4. 需要注意的是, NaN 与任何值都不相等(包括自身),另外,正 0 等于负 0

    NaN === NaN  // false
    +0 === -0 // true
    
  5. 两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址;

    {} === {} // false
    [] === [] // false
    (function () {} === function () {}) // false
    
  6. undefinednull 与自身严格相等;

    undefined === undefined // true
    null === null // true
    
    var v1;
    var v2;
    v1 === v2 // true
    

相等运算符

  1. 相等运算符比较两个值是否 相等

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

    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
    // 因为字符串转为数字时,省略前置和后置的空格
    
  3. 对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较,算法是先调用 valueOf 方法;如果返回的还是对象,再接着调用 toString 方法;

    // 数组与数值的比较
    [1] == 1 // true
    
    // 数组与字符串的比较
    [1] == '1' // true
    [1, 2] == '1,2' // true
    
    // 对象与布尔值的比较
    [1] == true // true
    [2] == true // false
    
    // 两个对象比较引用地址一定是 false
    {} == {} // false
    
  4. undefinednull 只有与自身比较,或者互相比较时,才会返回 true;与其他类型的值比较时,结果都为 false

    undefined == undefined // true
    null == null // true
    undefined == null // true
    
    false == null // false
    false == undefined // false
    
    0 == null // false
    0 == undefined // false
    
  5. 相等运算符隐藏的类型转换,会带来一些违反直觉的结果;

    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
    

位运算符

按位非

  1. 按位非运算符会把数字转为 32 位二进制整数,然后反转每一位,按位非,实质上是对操作数求负,然后减去 1

  2. 例如:

    // 5 的 32 位为
    00000000000000000000000000000101
    
    // ~5 的 32 位为:-6
    11111111111111111111111111111010
    

按位与

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

    第一个数字 第二个数字 结果
    1 1 1
    1 0 0
    0 1 0
    0 0 0
  2. 具体示例:

    // 12 的 32 位二进制表示为:1100
    // 10 的 32 位二进制表示为:1010
    console.log(12 & 10); // 8
    

按位或

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

    第一个数字 第二个数字 结果
    1 1 1
    1 0 1
    0 1 1
    0 0 0
  2. 具体示例:

    // 12 的 32 位二进制表示为:1100
    // 10 的 32 位二进制表示为:1010
    // 按位或的结果为:1110
    console.log(12 | 10); // 14
    

按位异或

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

    第一个数字 第二个数字 结果
    1 1 0
    1 0 1
    0 1 1
    0 0 0
  2. 具体示例:

    // 12 的 32 位二进制表示为:1100
    // 10 的 32 位二进制表示为:1010
    // 按位异或的结果为:0110
    
    console.log(12 ^ 10); // 6
    
  3. 按位异或如果是非整数值,如果两个操作数中只有一个为真,就返回 1,如果两个操作数都是真,或者都是假,就返回 0,示例如下:

    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
    

按位移位

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

  2. 具体示例:

    // `<<`:乘以 2 的指定数次方
    console.log(2<<2); // 8
    
    // `>>`:除以 2 的指定数次方
    console.log(16>>1); // 8
    

其他运算符

void 运算符

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

    var x = 3;
    void (x = 5) //undefined
    x // 5
    
  2. 下面是 void 运算符的两种写法都正确,建议采用后一种形式,即总是使用圆括号,因为 void 运算符的优先性很高,如果不使用括号,容易造成错误的结果,比如 “void 4 + 7” 实际上等同于 “(void 4) + 7”

    void 0 // undefined
    void(0) // undefined
    

逗号运算符

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

    'a', 'b' // "b"
    
    var x = 0;
    var y = (x++, 10);
    x // 1
    y // 10
    
  2. 逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作;

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

运算顺序

优先级

  1. JavaScript 各种运算符的优先级别是不一样的,优先级高的运算符先执行,优先级低的运算符后执行;

    4 + 5 * 6 // 34
    
  2. 如果多个运算符混写在一起,常常会导致令人困惑的代码,记住所有运算符的优先级,是非常难的,也是没有必要的;

    var x = 1;
    var arr = [];
    
    // 这五个运算符的优先级从高到低依次为:小于等于( <= )、严格相等( === )、或( || )、三元( ?: )、等号( = )
    var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];
    // 等价
    var y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];
    

圆括号的作用

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

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

面试题

a 等于什么值会让「a == 1 && a == 2 && a == 3 」条件成立;

JS
JS
JS
JS
JS
// 方式一:数值和对象比较,先执行 valueOf,如果还是对象则再执行 toString
var a = {
    i: 1,
    toString() {
        return a.i++;
    }
};
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式二:数值和对象比较,先执行 valueOf,如果还是对象则再执行 toString
var a = {
    i: 1,
    valueOf() {
        return a.i++;
    }
};
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式三:
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式四:
var a = [1, 2, 3];
a.valueOf = a.shift;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方式五:
var i = 0;
Object.defineProperty(window, 'a', {
    get() {
        // 获取 window.a 的时候触发
        return ++i;
    },
    set() {
        // 给 window.a 设置属性值的时候触发
    }
});
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}

[] == ![] 的结果是 true 吗

console.log([] == ![]); // true
// 1. 单目运算符优先级更高,则 ![] => fasle,[] == false
// 2. []先执行 valueOf,若返回值为对象继续执行 toString
//      [].valueOf() = [],[] == false
//      [].toString() = '','' == false
// 3. '' == false 
//      Number('') => 0
//      false => 0
// 4. 0 == 0 => true
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于产品、设计、开发的问题和看法,还有技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程

了解更多

目录

  1. 1. 算术运算符
    1. 1.1. 加法运算符
    2. 1.2. 余数运算符
    3. 1.3. 自增和自减运算符
    4. 1.4. 数值运算符,负数值运算符
    5. 1.5. 指数运算符
    6. 1.6. 赋值运算符
  2. 2. 比较运算符
    1. 2.1. 非相等运算符:字符串的比较
    2. 2.2. 非相等运算符:非字符串的比较
    3. 2.3. 严格相等运算符
    4. 2.4. 相等运算符
  3. 3. 位运算符
    1. 3.1. 按位非
    2. 3.2. 按位与
    3. 3.3. 按位或
    4. 3.4. 按位异或
    5. 3.5. 按位移位
  4. 4. 其他运算符
    1. 4.1. void 运算符
    2. 4.2. 逗号运算符
  5. 5. 运算顺序
    1. 5.1. 优先级
    2. 5.2. 圆括号的作用
  6. 6. 面试题
    1. 6.1. a 等于什么值会让「a == 1 && a == 2 && a == 3 」条件成立;
    2. 6.2. [] == ![] 的结果是 true 吗