Javascript操作符中的小细节

javascript中的操作符可用于表达式中,将小的表达式连接成大的表达式。本文对常见操作符的一些规则做简要的总结。

算术操作符

++ 和 —

单目,++--可用于数值的自加一和自减一。操作数会被转换为number,然后加一或减一,再赋值给操作数标识符。

整体表达式的值,取决于操作符合操作数的前后位置。如

let a = 1;
a++     // => 1,此时a === 2
--a     // => 1,此时a === 1
复制代码

加号(+)

+用作单目运算符时,可将其后的操作数转换为数值。如:

+ 1         // => 1
+ "1"       // => 1
> + {}      // => NaN
复制代码

用作双目运算符时可用于数值的加减和字符串的拼接。

当2个操作数都为string或者number时规则最简单,如下

1 + 1 // => 2
"a" + "b" // => "ab"
复制代码

其他情况下,操作符的行为如下:

  • 当操作数为对象时,会先将该对象转换为原始值。转换规则大致如下:对于Date对象,先调用toString(),得到的结果为原始值时转换结束,否则调用valueOf(),如果该方法返回原始值直接使用,否则会抛出类型错误。对于其他内置Js类型,也是这样的转换,不过是先尝试valueOf(),后尝试toString()
  • 之后,如果有string类型的操作数,则进行字符串的拼接,会将另一个操作数先转换为string类型,然后进行字符串的拼接。
  • 否则,两个操作数会统一转化为number类型,进行数值的加法运算。

示例如下:

1 + "2"     // => "12"
1 + {}      // => "1[object object]"
1 + [0]     // => "10"
1 + null    // => 1
1 + undefined // => NaN
true + true // => 2
复制代码

负号(-)

单目时将其后操作符转换为数值,然后改变符号。

- 1      // => -1
- "-1"   // => 1
- {}     // => NaN
复制代码

双目时,将两个操作数转换为number,然后进行减法运算。

乘除运算(* / % **)

将两个操作数转换为number,然后进行乘法、除法、取余或幂运算。

js中的数字都为浮点数。各种浮点数运算的结果不会被取整为整数。

其中,**与之前的Math.pow()功能相同,**有右结合特性,会优先计算**右边部分。如:

2 ** 3 ** 2       // => 2*(3*2)
复制代码

位运算符

位运算符会将操作数转化为数字,然后转换为2进制表示形式进行位运算。位运算符有 &|^~<<>>>>>,分别会进行按位与、按位或
、按位异或、按位取非、左移、右移、零填充右移。

对一个值取非,相当于改变符号并减一。

对于一个值左移一位,相当于该值乘2。左移n位,相当于该值乘2^n。右移也是如此,会响应除2^n,并且会舍弃余数。例子如下:

0x1234 & 0x00FF     // => 0x0034
0x1234 & 0x00FF     // => 0x0FF0
~0x0F       // => 0xFFFFFFF0
3 << 1      // => 6
7 >> 1      // => 3
-7 >> 1     // => -4
复制代码

关系操作符

=== 和 !==

=== 判断两个操作数是否严格相等并返回布尔值。不做类型转换,行为如下:

  • 两个值类型不同,则不相等。
  • 再判断值是否相等。
  • NaN属于number,但和任何值都不相等,包括自身(所以可用x !== x判断x是否为NaN)。
  • +0-0相等。
  • 对于string,需要判断长度和相同位置对应的底层字符编码是否相等。
  • 对于对象引用,只有引用同一值才相等。属性是否相同不作为判断依据。

!==和值和===相反。

== 和 !=

== 是基于类型转换的相等判断,如果两操作数类型不同,会尝试做类型转换,然后比较。
两数类型不同时,规则如下:

  • 如果两值为nullundefined,则相等。
  • 如果一个值为number,另一值为string,则将string转换为number,再比较数值。
  • 布尔值会转换为数值0或1。
  • 如果一个值为对象,另一值为数值或字符串,则先将对象转换为原始值,再比较。
  • 其他任何值的组合都不相等。

例:

null == undefined       // => true
"1" == true     // => true,true转换为数值1,"1"转换为数值1,相等
["0"] == 0      // => true,
复制代码

对于第三个例子,[“0”]为偏向字符串的对象,首先尝试调用自身的toString(),结果为”0″,为基本类型,对象到原始值的转换完毕。然后将”0″转换为数值0,与右边操作数比较。

比较操作符

比较操作符包含><>=<=。其中>=<=表示“不大于”和“不小于”,和=====没有依赖关系。

比较符最终只会进行number之间的比较和string之间的比较。对于number会比较数值的大小,对于string会从首字母开始比较各个字母的Unicode编码值大小。如:

"abc" > "ABC"       // => true,大写字母的编码值小于小写字母
复制代码

不能直接比较时,有如下规则:

  • 对象值会被转换为原始值。
  • 当两值都为字符串时,比较Unicode编码。
  • 其他情况下,将两值转化为number,比较大小。NaN和任何值比较结果都为false

in操作符

in用于判断右侧对象中是否有左侧字符串属性。左侧操作数为字符串、Symbol或可以转化为字符串的值,右侧必须为对象。返回布尔值。
示例:

