JavaScript中的操作符认识

216 阅读15分钟

ECMA-262描述了一组可用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等。ECMAScript中的操作符是独特的,因为它们可以用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用valueOf()和/或toString()方法来取得可以计算的值。
typeof()操作符返回的值可以是undefined、boolean、number、string、object(object、null)、symbol、functiontypeof()是一个操作符而不是函数,不需要传参(也可以使用参数)。严格来说,函数在ECMAScript中被认为是对象,并不代表一种数据类型,但是函数也有自己特殊的属性。为此,有必要通过typeof()操作符来区分函数和其他对象。
一元操作符只操作一个值的操作符叫一元操作符(unary operator)。一元操作符是ECMAScript中最简单的操作符。递增/递减操作符递增和递减操作符直接照搬自C语言。但有两个版本:前缀版和后缀版。无论使用前缀递增还是前缀递减操作符,变量的值都会在语句被求值之前改变。(在计算机科学中,这通常被称为具有副作用)前缀递增和递减在语句中的优先级是相等的,因此会从左到右依次求值递增和递减的后缀版语法一样,主要区别在于后缀版递增和递减在语句被求值之后才发生,这四个操作符可以用于任何值,意思是不限于整数——字符串、布尔值、浮点值,甚至对象都可以。递增和递减操作符遵循如下规则:对于字符串,如果是有效的数值形式,则转换为数值在应用改变,变量类型从字符串变成数值。如果不是有效的数值形式,则将变量变为NaN。变量类型从字符串变成数值。对于布尔值,若是false则转换为0。变量类型从布尔值变为数值。若是true则转换为1在应用改变,变量类型从布尔型转为数值对于浮点,加1或减1。对于对象,则调用valueOf()方法取得可以操作的值。对得到的值应用上述规则。若是NaN,则调用toString()方法并再次应用其他规则,变量类型从对象转换为数值。前缀和后缀最主要区别前缀版是先计算在赋值后缀版是先赋值再计算!一元加和减一元加和减操作符,在ECMAScript中跟高中数学中的用途一样。一元加由一个加号(+)表示,放在变量前,对数值没有任何影响。如果把一元加应用到非数值,则会执行与使用Number()转型函数一样的类型转换:布尔值false和true转为0和1,字符串根据特殊规则进行解析,对象会调用valueOf()或toString()方法。一元减由一个减号(-)表示,放在变量前面,主要用于把数值变成负值对数值使用一元减会将其变成相应的负值。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们转换,然后再取负值。一元加和减操作符主要用于基本的算数,但也可用于数据类型转换。
位操作符用于数值的底层操作,也就是操作内存中表示数据的比特位(位)。ECMAScript中的所有数值都以IEEE754 64位格式存储,但位操作并不直接应用到64位,而是先把值转换为32位整数,再进行位操作,之后再把结果转换为64位。对开发者而言,就好像只有32位整数一样,因为64位整数存储格式并不可见。有符号整数使用32位的前31位表示整数值。第32位表示数值的符号。0表示正,1表示负。这一位成为符号位(sign bit),它的值决定了数值其余部分的格式。正值以真正的二进制格式存储,即31位中的每一位都代表2的幂。第一位(成为第0位)2^0,第二位表示2^1....以此类推,如果一个位是空的,则以0填充,相当于忽略不计,比如数值18的二进制格式为 0000 0000 0000 0000 0000 0000 00010010或者更精简的10010,后者是用到的5个有效位,决定了实际的值。负值以一种成为二补数(或补码)的二进制编码存储,一个数值的二补数通过如下3个步骤计算得到:(1)、确定绝对值的二进制表示(2)、找到数值的一补数(或反码),换句话说,就是每个0都变成1,每个1变成0;(3)、给结果加1。要注意的是,在处理有符号整数时,无法访问第31位。ECMAScript会帮我们记录这些信息。在把负值输出为一个二进制字符串时,会得到一个前面加了减号的绝对值再将-18转换为二进制字符串时,结果得到-10010.转换过程会求得二补数,然后再以更符合逻辑的形式表示出来。默认情况下,ECMAScript中的所有整数都表示为有符号数。不过,确实存在无符号整数。对无符号整数来说,第32位不表示符号,因为只有正值。无符号整数比有符号整数的范围更大,因为符号位被用来表示数值了。在对ECMAScript中的数值应用位操作符时,后台会发生转换:64位数值会转换为32位数值,然后执行位操作,最后把结果从32位转换为64位存储起来。整个过程就像处理32位数值一样,这让二进制操作变得与其他语言中类似。但这个也导致了一个奇特的副作用,即特殊值NaN和Infinity在位操作中都会被当成0操作。如果将位操作符应用到非数值,首先会使用Number()函数将该值转为数值(这个过程是自动的),然后再应用到位操作,最终结果是数值。按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按位非是ECMAScript中为数不多的几个二进制数学操作符之一。按位非操作符应用到了数值18,得到的结果确实-19.由此可以看出按位非的最终效果是对数值取反并减1,如行5操作。实际上,尽管两者返回的结果是一样的,但位操作的速度快的多。这是因为位操作是在数值的底层表示上完成的。按位与操作符用和号(&)表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。按位或操作符用管道符( | )表示,同样有两个操作数,按位异或用脱字符( ^ )表示,同样有两个操作数。位异或只在一位上是1的时候返回1。左移左移操作符用两个小于号( << )表示,会按照指定的位数将数值的所有位向左移动。比如,数值2(二进制10)向左移5位,就会得到64(二进制1000000)注意在移位后,数值右端会空出5位。左移会以0填充这些空位,让结果是完整的32位数值。并且左移会保留它所操作数值的符号 有符号右移由两个大于号( >> )表示,会将数值的所有32位都向右移,同时保留符号(正或负)。有符号右移实际上是左移的逆运算。比如,如果将64右移5位,那就是2。移位后会出现空位。不过右移后空位会出现在左侧,且在符号位之后。ECMAScript会用符号位的值来填充这些空位,以得到完整的数值。 无符号右移\无符号位右移用3个大于号表示( >>> ),会将数值的所有32位都向右移。对于正数,无符号右移与有符号右移结果相同。对于负数,有时候差异会非常大。与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。对正数来说,这跟有符号右移效果相同。但对负数来说,结果就相差很多。无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。因为负数是其绝对值的二补数,所以右移之后结果变得非常大。- 布尔操作符一共有三个:逻辑非、逻辑与、逻辑或。

  • 逻辑非

    • 逻辑非操作符由一个感叹号( ! )表示,可应用给ECMAScript中的任何值。这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转为布尔值,然后再对齐取反。
    • 逻辑非操作符遵循如下规则:
    • 逻辑非操作符也可以用于把任意值转为布尔值,同时使用两个感叹号( !! ),相当于调用了转型函数Boolean()。
  • 逻辑与

    • 逻辑与操作符由两个和号( && )表示,应用到两个值。
    • 逻辑与操作符遵循如下真值表
    • 逻辑与操作符可用于任何类型的操作数,不限于布尔值。
    • 逻辑与操作符可用于任何类型的操作数,不限于布尔值。如果有操作数不是布尔值,则逻辑与并不一定返回布尔值,而是遵循如下规则:

      • 若第一个操作数是对象,则返回第二个操作数
      • 若第二个操作数对象,则返回第一个操作数求值为true才会返回该对象
      • 如果两个操作数都是对象,则返回第二个操作数
      • 如果一个操作数是null,则返回null
      • 如果有一个操作数是undefined,则返回undefined
    • 逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。在使用逻辑与操作符时,一定不要忘了它的短路的特性。
  • 逻辑或

    • 逻辑或操作符由两个管道符( || )表示
    • 逻辑或操作符遵循如下真值表:
    • 与逻辑与类似,如果有一个值不是布尔值,那么逻辑与操作符不一定返回布尔值。它遵循如下规则:

      • 若是第一个操作数是对象,则返回第一个操作数
      • 若是第一个操作数求值为false,则返回第二个操作数
      • 若是两个操作数都是对象,则返回第一个对象
      • 若是两个数都是null,则返回null
      • 若是两个数都是undefiend,则返回undefiend
      • 如是两个数都是NaN,则返回NaN
    • 同样与逻辑与类似,逻辑或操作符也具有短路的特性。只不过对于逻辑或而言,第一个操作数求值是true,第二个操作数就不会在被求值了。
  • 指数操作符

    • ECMAScript7新增了指数操作符,Math.pow()现在有了自己的操作符**,结果是一样的

    • 指数操作符也有自己的指数赋值操作符 **=,该操作符执行指数运算和结果的赋值操作

  • 加性操作符即是加法和减法操作符,一般都是编程语言中最简单的操作符。但在ECMAScript中,这两个操作符拥有一些特殊的行为。与乘性操作符类似,加性操作符在后台会发生不同数据类型的转换。只不过对这两个操作符来说,转换规则不是那么直观。

  • 加法操作符

    • 加法操作符( + )用于求两个数的和
    • 加法操作符执行加法运算一些特殊的转换规则:

      • 若是有任一操作数是NaN,则返回NaN
      • 若是Infinity加Infinity,则返回Infinity
      • 若是-Infinity加-Infinity,则返回-Infinity
      • 若是Infinity 加上-Infinity,则返回NaN
      • 若是+0加上-0,则返回+0
      • 若是+0加上+0,则返回+0
      • 若是-0加上-0,则返回-0
      • 若是两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面
      • 若是只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起,若是有任一操作数是对象、数值、布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串规则。
  • 减法操作符

    • 减法操作符也有一组规用于处理ECMAScript中不同类型之间的转换:

      • 若是infinity 减infinity,则返回NaN
      • 若是-infinity 减去 -infinity,则返回NaN
      • 若是infinity 减 -infinity,怎返回infinity
      • 若是-infinity减去 infinity,则返回-infinity
      • 若是+0减+0,返回+0
      • 若是+0减-0,返回-0
      • 若是-0减-0,返回+0
      • 若是有任一操作符是字符串、布尔值、null和undefined,则先在后台调用Number()将其转为数值,然后再根据前面的规则执行数学运算,若是转换结果是NaN,则减法计算的结果是NaN
      • 若是任一操作符是对象,则调用其valueOf()方法取得它的数值。若是该值是NAN,则减法计算结果为NAN,若是对象没有ValueOf()方法,则调用toString()方法,再将其得到的字符串转为数值。
  • 关系操作符

    • 关系操作符执行比较两个值得操作,包括小于、大于、小于等于、大于等于,用法跟数学课上学的一样。这几个操作符返回的是布尔值,如下所示:
    • 与ECMAscript中的其他操作符一样,再将它们应用到不同数据类型时也会发生类型转换和其他行为:

      • 若是操作数都是数值,则执行数值比较
      • 若是操作数都是字符串,则逐个比较字符串对应字符的编码
      • 若是有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较
      • 若是有任一操作数是对象,则调用valueOf()方法,取得结果后再根据前面的规则执行比较,若是没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则执行比较
      • 任何关系操作符在涉及比较NaN时都会返回false
      • 若是任一操作符时布尔值,则将其转为数值再比较
        • 在使用关系操作符比较两个字符串时,对于字符串而言,关系操作符会比较字符串中对应字符的编码,而这些编码是数值。比较完之后,会返回布尔值。问题的关键在于,大写的字母的编码都小于小写字母的编码。要是为了得到确实按照字母顺序比较的结果,就必须把两者转换为相同的大小写形式(全大写或是全小写),然后再比较(toLowerCase()方法 toUpperCase()方法).
  • 判断两个变量是否相等是编程中最重要的操作之一。

  • ECMAScript提供了两组操作符。第一组是等于和不等于,它们在比较之前执行转换。第二组是全等和不全等,它们在比较之前不执行转换。

    • 等于和不等于
      • ECMAScript中的等于操作符用两个等号( == )表示,不等于是用( != )表示,返回值是布尔型。这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。
      • 在转换操作数的类型时,相等和不相等操作符是否相等
        • 若是任一操作数是布尔值,则将其转换为数值再进行比较是否相等。false转为0,true转为1
        • 若是任一操作数字符串,另一个操作数是数值,则尝试将字符串转为数值,再比较是否相等
        • 若是任一操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面的规则进行比较。
        • 再进行比较时,这两个操作符会遵循如下规则
          • null和undefined相等
          • null和undefined不能转为其他类型的值再进行比较
        • 若是任一操作数是NaN,则相等操作符返回false,不相等操作符返回true
    • 全等和不全等
      • 全等操作符由( === )表示,不全等由( !== )表示
      • 不全等操作符,只有在两个操作数在不转换的前提下不相等才返回true
  • 条件操作符(三元操作符)

    • var variable = boolean_expression ? true_value : false_value;
  • 赋值操作符

    • 简单赋值用等于号( = )表示,将右手边的值赋给左边。
    • 复合赋值使用乘性、加性或位操作后跟等于号( = )表示
    • 每个数学操作符以及其他一些操作符都有对应的复合赋值操作符:
      • 乘后赋值 *=
      • 除后赋值 /=
      • 取模后赋值 %=
      • 加后赋值 + =
      • 减后赋值 -=
      • 左移后赋值 <<=
      • 右移后赋值>>=
      • 无符号右移后赋值>>>=
      • 这些操作符仅仅是简单写法,使用它们后不会提升性能
  • 逗号操作符

    • 逗号操作符可以用来在一条语句中执行多个操作, let num1 = 1, num2 = 2, num3 = 3;
    • 在一条语句中同时声明多个变量是逗号操作符最常用的场景。
    • 也可以使用逗号操作符来辅助赋值,在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值 let num = (3,2,1,0);num的值为0