# 运算符
# Math对象的扩展
- 二进制表示法 : 0b或0B开头表示二进制(0bXX或0BXX)
- 二进制表示法 : 0b或0B开头表示二进制(0bXX或0BXX)
- 八进制表示法 : 0o或0O开头表示二进制(0oXX或0OXX)
- Number.EPSILON : 数值最小精度
- Number.MIN_SAFE_INTEGER : 最小安全数值(-2^53)
- Number.MAX_SAFE_INTEGER : 最大安全数值(2^53)
- Number.parseInt() : 返回转换值的整数部分
- Number.parseFloat() : 返回转换值的浮点数部分
- Number.isFinite() : 是否为有限数值
- Number.isNaN() : 是否为NaN
- Number.isInteger() : 是否为整数
- Number.isSafeInteger() : 是否在数值安全范围内
- Math.trunc() : 返回数值整数部分
- Math.sign() : 返回数值类型(正数1、负数-1、零0)
- Math.cbrt() : 返回数值立方根
- Math.clz32() : 返回数值的32位无符号整数形式
- Math.imul() : 返回两个数值相乘
- Math.fround() : 返回数值的32位单精度浮点数形式
- Math.hypot() : 返回所有数值平方和的平方根
- Math.expm1() : 返回e^n - 1
- Math.log1p() : 返回1 + n的自然对数(Math.log(1 + n))
- Math.log10() : 返回以10为底的n的对数
- Math.log2() : 返回以2为底的n的对数
- Math.sinh() : 返回n的双曲正弦
- Math.cosh() : 返回n的双曲余弦
- Math.tanh() : 返回n的双曲正切
- Math.asinh() : 返回n的反双曲正弦
- Math.acosh() : 返回n的反双曲余弦
- Math.atanh() : 返回n的反双曲正切
# 1 运算符基础
# 运算符优先级
下面的表将所有运算符按照优先级的不同从高(20)到低(1)排列。
| 优先级 | 运算类型 | 关联性 | 运算符 |
|---|---|---|---|
| 21 | 圆括号 | n/a(不相关) | ( … ) |
| 20 | 成员访问 | 从左到右 | … . … |
| 需计算的成员访问 | 从左到右 | … [ … ] | |
| new (带参数列表) | n/a | new … ( … ) | |
| 函数调用 | 从左到右 | … ( … ) | |
| 可选链(Optional chaining) | 从左到右 | ?. | |
| 19 | new (无参数列表) | 从右到左 | new … |
| 18 | 后置递增(运算符在后) | n/a | … ++ |
| 18 | 后置递减(运算符在后) | n/a | … -- |
| 17 | 逻辑非 | 从右到左 | ! … |
| 17 | 按位非 | 从右到左 | ~ … |
| 17 | 一元加法 | 从右到左 | + … |
| 17 | 一元减法 | 从右到左 | - … |
| 17 | 前置递增 | 从右到左 | ++ … |
| 17 | 前置递减 | 从右到左 | -- … |
| 17 | typeof | 从右到左 | typeof … |
| 17 | void | 从右到左 | void … |
| 17 | delete | 从右到左 | delete … |
| 17 | await | 从右到左 | await … |
| 16 | 幂 | 从右到左 | … ** … |
| 15 | 乘法 | 从左到右 | … * … |
| 15 | 除法 | 从左到右 | … / … |
| 15 | 取模 | 从左到右 | … % … |
| 14 | 加法 | 从左到右 | … + … |
| 14 | 减法 | 从左到右 | … - … |
| 13 | 按位左移 | 从左到右 | … << … |
| 13 | 按位右移 | 从左到右 | … >> … |
| 13 | 无符号右移 | 从左到右 | … >>> … |
| 12 | 小于 | 从左到右 | … < … |
| 12 | 小于等于 | 从左到右 | … <= … |
| 12 | 大于 | 从左到右 | … > … |
| 12 | 大于等于 | 从左到右 | … >= … |
| 12 | in | 从左到右 | … in … |
| 12 | instanceof | 从左到右 | … instanceof … |
| 11 | 等号 | 从左到右 | … == … |
| 11 | 非等号 | 从左到右 | … != … |
| 11 | 全等号 | 从左到右 | … === … |
| 11 | 非全等号 | 从左到右 | … !== … |
| 10 | 按位与 | 从左到右 | … & … |
| 9 | 按位异或 | 从左到右 | … ^ … |
| 8 | 按位或 | 从左到右 | … |
| 7 | 逻辑与 | 从左到右 | … && … |
| 6 | 逻辑或 | 从左到右 | … |
| 5 | 空值合并 | 从左到右 | … ?? … |
| 4 | 条件运算符 | 从右到左 | … ? … : … |
| 3 | 赋值 | 从右到左 | … = … |
| 3 | 赋值 | 从右到左 | … += … |
| 3 | 赋值 | 从右到左 | … -= … |
| 3 | 赋值 | 从右到左 | … **= … |
| 3 | 赋值 | 从右到左 | … *= … |
| 3 | 赋值 | 从右到左 | … /= … |
| 3 | 赋值 | 从右到左 | … %= … |
| 3 | 赋值 | 从右到左 | … <<= … |
| 3 | 赋值 | 从右到左 | … >>= … |
| 3 | 赋值 | 从右到左 | … >>>= … |
| 3 | 赋值 | 从右到左 | … &= … |
| 3 | 赋值 | 从右到左 | … ^= … |
| 3 | 赋值 | 从右到左 | … |
| 3 | 赋值 | 从右到左 | … &&= … |
| 3 | 赋值 | 从右到左 | … |
| 3 | 赋值 | 从右到左 | … ??= … |
| 2 | yield | 从右到左 | yield … |
| 2 | yield* | 从右到左 | yield* … |
| 1 | 展开运算符 | n/a | ... … |
| 0 | 逗号 | 从左到右 | … , … |
# 1.1 优先级: 优先级高的运算符最先被执行
- 问题:1 || 1 ? 2 : 3 ;
答案:2
解析:
||的优先级高
相当于:(1 || 1 )? 2 : 3
而不是:1 || (1 ? 2 : 3 )
# 1.2 关联性: 运算符执行时的方向。是从左向右,还是从右向左
- 问题:
+function (){var a = b = 1;}();
console.log(b);
console.log(a);
答案:1 error
解析:赋值从右到左,var a = b = 1所以相当于
b = 1;
var a = b;
那有同学可能会问,为什么不是?
var b = 1;
var a = b;
还记得变量提升吗?var a = b = 1;在变量提升的时候,只会把a去声明,并不会执行赋值中的b。 所以要想把b也声明了就需要按照语法 var a=1 , b ;
现在我们仔细把优先级的题改一下
- 1 || fn() && fn()
MDN上写的是优先级高的运算符最先被执行,我们都知道 ||是短路的,后边不会执行。那么这个最先被执行的含义是什么呢?
# 1.3 短路
&&运算符的短路(a && b):如果a为假,b就不用执行了| |运算符的短路(a || b):如果a为真,b就不用执行了
但是如果短路运算符的两边都是bool值的话,则是正常的
与、或
- 问题:1 || fn() && fn()
答案:1 fn不会执行
解析:就是利用&&运算符的短路原理啊。
讲到这有些同学会觉得很简单啊,就是这样啊,看到短路后边就不用算了啊。也有的同学可能会有点懵,不是说好了, 优先级高的先被执行吗?明明&&的优先级高啊。哈哈,别吵吵,我们一起看下一题。
- 问题:
var a = 42;
var b = "foo";
var c = 0;
c || b ? a : b ; // 42
2
3
4
# 2 绑定
定义:运算符的优先级高先执行,并不是真正的执行,而是更强的绑定。
1 || fn() && fn() // &&的优先级高,所以将后边的绑定
1 ||(fn() && fn()) // 所以相当于1 和(fn() && fn())的值去逻辑或
1 ||(fn() && fn()) // 我们查表,逻辑或从左到右执行。
1 ||(fn() && fn()) // 左执行,1是真值,所以短路,后边不执行
问题:
var a = 42;
var b = "foo";
var c = 0;
c || b ? a : b ;
2
3
4
答案:42
解析:c || b ? a : b ; //查表
条件运算符权重是4,逻辑与符权重是6,所以逻辑与有更强的绑定
(c || b )? a : b ; //(c || b )相当于条件运算符里的条件
(c || b )? a : b ; //(c || b )值是0 ,所以值是 a
好,我们在做两题巩固一下
var a = 5;
var b = 5;
var c = 5+a+++b;
2
3
[ a , c ] 答案:[6, 15]
解析:b = 5+a+++b; //查表
后置递增权重17前置递增权重16
b = 5 +(a++)+ b; //++优先级更高,所以和绑定a绑定在一起
b = 5 +(a++)+ b; //根据语法后置递增先执行语句,后递增
b = 5 +(a++)+ b; //执行语句时a是5,所以b是15
b = 5 +(a++)+ b; //a在进行自增,得到6
var a = 5;
var b = 5;
var c = ++a-b;
2
3
[ a , c ] 答案:[6, 1]
解析:var c = ++a-b; //查表 前置递增权重和一元减权重都是16,从左往右执行
var c = ++a-b; //根据语法前置递增先递增,后执行语句 a = 6
var c = ++a-b; //执行语句时a是6,所以b是1
var a = 42;
var b = "foo";
var c = 0;
a && b || c ? c || b ? a : c && b : a
2
3
4
# 3 关联
定义:运算符的关联性去定义表达式的处理方向
问题:a && b && c 的执行顺序
解析:
(1)两个运算符都是&&,权重一样。所以这个时候就要看关联性。
(2)查表 &&的关联性是从左到右
(3)所以表达式应该是 ( a && b ) && c问题:a ?b :c ?d : e 的执行顺序
解析:
(1)两个运算符都是条件运算符,权重一样。所以这个时候就要看关联性。
(2)查表条件运算符的关联性是从右到左
(3)所以表达式应该是 a ?b :(c ?d : e )
# 4 释疑
释疑顾名思义就是解释调疑惑的地方,那最好的办法就是加()。
如果你能够熟练运用优先级/关联的规则,你的代码能更简洁,许多框架都是这样写的,非常漂亮。
# JS 新语法「可选链?」「双问号??」
# 案例介绍
var street = user.address && user.address.street;
//有了新语法,则可写成:
var street = user.address?.street
2
3
var fooInput = myForm.querySelector('input[name=foo]')
var fooValue = fooInput ? fooInput.value : undefined
//有了新语法,则可写成:
var fooValue = myForm.querySelector('input[name=foo]')?.value
2
3
4
但在实际使用中,还是会有些不便,比如:
const result = response?.settings?.n || 100
你希望如果 response 或者 response.settings 或者 response.settings.n 不存在(值为 null 或者 undefined)时,result 保底值为 100。
但是上面代码在 n 为 0 的时候,也会让 result 变成 100,你实际上希望此时 result 为 0。
于是你只好把代码写成这样
const result = response?.settings?.n === undefined ? 100 : response?.settings?.n
//或者封装一下
const result = fetch(response?.settings?.n, 100)
//现在,你可以用另一个新语法——「双问号语法」——来简化代码:
const result = response?.settings?.n ?? 100;//这个 ?? 的意思是,如果 ?? 左边的值是 null 或者 undefined,那么就返回右边的值。
2
3
4
5
6
# 可选链释义
Optional Chaining 是 JavaScript 的一个新特性,它允许我们在
尝试访问对象的属性之前检查对象是否存在。其他语言也有类似的东西,例如,C# 的 Null Conditional 操作符,与 Optional Chaining 非常类似。
?.
// Still checks for errors and is much more readable.
const nameLength = db?.user?.name?.length;
2
如果db、user或name是undefined或null会发生什么?使用 Optinal Chaining 操作符时,JavaScript 会将nameLength初始化为undefined,而不是抛出错误。
# 其他语法形式_调用和动态属性
还有一个用来调用可选方法的操作符版本:
// Extends the interface with an optional method, which is present
// only for admin users.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;//因为?.()是实际的操作符,它适用于 之前 的表达式
2
3
操作符还有第三种用法,就是可选的动态属性访问,通过
?.[]实现。它要么返回括号中的参数所引用的值,或者如果没有可以获取值的对象,则返回undefined。
// Extends the capabilities of the static property access
// with a dynamically generated property name.
const optionName = 'optional setting';
const optionLength = db?.user?.preferences?.[optionName].length;
2
3
4
需要非undefined默认值时,Optinal Chaining 操作符可以与
双问号??操作符组合使用。这样可以使用指定的默认值进行安全的深层属性访问
{ // With optional chaining and nullish coalescing:
const firstName = object?.names?.first ?? '(no first name)';
// → 'Alice'
const middleName = object?.names?.middle ?? '(no middle name)';
// → '(no middle name)'
}
2
3
4
5
6
7
# OptinalChaining操作符的属性
Optinal Chaining 操作符具有一些有趣的属性:
短路
堆叠
可选删除
短路(Short-circuiting)意味着如果 Optinal Chaining 操作符提前返回,则不对表达式的其余部分求值:
// `age` is incremented only if `db` and `user` are defined.
db?.user?.grow(++age);
2
堆叠(Stacking)意味着可以对一系列属性访问应用多个 Optinal Chaining 操作符:
// An optional chain may be followed by another optional chain.
const firstNameLength = db.users?.[42]?.names.first.length;
2
可选删除(Optinal deletion)意味着可以将delete操作符与 Optinal Chain 结合使用:
// `db.user` is deleted only if `db` is defined.
delete db?.user;
2