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 - 运行展示:
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- 上面代码使用了
?.运算符,直接在链式调用的时候判断,左侧的对象 是否为null或undefined。如果 是,就 不往下运算,而是 返回undefined。
- 上面代码使用了
- 注意:
- 如果 运算符左侧 的 值为
null或undefined,就不会往下运行,直接返回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不是null或undefined,但也不是函数,那么a?.()会报错。
- 如果
- 上面代码中,特别注意后两种形式:
- 注意:
- 短路机制:
- 本质上,
?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行;a?.[++x] // 等同于 a == null ? undefined : a[++x] - 上面代码中,如果
a是null或undefined,那么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判断运算符
- 读取对象属性值的时候,如果某个属性的值是
null或undefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。const headerText = response.settings.headerText || 'Hello, world!'; const animationDuration = response.settings.animationDuration || 300; const showSplashScreen = response.settings.showSplashScreen || true;- 上面的三行代码都是通过
||运算符指定默认值,但是这样写是错误的。开发者的原意是,只要属性值的值为null或undefined,默认值就会生效,但是属性值如果是空字符串、false、0,默认值就会生效。
- 上面的三行代码都是通过
- 为了避免这种情况,
ES2020引入 了一个新的 Null判断运算符??。它的行为类似于||,但是 只有 运算符左侧 的 值为null或undefined时,才会 返回 右侧 的值;const headerText = response.settings.headerText ?? 'Hello, world!'; const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true;- 上面代码中,默认值 只有在 左侧属性值 为
null或undefined时,才会生效;
- 上面代码中,默认值 只有在 左侧属性值 为
- ❗❗ 运算符的一个目的,就是跟 判断运算符
?.配合使用,为null或undefined的值设置默认值。js const animationDuration = response.settings?.animationDuration ?? 300;- 上面代码中,如果
response.setting是null或undefined,或者response.setting.animationDuration是null或undefined,就会返回默认值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如果不存在属性foo或baz,则为这两个属性设置默认值,有了 【Null 赋值运算符】 以后,就可以统一写成下面这样:
function example(opts) { opts.foo ??= 'bar'; opts.baz ??= 'qux'; } - 上面示例中,参数对象