let obj = {a: 1};
"a" in obj      // => true
"b" in obj      // => false
"toString" in obj       // => true,继承的方法 

let arr = ["a", "b"]
"0" in arr      // => true
1 in arr        // => true,1会转换为"1"
3 in arr        // => false
复制代码

instanceof

instanceof期待左侧操作数为对象,右侧操作数为对象类型,然后判断对象是否为该类型的实例,返回布尔值。
如:

let arr = [];
arr instanceof Array        // => true
arr instanceof Object       // => true
arr instanceof Date         // => false
复制代码

instaceof依据原型链进行判断,对于obj instaceof f而言,会先取得f.prototype的值,然后判断obj.__proto__是否为f.prototype、查找到则返回true,否则会继续沿着原型链(obj._proto__.__proto__)查找,直至原型链的终点null,返回false。所有对象都是Object的实例。

逻辑操作符

逻辑与(&&)

&&常用于布尔值的运算,操作数都为true时返回true。如:

a === 1 && b === 2
复制代码

&&并不要求操作数的类型。操作数为其他类型值时也有有趣的特性。JS中的值可被分为真值和假值。假值有:false""0undefinednull。其他值为真值。

&&有短路的特性,当左操作数为真值时,返回的结果为右操作数。左操作数为假值时,返回结果为左操作数,并且不会对右操作数求值。如:

10 && 3     // => 3
1 && (console.log("r"))     // 求值为右值,打印"r"。
0 && (console.log("r"))     // => 0
复制代码

逻辑或(||)

类似于逻辑与,||也有短路的特性。当左操作数为真值时,返回结果为左操作数,不对右操作数求值。当左操作数为假值时,返回结果为右操作数。如:

10 || 3     // => 10
1 || (console.log("r"))     // => 1
0 || (console.log("r"))     // 求值为右值,打印"r"。
复制代码

逻辑非(!)

逻辑非没有短路特性,!会将操作数传唤为布尔值,然后取反,最终返回布尔值。一个常用的逻辑化简如下:
对一个逻辑表达式整体取反,相当于对操作数和操作符各自取反。

!(p && q) === (!p || q)     // => true
复制代码

其他操作符

void

void返回值始终为undefined,但其之后的表达式仍会正常执行。可以用于不使用花括号的箭头函数。如:

let a = 1;
const a_pp = () => void(a++);
a_pp();     // => undefined
a;      // => 2
复制代码

条件操作符(?:)

条件操作符接收三个操作数,第一个操作数会被转换为布尔值。为真时,返回第二个操作数;否则返回第三个操作数。如:

const bol = true
bol ? "1" : "0";        // => "1"
复制代码

先定义(??)

虽然它和条件操作符长得很像,但两者没有关系。是双目操作符,求值返回先定义的操作数。如果左操作数不为nullundefined,就返回左操作数,否则返回右操作数。如:

const apple = "apple"
apple ?? "new apple"        // => "new apple"
复制代码

它也有类似于逻辑或(||)的短路性质,可用于判断值是否有效。区别在于||需要判断左操作数为真值还是假值。在逻辑或中""0false都是无效值。而在??中,它们为有效值。因此可以看做是对||的补充。如:

let str = "", num = 0;
str ?? str : "initial"      // => "initial"
num ?? "exist" : "not exist"              // => "exist"
复制代码

??&&||一起使用时,需要用圆括号表明先计算哪部分,不然会报错。

条件属性访问(?.)

javascript中,nullundefined两个值上不存在属性。因此,通过普通的属性访问(a.ba[b])访问它们的属性会抛出TypeError。可以使用条件属性访问的方式(a?.ba?[b])避免这种错误。如:

let a;
a?.b        // => undefined
复制代码

对于a?.b,如果anullundefined,则该表达式值就为undefined,不会继续访问a.b这个属性;如果a不为nullundefined就会继续访问a.b。像a.b一样,它也可以链式访问。如:

let a = {b: null};
a?.b?.c?.d      // => undefined
复制代码

同理,a?[b]也是同样的用法,当anullundefined时,不会访问a[b]。自然而然的,b所代指的表达式也不会执行求值。

条件调用(?())

类似于条件属性访问,条件调用符号为?(),可解决a()anullundefined抛出的TypeError。如:

let a;
a?.comp()       // => undefined,
复制代码

aundefined,表达式值直接确定为undefined,不会真正的执行a.comp()。函数调用中左括号左操作数也必须为函数,因此,如例子中的a为函数之外的其他有意义变量,也会抛出TypeError。

?()也适用于方法调用,方法调用又包含了属性访问,因此,需要区分条件属性访问和条件方法调用。如:

o.m();      // 常规属性访问,常规方法调用
o?.m();     // 条件属性访问,常规方法调用
o.m?();     // 常规属性访问,条件方法调用
o?.m?();        // 条件属性访问,条件方法调用
复制代码

因此,第一个式中,o须为对象,且o.m为函数。第二个式中,o可为nullundefinedo为其他值时,o.m须为函数。第三个式中,o须为nullundefined之外的的值即可。第四个式中,o可为nullundefinedo为其他值时,o.m须为nullundefined或函数。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享