JavaScript运算符
也叫操作符,英文:operator,关于运算符,除了都知道的优先级,还要知道Associativity
JavaScript中的运算符大多由标点符号表示,少数由关键字表示,每一个运算符具有不同的行为,运算符始终都遵循着一些固定语法。一些运算符可以作用于任何数据类型, 但仍然希望它们的操作数是指定类型的数据,并且大多数运算符返回一个特定类型的值,在下面的运算符规则表中,箭头前为运算符操作数的类型,箭头后为运算结果的类型
分类
JavaScript的运算符总共有59个,如果根据其操作数的个数进行分类,分为一元运算符,二元运算符,三元运算符。而它们的目的都是一致的: 将一个到多个表达式合并为一个表达式
优先级和结合性
运算符优先级控制着运算符的执行顺序,优先级高的运算符的执行总是先于优先级运算符低的运算符——————错误的理解
修正:
不同操作相邻出现的时候,表达式是作为左操作符的后操作数呢?还是作为右操作符的前操作数呢?=>这就是操作符优先级干的事
对优先级错误的理解:
认为拿到一行语句,先按照操作符的优先级划分,找到最高优先级的操作符,先求值。
所以就对 baz
&&
fn()
这样的代码存在困惑,因为如果这么理解的话,fn()
的优先级最高,那么无论如何都会先执行 fn()
再去 &&
判断,可实际的是 &&
判断前面的baz
是否存在而决定是否执行后面的fn()
,此时发生冲突。但实际上所谓的优先级是指操作数结合操作符,优先结合。即同一个操作数被左右两个操作符共同使用的时候,此时这个操作数并不会被两个操作符共同使用,而决定它被谁使用的依据就是操作符的优先级。不是说谁的优先级高就先给谁求值。
那么何时求值?比如 d = a + (b+c)
对表达式d求值,首先对表达式a求值,返回;然后要对 + 后的一个表达式求值,此时 + 后面的表达式结果依赖于b + c
,此时b + c
才开始求值,因为最终的结果依赖b + c
的结果,所以才有了求值。
所以像短路运算符 &&
和 ||
一起出现的时候,因为&&
的优先级要比||
高,所以&&优先结合操作数(但是并不意味着要立即求值,还是要根据从左往右的顺序,先对第一个操作数求值。剩下的,有依赖有需求才会求值。)
所以千万不要再理解成优先级高,优先求值
优先级是怎么产生的?
|
|
【1】注意: b的结果不一样了,上面代码发生了什么?说明了什么?
原因就在于
,
运算符的优先级比=
低,当不同操作相邻(,
=
)出现时,表达式a++
是作为=
的后操作数呢?还是作为,
的前操作数呢?=>操作符的优先级工作了
=>因为
=>
的优先级高于,
=>原式<=>(b = a++), a;
=>此时a++后自增,先返回值,所以表达式b的值是42
|
|
|
|
Associativity(结合性)
如果所有操作符优先级一样,结合性决定了执行结果的唯一性。
结合性是怎么起作用的?
当表达式相邻的操作符具有相同的优先级时,优先级已经不能决定表达式最终结果的时候,结合性就起作用了。
|
|
结合性解决了什么问题?
当优先级一样的时候括号怎么加
|
|
左结合性(从左到右)意味着它被处理为(a OP b) OP c,而右结合性(从右到左)意味着它被解释为a OP (b OP c)。而对于以下代码右结合性是正确的,所以你可以写:
|
|
那么:
|
|
这个要怎么算?先看优先级,两个一样。再看结合性,右结合,所以:
先算c?d:e
再算 a?b:(c?d:e) 这就是所谓右结合。如果是左结合的话 就是先算a?b:c
再算 (a?b:c)?d:e
实际上,一般结合性的问题都可以用括号来解决。
先要考虑优先级,有相同优先级的就看结合性以决定括号的添加方式。结合性决定处理相同优先级的运算符的顺序
或者说为什么结合性不同影响着执行结果???
因为表达式可能会产生副作用,比如函数调用:
|
|
这里foo()会首先执行,它的返回结果决定了bar()是否执行。所以如果bar()在foo()之前执行,整个结果完全不同。
如何更好理解操作符的结合性???
想象一下,什么情况下 AB 不等于 BA
矩阵(不考虑单位矩阵的情况下)
执行顺序
执行顺序是不变的,总是从左到右。优先级和结合性并没有影响代码的执行顺序,也无法影响,它们做的是改变了表达式的求值顺序,注意 执行顺序 != 求值顺序
|
|
深入浅出优先级,结合性,执行顺序,求值顺序
代码执行顺序是永远不变的,从左往右执行。
而优先级和结合性是如何添加括号的依据,表达式越先求值的说明离AST根节点越远,为什么?先求值的说明依赖少,有依赖的还得继续向下遍历。
什么是左值(Left-hand-side expressions)
Left values are the destination of an assignment.
左值指的是赋值操作的目标,也就是能出现在赋值表达式左边的表达式,自定义的函数不能返回左值是ECMAScript的规范。
那么什么类型的值可以出现在赋值表达式的左侧呢?
变量
对象属性
数组元素
运算符总结(持续更新)
优先级 | 运算符 | 操作 | 结合性 | 类型 |
---|---|---|---|---|
20 | ( … ) | 分组 | N/A | N/A |
19 | … . … | 成员访问 | l-to-r | |
19 | … [ … ] | 成员访问 | l-to-r | N/A |
19 | new … ( … ) | 有参构造 | N/A | N/A |
18 | … ( … ) | 函数调用 | l-to-r | N/A |
18 | new … | new.target操作符检测是否用new操作符调用函数/构造函数 | r-to-l | new.target=>undefined/ 在类构造函数中,new.target指的是直接调用的构造函数new。 |
17 | … ++ | 后自增 | N/A | N/A |
17 | … – | 后自减 | N/A | N/A |
16 | ! … | 逻辑非 | r-to-l | bool->bool |
16 | ~ … | 按位求反 | r-to-l | |
16 | + … | 一元加(注意区别于二元加) | r-to-l | any=>num |
16 | - … | 一元减(注意区别于二元减) | r-to-l | any=>num |
16 | ++ … | 先自增 | r-to-l | 属于算术运算符,操作lval |
16 | – … | 先自减 | r-to-l | 属于算术运算符,操作lval |
16 | typeof … | 判断类型 | r-to-l | any=>str |
16 | void … | 返回空 | r-to-l | any=>undefined |
16 | delete … | 删除属性 | r-to-l | lval->bool |
15 | … … | 幂运算 | r-to-l | any,any=>num,求幂前会发生类型转换num |
14 | … * … | 求积 | l-to-r | any,any=>num,求积前会发生类型转换num |
14 | … / … | 求商 | l-to-r | any,any=>num,求商前会发生类型转换num |
14 | … % … | 取余 | l-to-r | any,any=>num,求商前会发生类型转换num |
13 | … + … | 求和/拼接(考虑类型转换) | l-to-r | any,any=>any |
13 | … - … | 求差(考虑隐式转换) | l-to-r | any,any=>num |
12 | … << … | 按位左移 | l-to-r | |
12 | … >> … | 按位右移 | l-to-r | |
12 | … >>> … | 无符号右移 | l-to-r | |
11 | … < … | 小于(会发生隐式转换) | l-to-r | any,any=>bool |
11 | … <= … | 小于等于(会发生隐式转换) | l-to-r | any,any=>bool |
11 | … > … | 大于(会发生隐式转换) | l-to-r | any,any=>bool |
11 | … >= … | 大于等于(会发生隐式转换) | l-to-r | any,any=>bool |
11 | … in … | 测试属性 | l-to-r | str,obj=>bool |
11 | … instanceof … | 测试对象类(含原型链) | l-to-r | obj,fn=>bool |
10 | … == … | 测试相等 | l-to-r | any,any=>bool |
10 | … != … | 测试不等 | l-to-r | any,any=>bool |
10 | … === … | 测试严等 | l-to-r | any,any=>bool |
10 | … !== … | 测试严不等 | l-to-r | any,any=>bool |
9 | … & … | 按位与 | l-to-r | |
8 | … ^ … | 按位非 | l-to-r | |
7 | … | … | 按位或 | l-to-r | |
6 | … && … | 逻辑与(操作数选择器) | l-to-r | any,any=>any |
5 | … || … | 逻辑或(操作数选择器) | l-to-r | any,any=>any |
4 | … ? … : … | 三元表达式 | r-to-l | bool,any,any=>any |
3 | … = … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … += … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … -= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … = … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … = … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … /= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … %= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … <<= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … >>= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … >>>= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … &= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … ^= … | 运算且赋值 | r-to-l | lval,any=>any |
3 | … |= … | 运算且赋值 | r-to-l | lval,any=>any |
2 | yield … | 暂停当前gen函数执行,返回表达式的值给gen函数的调用者 | r-to-l | any=>any |
2 | yield … | 用于在一个gen函数中调用另一个gen函数 | r-to-l | AnoFn*()=>any |
1 | … … | 展开运算符 | r-to-l | array=>any |
0 | , … | 忽略第一个操作数,返回第二个操作数 | r-to-l | any,any=>any |
注:
l-to-r : left-to-right 左结合
r-to-l : right-to-left 右结合
lval : leftValue 左值
in右侧必须是一个对象。例如,您可以指定使用String构造函数创建的字符串,但不能指定字符串文字。in操作符会检测原型链上的属性。使用delete操作符删除属性,则操作in员返回false该属性。如果将属性设置为undefined但不将其删除,则in该属性的操作符返回true。
instanceof操作符会检测原型链。
关于–,++操作符报错“ReferenceError: Invalid left-hand side expression in postfix operation” ,这里报的是引用错误,其实想想应该可以这么理解因为++ 和 –操作符不能直接操作值类型,因为不管是先自增/自减。还是后自增/自减,总要返回值。那么值往哪返?->肯定需要一个容器,这个容器就是左值.
|
|
操作符对数据做了什么???
关注类型转换
参考来源:
知乎:JavaScript中运算符优先级的问题?@RednaxelaFX
解释器,树遍历解释器,表达式的求值顺序@RednaxelaFX