运算符

最后更新于:2022-04-01 23:53:12

## 运算符 ECMA-262描述了一组用于操作数据值的运算符,包括算术运算符、位运算符、关系运算符和相等运算符。 **1.1 一元运算符** 只能操作一个值的运算符叫做一元运算符。 **1.递增和递减** 递增(++)和递减(--)都有两种使用方式:**前置型**和**后置型**。 **前置型**指的是运算符放在要操作的变量之前,而**后置型**则是运算符放在要操作的变量之后。 ``` var age = 10; var age2 = 10; ++age; --age; console.log(age); // 11 console.log(age2); //9 ``` 执行前置递增和递减操作时,变量的值都是在语句被求值以前改变的(也就是先自增或自减,再和其他值进行操作): ``` var num = 2; var age = ++num + 2; console.log(age); // 5 ``` 在上面的例子中,在与2相加之前,num已经加1变成了2。 后置型递增和递减运算符不变(依然是++和--),不过除了要放到变量之后外,包含它们的语句是被求值之后才执行(先和其他值进行操作后,再自增或自减): ``` var num = 2; var age = (num++) + 2; console.log(age); // 4 console.log(num); // 3 ``` 注意:由于JavaScript会自动进行分号补全,因此不能在后增量运算符和操作数之间插入换行符 ``` num ++; ``` 这会报错 这4个操作符对任何值都适用,不过,当应用于不同的值时,递减和递增操作符会遵循下列规则: - 当操作数是一个包含有效数字字符的字符串,系统会将其转换为数字值,再执行递减或递增。 - 当操作数是一个不包含有效数字字符的字符串,系统将变量的值设置为NaN - 当操作数是布尔值,会将其转为数值(true转为1,false转为0)再操作。 - 当操作数是浮点数值,直接执行递减或递增 - 当操作数是对象,先调用对象的valueOf()方法取得一个可供操作的值,然后再遵循上面的三条规则。如果结果是NaN,则在调用toString()方法后再遵循上面的规则转换。 ``` var a = '2'; var b = 'a'; var c = false; var d = 1.1; var o = { valueOf: function() { return -1; } }; a++; // 3 b++; // NaN c--; // -1 d--; // 0.10000000000000009 (浮点数操作结果,类似0.1+0.2 != 0.3) o--; -2 ``` **2.一元加和减运算符** 一元加(+)和减(-)运算符和数学上的含义一样,表示负正。 不过,对非数值应用一元加运算符时,该运算符就像Number()转型函数一样对这个值执行转换。(转换规则参考数据类型那一节中的Number()的转换规则) 一元减运算符对数值是表示负数,而对于非数值,转换规则和一元加(也就是和Number())一样,然后将转换后的数值转为负数。 **1.2 位运算符** 位运算符是按内存中表示数值的位来操作数值。 ECMAScript中的数值都以IEEE-75464位格式存储,但位运算符并不直接操作64位的值,而是先将64位的值转换为32位,然后执行操作,最后将结果转换回64位。 对于有符号的整数,32位(从右往左,最右边为第一位)中的前31位用于表示整数的值,第32位用于表示数值的符号:0表示正数,1表示负数。这个表示符号的位叫做符号位。符号位的值决定了其他位数值的格式。 正数以纯二进制格式存储,31位中的每一位(从右往左)都表示2的幂。第一位(叫做位0)表示2^0,第二位表示2^1,以此类推。没有用到的位以0填充,可以忽略。 ``` 18 二进制表示法:10010 2^4 × 1 + 2^3 × 0 + 2^2 × 0 + 2^1 × 1 + 2^0 × 0 = 18 ``` 负数同样以二进制码存储,但使用的格式是二进制补码。 如何计算一个数值的二进制补码?需要3个步骤: * 求这个数值绝对值的二进制码 * 求二进制反码,即0替换1,将1替换0 * 得到二进制反码加1 ``` -18 //第一步,求18的二进制码 0000 0000 0000 0000 0000 0000 0001 0010 // 求反码,后加1 1111 1111 1111 1111 1111 1111 1110 1101 1 1111 1111 1111 1111 1111 1111 1110 1110 // -18的二进制表示 ``` 如果对非数值应用位运算符,会先使用Number()函数将该值转换为一个数值(自动转换),再进行位运算符操作,最后得到一个数值 当你去求负数的二进制时,ECMAScript会尽力向我们隐藏所有这些信息。比如: ``` var num = -18; console.log(num.toString(2)); // "-10010" ``` 上面的代码对负数求二进制码,得到的结果只是这个负数绝对值的二进制码前面加上一个负号。 **(1)按位非(NOT)** 按位非运算符(~)进行`否运算`,执行按位非的结果就是返回数值的反码。 ``` ~ 3 // -4 ``` 上面的表达式对3进行否运算,得到-4。 ``` //3的二进制码 00000000000000000000000000000011 // 取反码 11111111111111111111111111111100 //补码转为十进制的规则,在下面可以看到 11111111111111111111111111111011 //减1 00000000000000000000000000000100 //再次取反得到~3的十进制,也就是4,加上负号,就是-4 ``` 负数二进制码(补码)转换为十进制的规则:需要将这个数减去1,再取一次反,然后加上负号,才能得到这个负数对应的10进制值。 看起来是不是眼花缭乱,其实我们可以不用管里面的转换逻辑,只需记得按位非(否运算)就是**一个数与自身的取反值相加,等于-1**。 ``` ~3 + 3 = -1 => -1 - 3 = -4 == ~3 ``` **2.按位与(AND)** 使用按位与(&)运算符来进行`与运算`,它有两个运算符数。 ``` 0 & 3 //0 0和1的二进制是00和11 00 11 00 ``` 运算规则是只有两个数值的对应位都是1时才返回1,其他情况返回0。 **按位或(OR)** 使用按位或(|)运算符进行`或运算`. ``` 0 | 3 // 3 00 11 11 ``` 或运算的规则是只要有一个位是1就返回1,只有两个位都是0时才返回0 位运算只对整数有效,遇到小数时,会将小数部分舍去,只保留整数部分。所以,将一个小数与0进行或运算,等同于对该数去除小数部分,即取整数位。 **4.按位异或(XOR)** 使用按位异或(^)来进行`异或运算`。 ``` 0 ^ 3 // 3 00 11 11 ``` 异或运算的规则是两个数值的对应位上只有一个1时才返回1,其他情况返回0. **5.左移** 左移运算符(<<),这个运算符会将所有位向左移动指定的位数,尾部补0。 ``` 2 << 5 // 64 2的二进制码 10 1000000 ``` 左移可看做是数值乘以2的指定次方 注意:左移不会影响操作数的符号位,也就是说,-2向左移动5位后,是-64 **6.右移** **(1)有符号的右移** 有符号的右移(>>)将数值向右移动,但保留符号位(即正负号标记,也就是第32位) ``` 64 >> 5 // 2 1000000 10 -64 >> 5 //-2 ``` 有符号的右移可看做是数值除以2的指定次方 **(2)无符号的右移** 无符号的右移(>>>),这个运算符会将数值的所有32位向右移动。对正数来说,无符号右移的结果与有符号右移相同: ``` 64 >>> 5 //2 ``` 但负数就不一样了,无符号右移是以0来填充空位的,所以无符号右移得到的结果都是正数。 ``` -64 >>> 5 // 134217726 ``` 有一点需要特别注意,位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。 **1.3 布尔运算符** 布尔运算符是用来比较两个值的关系的。 **1. 逻辑非(or)** 逻辑非(!)可以应用于JavaScript中的任何值,最终都会返回一个布尔值。 逻辑非运算符首先会将它的操作数转换为一个布尔值,然后再取反。 **逻辑非规则**: * 如果操作数是一个对象、非空字符串、任意非0数值(包括Infinity),则返回false * 如果操作数是空字符串、数值0、null、undefined、NaN,则返回true 如果同时使用两个(!!),就会像使用Boolean()转型函数一样的转换逻辑。 ``` !false // true !'tg' // false !'' // true !!0 //false ``` **2.逻辑与** 逻辑与(&&)有两个操作数,如果是布尔值,只有两个都是true时,才会返回true,否则返回false ``` var isTrue = true && false; //false ``` 如果不是布尔值,它遵循下面的规则: * 如果第一个操作数是对象,则返回第二个操作数 * 如果第二个操作数是对象,则只有在第一个操作数的求值为true时才会返回第二个操作数 * 如果有一个操作数是null,则返回null * 如果有一个操作数是NaN,则返回NaN * 如果有一个操作数是undefined,则返回undefined 逻辑与操作符也就是先将第一个操作数转换为Boolean类型判断是true或false,再根据结果决定是否执行第二个操作数 ``` 0 && 'tg' ; // 0 {} && 'tg'; // "tg" ``` 逻辑与操作属于短路操作,也就是说如果第一个操作数能够决定结果(等于false时),就不会再对第二个操作数求值。 **3.逻辑或** 逻辑或(||)也有两个操作数。 如果两个操作数都是布尔值,则有一个为true时,就返回true,否则返回false。 如果有一个操作数不是布尔值,则遵循下列规则: * 如果第一个操作数是对象,则返回第一个操作数 * 如果第一个操作数的求值结果是false,则返回第二个操作数 * 如果两个操作数都是对象,则返回第一个操作数 * 如果两个操作数都是null,则返回null * 如果两个操作数都是NaN,则返回NaN * 如果两个操作数都是undefined,则返回undefined 逻辑或也是短路运算符,也就是说,如果第一个操作数的求值结果为true,就不会对第二个操作数求值了。 ``` true || 'tg'; // true 0 || 'tg'; // "tg" ``` 一般情况下,我们可以使用逻辑或来避免变量赋null或undefined值: ``` function test(name){ name = name || 'tg'; console.log(name); } test(); // "tg" test('tg2'); // "tg2" ``` 上面的例子,表示当调用test()方法不传参时,name赋予一个默认值"tg";如果带有参数,则使用参数值。 注意:逻辑与(&&)和逻辑或(||)返回的都是运算值。 **1.4 乘性运算符** ECMAScript定义了3个乘性运算符:乘法、除法和求模。 当操作数是非数值时,会执行自动的类型转换。如果操作数不是数值,会先使用Number()转型函数将其转换为数值(后台自动),再进行运算。 **1.乘法** 乘法运算符(`*`),用于计算两个数值的乘积。 处理特殊值时,乘法运算符会遵循下列规则: * 如果操作数都是数值,但乘积超过了ECMAScript数值范围,则返回Infinity或-Infinity * 如果有一个操作数是NaN,结果是NaN * 如果是Infinity乘以0,结果是NaN * 如果是Infinity与非0数值相乘,结果是Infinity或-Infinity,取决于非0数值的符号 * 如果是Infinity与Infinity相乘,结果是Infinity * 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后遵循上面的规则 ``` console.log(1 * NaN); // NaN console.log( Infinity * 2); // Infinity console.log(Infinity * 0); // NaN console.log(Infinity * Infinity); // Infinity ``` **2.除法** 除法运算符(/),执行第二个操作数除第一个操作数计算。 处理特殊值,规则如下: * 如果操作数都是数值,但商超过了ECMAScript的表示范围,则返回Infinity或-Infinity * 如果有一个操作数是NaN,结果是NaN * 如果是Infinity被Infinity除,结果是NaN * 如果是零被零除,结果是NaN * 如果是非零的有限数被零除,结果是Infinity或-Infinity,取决于有符号的操作数 * 如果是Infinity被任何非零数值除,结果是Infinity或-Infinity * 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后遵循上面的规则。 ``` console.log(NaN / 1); // NaN console.log(0 / 0); // NaN console.log(1 / 0); // Infinity console.log(2 / Infinity); // 0 console.log(Infinity / Infinity); // NaN console.log(Infinity / 2); // Infinity ``` **3.求模** 求模(余数)运算符(%) 处理特殊值,规则如下: * 如果被除数是无穷大值而除数是有限大的数值,结果是NaN * 如果被除数是有限大的数值而除数是零,结果是NaN * 如果是Infinity被Infinity除,结果是NaN * 如果被除数是有限大的数值而除数是无穷大的数值,结果是被除数 * 如果被除数是零,结果是零 * 如果有一个操作数不是数值,则在后台调用Number()将其转换为数值,然后遵循上面的规则。 ``` console.log(5 % 3); // 2 ``` **1.5加性运算符** 加性运算符也会在后台转换不同的数据类型。 **1.加法** 如果两个操作数都是数值,执行常规的加法计算,然后根据下面的规则返回结果: * 如果有一个操作数是NaN,结果是NaN * 如果Infinity加Infinity,结果是Infinity * 如果是-Infinity加-Infinity,结果是-Infinity * 如果是Infinity加-Infinity,结果是NaN * 如果是+0加+0,结果是+0 * 如果是-0加-0,结果是-0 * 如果是+0加-0,结果是+0 如果有一个操作数是字符串,则遵循下列规则: * 如果两个操作数都是字符串,则拼接两个字符串 * 如果只有一个操作数是字符串,则将另一操作数转换为字符串,然后拼接 * 如果有一个操作数是对象、数值或布尔值,则调用它们的toString()方法取得相应的字符串值,然后遵循上面的规则。对于undefined和null,则分别调用String()函数并取得字符串"undefined"和"null"。 ``` 5 + 5 //10 5 + '5" // "55" ``` 如果你要在有字符串操作数的加法中执行常规的加法操作,应该使用圆括号将要相加的数值括起来: ``` 'tg' + 5 + 5 // "tg55" 'tg" + (5 + 5) // "tg10" ``` **2.减法** 减法运算符(-) 对于特殊值,减法操作会遵循下列规则: * 如果有一个操作数是NaN,结果是NaN * 如果Infinity减Infinity,结果是NaN * 如果是-Infinity减-Infinity,结果是NaN * 如果是Infinity减-Infinity,结果是Infinity * 如果是-Infinity减Infinity,结果是-Infinity * 如果是+0减+0,结果是+0 * 如果是-0加-0,结果是+0 * 如果是+0减-0,结果是-0 * 如果有一个操作数是字符串、布尔值、null或undefined,则先在后台调用Number()将其转换为数值,然后遵循上面的规则进行计算。 * 如果有一个操作数是对象,则调用对象的valueOf()方法以取得表示该对象的数值;如果该对象没有valueOf()方法,则调用其toString()方法将得到的字符串转换为数值,然后遵循上面的规则进行计算。 ``` 5 - true; // 4 (true转换成1) 5 - '2' // 3 5 - null; // 5(null转换成0) ``` **1.6 关系运算符** 小于(<)、大于(>)、小于等于(<=)和大于等于(>=)四个关系运算符用来对两个值进行比较,最后返回一个布尔值。 当使用了非数值时,则会遵循下列规则: * 如果两个操作数是字符串,则比较两个字符串对应的字符编码值 * 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后进行比较 * 如果一个操作数是对象,则调用这个对象的valueOf()方法,用得到的结果按照前面的规则比较。如果对象没有valueOf()方法,则调用toString()方法,并用得到的结果按照上面的规则进行比较。 * 如果一个操作数是布尔值,则先将其转换为数值,然后进行比较。 **注意**:在使用关系运算符比较两个字符串时,比较的是两个字符串中对应位置的每个字符的字符编码值(从左往右),经过逐个比较后,直到有胜负(也就是不相等时),再返还布尔值。 ``` 'abc' > 'abd' //false 'A' < 'a' // true '23' < '3' // true ``` 在上面的例子中,第一行代码会先比较"a"和"a",然后是"b"和"b",最后是"c"和"d",由于c的字符编码值是63,d的字符编码值是64,所以返还false。 由于小写字母的字符编码值总是大于大写字母的字符编码值,所以第二行代码返还true。 第三行代码也是字符串比较,所以比较的是字符编码值(2是50,3是51),所以返回true 注意:任何操作数与NaN比较,都会返回false。 **1.7 相等运算符** 相等运算符有两组:相等和不相等(先转换再比较)、全等和不全等(只比较不转换) **1.相等和不相等** 相等(==),不相等(!=) 这两个运算符都会先转换操作数(强制转换),然后比较。 对于不同的数据类型,转换规则如下: * 如果有一个操作数是布尔值,则在比较前先将其转换为数值(false转为0,true转为1) * 如果一个操作数是字符串,另一个操作数是数值,先将字符串转为数值 * 如果一个操作数是对象,另一个不是,先调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较 * null和undefined是相等的 要比较之前,不能将null和undefined转换成其他任何值 * 如果有一个操作数是NaN,则相等运算符返回false,不相等运算符返回true。(要记住NaN不等于本身原则) * 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等运算符返回true,否则返回false。 ``` NaN == NaN // false true == 1 // true null == undefined // true ``` **2.全等和不全等** 除了在比较之前不转换操作数类型之外,全等和不全等与相等和不相等没什么区别。 ``` '55' == 55 // true '55' === 55 // false ``` **1.8 条件运算符** 条件运算符是ECMAScript中唯一的三元运算符。 ``` variable = boolean_expression ? true_value : false_value; ``` 如果boolean_expression返回true,则执行true_value,否则,执行false_value ``` var name = (1 > 2) ? 'tg' : 'tg2'; // "tg2" ``` 上面的代码中1小于2,所以是false,则将"tg2"赋值给name。 **1.9 赋值运算符** (=)是最简单的赋值操作,其作用是把右侧的值赋给左侧变量。 ``` var name = 'tg'; ``` 复合赋值运算符 ``` 乘赋值(x *= y) 等同于 x = x * y 除赋值(x /= y) 等同于 x = x / y 模赋值(x %= y) 等同于 x = x % y 加赋值(x += y) 等同于 x = x + y 减赋值(x -= y) 等同于 x = x - y 左移赋值(x <<= y) 等同于 x = x << y 有符号右移赋值(x >>= y) 等同于 x = x >> y 无符号右移赋值(x >>>= y) 等同于 x = x >>> y ``` 例子: ``` var a = 1; a += 1; console.log(a); // 2 ``` **1.10 逗号运算符** 使用逗号运算符可以在一条语句中执行多个操作: ``` var name = 'tg', age = 1; ``` 逗号运算符多用于声明多个变量。 逗号运算符还可以用于赋值。在用于赋值时,逗号运算符总会返回表达式中的最后一项: ``` var num = (1,5,3); // num的值为3 ``` 3是表达式中的最后一项,因此num的值就是3. **1.11 in运算符** in运算符希望它的左操作数是一个字符串或可以转换成为字符串,希望它的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数值的属性名,则返回true。 ``` var o = {x:1}; "x" in o //true ``` **1.12 instanceof运算符** `instanceof`运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回true。 ``` var a = new Array(); a instanceof Object; //true ``` 注意:所有对象都是Object的实例 **1.13 typeof运算符** `typeof`是一元运算符,用来判断数据类型: ``` typeof 1 // "number" ``` **1.14 delete运算符** `delete`是一元运算符,它用来删除对象的属性或数组元素。 ``` var o={x:1} delete o.x; "x" in o //false ``` **1.15 void运算符** `void`是一元运算符,它出现在操作数之前,操作数可以是任意类型。操作数会照常计算,但忽略计算结果并返回undefined。 ``` void 0 //undefined void(0) //undefined var a = 1; void (a=2); a //2 ``` 这个运算符主要是用于书签工具(bookmarklet),以及用于在超级链接中插入代码,目的是返回undefined可以防止网页跳转。 ``` ``` **2. 运算顺序** **2.1 优先级** JavaScript各种运算符的优先级别(Operator Precedence)是不一样的。优先级高的运算符先执行,优先级低的运算符后执行。 **2.2 圆括号** 圆括号(())可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的表达式会第一个运算。 注意:因为圆括号不是运算符,所以不具有求值作用,只改变运算的优先级。 对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的“左结合”(left-to-right associativity),即从左边开始计算。 但是少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的“右结合”(right-to-left associativity)。其中,最主要的是赋值运算符(=)和三元条件运算符(?:)。
';