在 JavaScript 或等语言中会出现很多运算符,对于有些运算符不明白看别人代码就有些懵,学习并整理一下它们的功能及用法。

这些运算符主要包括:双叹号 !!,短路或 ||,短路与 &&,按位非 ~,按位与 &,异或 ^,双波浪号 ~~,可选链式操作符(Optional Chaining Operator) ?.

有些运算符是某些语言特有的,但大多具有普遍性,代表的含义和语法也不尽相同。

可能还会有一些运算符比如:可选参数 ?: 非空断言 !. 这些包含在某个特定语言,例如:TypeScript 中的运算符在此不再解释。


双叹号 !!

  • 作用

    将不同类型的值转换为 boolean 类型

    如果该类型的值为真,则返回 true,在 JS 中被认为是真的值包括:

  • 对象:{}、new Date()

  • 数组:[]

  • 非空字符串:“str”

  • 非 0 数字:-1

如果该类型的值为假,则返回 false,在 JS 中被认为是假的值包括:

  • 零长度空字符串:""

  • 0

  • null

  • undefined

  • Nan

  • 用法

    1. 在需要传入 boolean 类型的地方,比如字符串判空的条件表达式等
    if (!!str) {
      return test.endsWith('.png');
    }
    

短路或 ||

  • 作用

    左边的表达式为 true 则不执行右边的表达式,为 false 则执行右边的表达式

  • 用法

    1. 默认值处理  
    
    const param = object || ''
    
    1. 逻辑运算

短路与 &&

  • 作用

    左边的表达式为 false 则不执行右边的表达式,为 true 则执行右边的表达式

  • 用法

    1. 访问对象属性时,检测对象是否存在 object && object.getName()
    2. 逻辑运算
    3. 虽然 ||&& 配合可以做 if/else 判断,object && object.getName() || '',但是建议使用三目运算符

按位非 ~

  • 作用

    可以简单的理解为该值取负值后减 1:~N = -(N+1)

    按位运算符会将其操作数隐式转换为带符号的 32 位整数。

  • 用法

    1. 判断数组或者字符串中是否存在某个元素

      if(str.indexOf(subStr) != -1) {}
      if(str.indexOf(subStr) >= 0) {}
      

      可以简写为: if(~str.indexOf(subStr)) {}

      原理:不存在返回 -1,~-1 = 0,大于 -1 的值,0,1,2,3 … 按位非的值 1,2,3,4…都大于 0

      ES6 中引入了新的方法 includes(),可以用来检测一个字符串是否包含另外一个字符串。

    2. 位掩码(Bitmasking):单用单一字段存储多个值时撤销某个值

      位掩码经常被用做单个 int 字段储存多个配置项的情景,这些配置项一般会用做 2 进制的不同位置的值表示其掩码,例如使用左移运算符 1 << this.ordinal(),如果我们要根据状态设置或取消配置项则可以:

      public enum Config {
          CONFIG_A,
          CONFIG_B,
          CONFIG_C;
      
          public final int mask = 1 << this.ordinal();
      
          public final int getMask() {
              return this.mask;
          }
      
          /**
           * 是否启用该配置
           */
          public static boolean isEnabled(int configs, Config config) {
              return (configs & config.mask) != 0;
          }
      
          /**
           * 设置/取消该配置
           */
          public static int config(int configs, Config config, boolean state) {
              if (state) {
                  configs |= config.mask;
              } else {
                  configs &= ~config.mask;
              }
      
              return configs;
          }
      }
      

按位与 &

  • 作用

    按位操作,同时为 1 则为 1,否则为 0

  • 用法

    1. 位掩码:权限管理的权限判断

      一个用户对文件或目录所拥有的权限分为三种:”可读”、”可写”和”可执行”,分别用 1(001) 、2(010) 和 4(100) 来表示,它们之间可以任意组合:有“可读”、“可写”权限就用 3 来表示(1 + 2 = 3);有”可读“、”可执行“权限就用 5 来表示(1 + 4 = 5),三种权限全部拥有就用 7 表示(1 + 2 + 4 = 7)。

      判断用户是否有可写权限 if (role & 2 === 2)

      假设用户权限为 5(101) & 2(010) = 1(001) !== 2(010),则该用户不包含可写权限

      用户权限为 3(011) & 2(010) = 2(010) === 2(010),则该用户包含可写权限

