ES6新特性 - 运算符扩展(指数、链判断、Null判断、逻辑赋值运算符)

182 阅读6分钟

ES6运算符扩展

1. ** - 指数运算符

  • 这个运算符的一个 特点右结合(先算右边的),而不是常见的左结合;
    console.log(2 ** 2);  // 4,等价于 2 * 2 (2 的 2次方)
    console.log(2 ** 3);  // 8,等价于 2 * 2 * 2 (2 的 3次方)
    
  • 多个 指数运算符 连用时,是从 最右边 开始计算 的;
    // 首先计算的是第一个指数运算符,然后才到第一个指数运算符
    // 先计算得到 3的4次方,然后再计算 2 的 81次方
    console.log(2 ** 3 ** 4);
    
    // 等价于下边这块代码
    let num = 3 ** 4;  // 81
    num = 2 ** num;
    console.log(num);  // 2.4178516392292583e+24
    
  • 指数运算符 可以与 等号 结合,形成一个新的赋值运算符(**= );
    // 指数运算符 与 = 结合
    let a = 6;
    a **= 2;  // 等价于 a = a ** 2
    console.log(a);  // 36
    console.log('-------------')
    
    let b = 4;
    b **= 3;  // 等价于 b = b ** 3
    console.log(b);  // 64
    
  • 运行展示: image.png

2. ?. - 链判断运算符

  • 编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取 message.body.user.firstName 这个属性,安全的写法是写成下面这样:
    const message = {
        body: {
            user: {
                firstName: '禁止摆烂_才浅',
            }
        }
    }
    
    // 错误的写法
    const firstName = message.body.user.firstName || 'default'
    
    // 正确的写法
    const firstName = (
        message && 
        message.body && 
        message.body.user && 
        message.body.user.firstName
    ) || 'default
    
    • 上面的例子,firstName属性在对象的第四层,所以需要判断四次,每一层的 对象是否存在属性是否有值
  • 三元运算符 ? : 也常用于判断对象是否存在:
    const textObj = {
        value: '禁止摆烂_才浅'
    }
    const value = textObj ? textObj.value : 'default'
    
    • 上面例子中,必须先判断textObj是否存在,才能读取 textObj.value
  • 这样层层的判断是非常麻烦的,因此在ES 2020引入了【链判断运算符?. 简化了上面的写法;
    const firstName = messgae?.body?.user?.firstName || 'default'
    const textObjValue = textObj?.value
    
    • 上面代码使用了 ?. 运算符,直接在链式调用的时候判断,左侧的对象 是否为 nullundefined。如果 ,就 不往下运算,而是 返回 undefined
  • 注意
    • 如果 运算符左侧 的 值为 nullundefined,就不会往下运行,直接返回 undefined
  • 下面是判断对象方法是否存在,如果存在就立即执行的例子:
    const fnObj = {
        sayHi: () => {
            return '每天进步一点点'
        }
    }
    console.log(fnObj?.sayHi?.())
    
    • 上面代码中,fnObj.sayHi如果有定义,就会调用该方法,否则textObj.sayHi直接返回undefined,不再执行?.后后面的部分。
  • 下面是 ?.运算符 常见形式,以及不使用该运算符时的等价形式:
    a?.b
    // 等价于 a == null || undefined ? undefined : a.b
    
    a?.[x]
    // 等价于 a == null || undefined ? undefined : a[x]
    
    a?.b()
    // 等价于 a == null || undefined ? undefined : a.b()
    
    a?.()
    // 等价于 a == null || undefined ? undefined : a()
    
    • 上面代码中,特别注意后两种形式:
      • a?.b()
        • 如果 a?.b() 里面的 a.b 有值,但不是函数,不可调用,那么 a?.b() 是会报错的;
      • a?.()
        • 如果 a 不是 nullundefined,但也不是函数,那么 a?.() 会报错。
  • 注意
    • 短路机制:
      • 本质上,?. 运算符相当于一种短路机制,只要不满足条件,就不再往下执行;
        a?.[++x]
        // 等同于
        a == null ? undefined : a[++x]
        
      • 上面代码中,如果 anullundefined,那么x不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值;
    • 括号的影响:
      • 如果属性连有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响;
        (a?.b).c
        // 等价于
        (a == null ? undefined : a.b).c
        
      • 上面代码中,?.圆括号 外部 没有 影响,不管 a 对象是否存在,圆括号后面的c总是会执行。
      • 一般来说,使用 ?. 运算符 的场合不应使用圆括号;
    • 报错场合:
      • 以下写法是禁止的,会报错:
        // 构造函数
        new a?.()
        new a?.b()
        
        // 链判断运算符的右侧有模板字符串
        a?.`${b}`
        a?.n`${c}`
        
        // 链判断运算符的左侧是 super
        super?.()
        super?.foo
        
        // 链判断运算符用于赋值运算符左侧
        a?.b = c
        
    • 右侧 不得为 十进制数值
      • 为了保证兼容以前的代码,允许 foo?.3:0 被解析成 foo ? .3 : 0,因此规定如果 ?. 后面紧跟一个十进数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也即是说,那个小数点会归属于后面的十进制数字,形成一个小数;