位异或 ^

  • 作用

    如果 a、b 两个值不相同,则异或结果为 1。如果 a、b 两个值相同,异或结果为 0。

    异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用 1 表示真,0 表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法。

    异或的符号为 XOR、⊕。

  • 用法

    1. 不引入中间数,交换两个整形数字

      void swap(int a, int b)
      {
        a=a^b;
        b=b^a;
        a=a^b;
      }
      
    2. 结果取反

      boolean evaluate() {
         return isNegated() ^ expression;
      }
      

      如果 isNegatedtrue,则结果为 expression 的取反,否则为表达式的结果。

双波浪号 ~~

  • 作用

    可以将浮点数或者字符串数值删除小数点后的所有内容并转换为整形,

    对于小于 0 的数字向上转型,大于 0 的数字向下转型,相当于:

    function(x) {
    if(x < 0) return Math.ceil(x);
    else return Math.floor(x);
    }
    

    x 只能位于 -(2^31) 和 2^31 - 1 之间,否则会溢出

可选链式操作符(Optional Chaining Operator) ?.

  • 作用

    确保在取对象属性时不用做不必要的前置对象存在的判断,以及避免抛出 undefined 异常

    const user = { name: 'Zeral' }
    
    // 不会抛出异常
    const city = user?.address?.city
    
    console.log(city) // undefined
    

    之前我们可能会这样写:

    var user = { name: 'Zeral' }
    var city = user
         && user.address
         && user.address.city
    

    该语法也支持 function 和 constructor 的调用

    const address = getAddressById?.(123)

    如果 getAddressById 是个函数则会被调用,否则返回 undefined

    该语法暂未被浏览器广泛支持,使用时请配置babel-plugin-transform-optional-chaining

空值合并运算符(Nullish Coalescing) ??

空值合并操作符??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

逻辑或操作符(||不同,逻辑或操作符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,''0)时。

  • 用法

  1. 为变量赋默认值

    以前,如果想为一个变量赋默认值,通常的做法是使用逻辑或操作符(||),然而,由于 || 是一个布尔逻辑运算符,左侧的操作数会被强制转换成布尔值用于求值。任何假值(0''NaNnullundefined)都不会被返回。这导致如果你使用 0''NaN 作为有效值,就会出现不可预料的后果。

    let count = 0;
    let text = "";
    
    let qty = count || 42;
    let message = text || "hi!";
    console.log(qty);     // 42,而不是 0
    console.log(message); // "hi!",而不是 ""
    

    空值合并操作符可以避免这种陷阱,其只在第一个操作数为 nullundefined 时(而不是其它假值)返回第二个操作数:

    let myText = ''; // An empty string (which is also a falsy value)
    
    let notFalsyText = myText || 'Hello world';
    console.log(notFalsyText); // Hello world
    
    let preservingFalsy = myText ?? 'Hi neighborhood';
    console.log(preservingFalsy); // '' (as myText is neither undefined nor null)
    
  2. 短路

    与 OR 和 AND 逻辑操作符相似,当左表达式不为 nullundefined 时,不会对右表达式进行求值。

    function A() { console.log('函数 A 被调用了'); return undefined; }
    function B() { console.log('函数 B 被调用了'); return false; }
    function C() { console.log('函数 C 被调用了'); return "foo"; }
    
    console.log( A() ?? C() );
    // 依次打印 "函数 A 被调用了"、"函数 C 被调用了"、"foo"
    // A() 返回了 undefined,所以操作符两边的表达式都被执行了
    
    console.log( B() ?? C() );
    // 依次打印 "函数 B 被调用了"、"false"
    // B() 返回了 false(既不是 null 也不是 undefined)
    // 所以右侧表达式没有被执行