3. Null判断运算符

  • 读取对象属性值的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。常见做法是通过 || 运算符指定默认值。
    const headerText = response.settings.headerText || 'Hello, world!';
    const animationDuration = response.settings.animationDuration || 300;
    const showSplashScreen = response.settings.showSplashScreen || true;
    
    • 上面的三行代码都是通过 ||运算符指定默认值,但是这样写是错误的。开发者的原意是,只要属性值的值为nullundefined,默认值就会生效,但是属性值如果是 空字符串、false、0,默认值就会生效。
  • 为了避免这种情况,ES2020引入 了一个新的 Null判断运算符 ??。它的行为类似于||,但是 只有 运算符左侧值为 nullundefined 时,才会 返回 右侧 的值
    const headerText = response.settings.headerText ?? 'Hello, world!';
    const animationDuration = response.settings.animationDuration ?? 300;
    const showSplashScreen = response.settings.showSplashScreen ?? true;
    
    • 上面代码中,默认值 只有在 左侧属性值nullundefined 时,才会生效;
  • ❗❗ 运算符的一个目的,就是跟 判断运算符 ?. 配合使用,nullundefined 的值设置默认值js const animationDuration = response.settings?.animationDuration ?? 300;
    • 上面代码中,如果 response.settingnullundefined,或者response.setting.animationDurationnullundefined,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断;
  • 这个运算符很适合判断函数参数是否赋值:
    function Component (props) {
        const enable = props.enable ?? true
        // ...
    }
    
    • 上面代码判断props参数的enabled属性是否赋值,基本等同于下面的代码:
      function (props) {
          const {
              enabled: enable = true
          } = props
          // ...
      }
      
  • ?? 本质 上是 逻辑运算,它与其他两个逻辑运算符 &&|| 有一个 优先级 问题:
    • 现在的规则是,如果多个逻辑运算符一起使用,必须 用括号 表明 优先级,否则会报错;
    (lhs && middle) ?? rhs;
    lhs && (middle ?? rhs);
    
    (lhs ?? middle) && rhs;
    lhs ?? (middle && rhs);
    
    (lhs || middle) ?? rhs;
    lhs || (middle ?? rhs);
    
    (lhs ?? middle) || rhs;
    lhs ?? (middle || rhs);
    

4. ||= 、 &&= 、 ??= - 逻辑赋值运算符

  • ES2021引入了三个新的逻辑赋值运算符,将逻辑运算符与赋值运算符结合;
    // 或赋值运算符
    x ||= y
    // 等同于
    x || (x = y)
    
    // 与赋值运算符
    x &&= y
    // 等同于
    x && (x = y)
    
    // Null 赋值运算符
    x ??= y
    // 等同于
    x ?? (x = y)
    
    • 这三个运算符||=&&=??=相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算;
  • 他们的一个用途是为了 变量属性 设置 默认值
    // 老的写法
    user.id = user.id || 1;
    
    // 新的写法
    user.id ||= 1;
    
    • 上面代码中,user.id属性如果不存在,则视为1,新的写法比老的写法更紧凑一些;
  • 另一个例子:
    function example(opts) {
      opts.foo = opts.foo ?? 'bar';
      opts.baz ?? (opts.baz = 'qux');
    }
    
    • 上面示例中,参数对象opts如果不存在属性foobaz,则为这两个属性设置默认值,有了 【Null 赋值运算符】 以后,就可以统一写成下面这样:
    function example(opts) {
      opts.foo ??= 'bar';
      opts.baz ??= 'qux';
    }