js之运算符

358 阅读15分钟

 在Javascript中运算符大多由标点符号少数由关键字表示,可以根据其操作数个数进行分类。大多数运算符是一个二元运算符,将两个表达式合成一个比较复杂的表达式。需要注意运算符的优先级,它控制着运算符执行顺序,优先级高的运算符总是先执行。大致可以分为算术运算符,逻辑运算符,位运算符等等。下面来分别介绍。

一、算术运算符

算术运算符包括一元算术运算符和二元算术运算符两种。

1、一元运算符

一元运算符包括:一元加法(+)、一元减法(-)、递增(++)和递减(--)。它只对一个表达式执行操作,并产生一个新值。   

+/-:一元加减运算符  

  一元加由一个加号(+)表示,放在变量前面没有任何影响。但如果将其应用到非数值,则会执行Number()函数进行类型转换。布尔值转换为1或0,字符串根据规则来解析,对象则会调用它们的valueof()/toString()方法得到可以转换的值。

  一元减由一个减号(-)表示,放在变量前面主要用于把数值变为负值。在应用到非数值时,一元减也会遵循与一元加一样的规则先转换再取负值。

let b = '2.2';
let c = false;
let d = NaN;
let e = undefined;
let f = {
    valueOf(){ return 8; }
};
b = +b;
c = +c;
d = -d;
e = -e;
f = -f
console.log(b,c,d,e,f);//2.2 0 NaN NaN -8

++/--:递增和递减操作符

  递增和递减有两个版本即前缀版和后缀版。前缀版就是位于要操作变量前面,后缀版就是位于要操作变量后面。

  • 如果只操作自己:

    • ++放在前面还是后面是一样,让自己加1;

    • --放在前面或者后面是一样,让自己减1;

  • 如果是操作别人:(赋值给其它变量):

    • ++/--放在后面,变量首先进行操作,再自身进行加/减。

    • ++/--放在前面,变量自身先加/减,然后再进行操作。

不管是前增还是后增,这个运算符通常用于for循环控制循环内的计数器。

let age = 20;
++age;
console.log(age);//21
let num1 = 4;
let num2 = num1++;
let num3 = ++num1;
console.log(num1,num2,num3);//6  4  6
console.log(num3,num2);
let num5 = num3-- + --num2; 
console.log(num5); //9

2、二元算术运算符

二元运算符包括 +、-、*、/、%(求模)

加法(+)

    在多数程序设计语言中,加法通常是简单的数字运算,但在Javascript中,加法还可以进行字符串拼接(这其中涉及到隐式类型转换)。

let num1 = 0 + 0;
let num2 = 0 + '0';
console.log(num1,num2); //0 '00'

减法(-)

    减法只涉及到数字减法运算,使用Number()函数将非数值类型转为数值或者是NaN。

console.log(1 - {}); //NaN
console.log(1 - []); // 1
console.log(1 - [1,2]); //NaN
console.log(1 - undefined); //NaN

乖法(*)

    用于计算两个数值乖积,会通过Numbe()方法将非数值转换成数值或者是NaN。

console.log(1 * {}); //NaN
console.log(1 * []); // 0
console.log(1 * [1,2]); //NaN
console.log(1 * undefined); //NaN

除法(/):

    执行第一个操作数除以第二个操作数,通过Number()转型函数将非数值类型转换为数值或NaN。

console.log(1 / {}); //NaN
console.log(1 / []); // Infinity
console.log(1 / [1,2]); //NaN
console.log(1 / undefined); //NaN

求模(%)

取模时候果前面数字比后面小,那得到的值就是前面数字,求模结果与第一个操作数的符号保持一致,前面的字为infinity,后面数字为0时,结果为NaN。

console.log(3 % 5); //3
console.log(-3 % 5); //-2
// 求一个数的个位 十位 百位 千位
const num = 4780.4;
let a = parseInt(num % 10); //个位
let b = parseInt((num % 100) / 10); //十位
let c = parseInt((num % 1000) / 100); //百位
let d = parseInt((num % 10000) / 1000); //千位
console.log(a, b, c, d); // 0 8 7 4


二、比较运算符

    关系运算符用于测试两个值的关系,根据关系是否存在返回true或者是false。关系表达式总是返回一个布尔值。

    具有如下8个关系运算符:大于(>),小于(<),小于等于(<=),大于等于(>=),相等(==),不等(!=),恒等(===),不全等(!==)     其中需要注意的是:

  • “===”:恒等运算符,也称是严格相等运算符,只有在无需类型转换运算数就相等的情况下才为true。
console.log('2' === 2);           //false  二者的类型不同
console.log(undefined === undefined);   //true 二者类型与值都相同
console.log(NaN === NaN);        //false  NaN和任何值都不相等包括NaN
console.log([] === []);         //false 对象的相等必须是引用的相等才可以
console.log(10 === 0xa);         //true 转换成十进制进行比较

  • "==":相等运算符它和“===”恒等运算符相类似,但是没有那么严格,如果两个操作数不是同一类型,它会进行转换后再进行比较:
    • 当操作数类型一样,比较规则和恒等运算符一样,都相等才相等;
    • 当操作数类型不一样时,会按如下规则来判定:
      • 如果一个值是对象类型,另一个是原始类型,则对象类型会通过valueOf()方法进行转换,转换成原始值,如转换的不是原始值,则通过toString()方法转换再进行比较;
      • 在对象转换为原始值后,如果两个操作数是字符串,则进行字符串比较,如果里面有一个操作符不是字符串,那两个操作数通过Number()方法进行转换,转成数字进行比较。
        console.log('abc' == 'abc');    //true    类型相等,值也相等,当然相等
        console.log(NaN == NaN);      //false   NaN和任何者不等
        console.log([] == []);        //false    
        console.log(10 == 0xa);       //true    转换成十进制进行比较是否相等
        console.log(true == 0);       //false   true转换成数字为1
        console.log(true == '1');     //true 
        console.log(null == undefined); // true   null和undefined是相等
        console.log(0 == null);      // false  0和null不等
        console.log('' == false);    //true    空字符串转成0和false相等
  •  "=": 可以看成是得到或者赋值,把等号右边的值赋给等号左边的变量或属性。 如果一个表达式中出现了多个赋值运算符,运算顺序是从右到左。

  •  “!==”:恒不等运算符,又称严格不等运算符。操作数比较过程与恒等运算符相同,结果取反就可。

  •  “!=”:不相等运算符。它的操作数比较过程和与相等运算符相同,结果取反即可。

注意:undefined只与null相等,与其它任何值相比较都不相等。字符串相对比,比较的是字符对应的ASCII码。

    console.log([] !== []);     //true
    console.log(1 !== true);    //true
    console.log(NaN != '');     //true
    console.log(undefined == null); //true

三、赋值运算符

    赋值运算符有:=、+=、-=、*=、/=、%=。先进行指定运算,然后把右边得到的值赋值给右边。

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;

四、逻辑运算符

    逻辑运算符通常用于布尔型(逻辑)值。这种情况下,它们返回一个布尔值。它经常和关系运算符一起配合使用。

1、逻辑或(||)

    有两个操作数,只有两个条件都不成立时,结果才是不成立,否则都是成立。它是一种短路操作。可多个连用,返回第一个布尔值为true表达式的值,常用于为变量设置默认值。左侧为真,返回左侧值,左侧为假,返回右侧值。

//左侧为真,返回左侧值,左侧为假,返回右侧值
console.log("string" || ""); //string
console.log(undefined || 2); //2
console.log("" || undefined || false || 3 || string); //3

let n = 1,m = 1,
result = true || ++n, //true为真,不执行后面操作
result = false || --m; //false为假,执行行后面操作
console.log(n, m); //1 0

//如果没有向参数a中传入值,则将参数设置为空对象
 function fn(a) {
   a = a || {}
 };

2、逻辑与(&&)

    逻辑与是由两个和号(&)表示,有两个操作位数,只有当左右两边都时成立,整个条件才成立.否则就是不成立。它属于一个短路操作,如果第一个条件就可决定结果,那就不用操作第二个条件了。它可以和多个运算符连用,返回第一个布尔值为false的表达式。可以取代if结构,也可以用于回调函数中。

let i = 1 , n = 1;
result1 = (true && i++);//第一个条件成立,操作第二个条件
resutl2 = (false && ++n);//第一个条件不成立,直接退出
console.log(i,n);  //2 1 
//左侧为真,返回右侧的值,左侧为假,返回左侧的值 
//false,undefined,null,0,NaN,'' 这些为假其余为真;
//可多个连用,返回第一个布尔值为false的表达式的值
console.log('string' && '');  //''
console.log('' && undefined);//''
console.log(NaN && false);//NaN
console.log(true && 'string' && undefined && '' && 2); //undefined       
if (a == b) {dosomething()}//等价于
a == b && dosomething();

function fn(a){if(a){a()}}//等价于
function fn(a){a && a()};

3、逻辑非(!)

    逻辑非是一个叹号,无论这个值是什么数据类型,使用这个操作符后都会返回一个布尔值。逻辑非操作符会先将它的操作数转换成一个布尔值然后再对其求反。如果同时用两个逻辑非的话那就相当于调用Boolean()方法,负负得正。一般用于控制循环。

console.log(!!NaN); //false
console.log(!![]);  //true
console.log(!! new Boolean(false)); //true

五、条件运算符

    三目运算符也叫条件运算符它是Javascript中唯一的三个操作数运算符

  • 基本格式:condition ? expr1 : expr2

  • 执行流程:condition为true,返回expr1,conditioin为false,返回expr2

    三元运算符的操作数可以是任意类型,其实用if语句也会带来同样效果,三元运算符只是提供了一种简写形式。

    事实上,三元运算符可以扩展使用,当设置的条件成立或者是不成立时,执行语句都可以不止一句,如下:condition?(expr1).(expr2) : (expr3).(expr4) 多个执行语句用"."连接,这样它的功能更像if...else流程语句上靠近了。

    同时它还可以嵌套使用,但嵌套使用可读性不好,不利于后期维护。

         //求a,b,c三值中的最大值
        let a = 10, b = 12, c = 19; //要注意a,b,c类型相同
        let max1 = (a > b) ? a : b;
        let max2 = (max1 > c) ? max1 : c;
        console.log(max2); //19

        let max = ((a > b) ? a : b) > c ? ((a > b) ? a : b) : c;
        console.log(max); //19

        if (a > b && a > c) {
            max = a;
        } else if (b > a && b > c) {
            max = b;
        } else {
            max = c;
        }
        console.log(max);//19

六、位运算符

    位运算在数字底层(表示数字的32个数位)进行运算。由于位运算是低级运算操作,所以速度往往也是最快的,但是它很不直观,许多场合不能够使用。大多数语言都提供了按位运算符,恰当的使用按位运算符有时候会取得很好效果。

    位运算只对整数起作用,如果一个运算不是整数,会自动转为整数后再运行。虽然在Javascript内部,数值都是以64位浮点数形式储存,但是位运算是以32位带符号的整数进行运算,并且返回值也是一个32位带符号整数。

    这种位转换使得在对NaN和infinity这种特殊值应用位操作时,这两个值会被当成0来处理。如果是对非数值应用位操作符会先使用Number()方法将值转换成数值再应用位操作,得到的结果是一个数值。

    二进制:ECMAScript整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在ECMAScript中,所有整数字面量默认都是有符号整数。

    有符号整数使用31位表示整数的数值,用第32位表示整数的符号,0表示正数,1表示负数。数值范围从-2147483648 到 2147483647。表示符号的位叫做符号位,符号位的值决定了其它位数值的格式。其中,正数以纯二进制格式存储,31位中的每一位都表示2的幂,第一位(叫做位0)表示2的1次以此类推,没有用到的位以0填充,可忽略不计。

如:10

image.png 10的二进制是‘1010’。它只用了前4位,这4位是数字的有效位,其它数位可以忽略不计。

 console.log((10).toString(2)); //1010

反推: image.png 负数可以存储为二进制代码,不过采用形式是二进制补码,计算数字二进制补码步骤如下:

  • 1、确定数字的非负版本二进制

  • 2、求得二进制反码后要把0替换为1,1替换为0

  • 3、在二进制反码上加1

    如:确定-10的二进制补码

  • 1、10的二进制表示如下:0000 0000 0000 0000 0000 0000 0000 1010

  • 2、把0替换1,1替换0: 1111 1111 1111 1111 1111 1111 1111 0101

  • 3、在二进制反码上加1

image.png 所以-10的二进制表示是1111 1111 1111 1111 1111 1111 0110。需要注意在处理有符号的整数时不能访问31位。但是,把负整数转换成二进制字符串后,ECMAScript并不以二进制补码形式显示,而是用数字绝对值的标准二进制代码前面加负号的形式输出,这是为了避免访问位31位,为了方便 。

console.log((-10).toString(2)); //-1010

    位运算符可以进行运算,包括位非(NOT),按位与(AND),按位或(OR),按位异或(XOR),左移,有符号右移,无符号右移。

1、按位非(NOT)

按位非操作符由一个波浪线(~)表示,操作它可以返回数值反码,其本质是操作数的负值减1。对一个整数两次按位非得到它的自身,对于小数两次按位非可以得到取整效果。

let n = 9;
let i = 9.9;
let m = ~n;
console.log(m); //-10
console.log(~~n);//9
console.log(~~i);//9

2、按位与(AND)

    按位与操作符由一个和符号(&)表示,它有两个操作数。直接对数字的二进制形式进行运算。它把每个数字中的数位对齐,然后用下面的规则对同一位置上的两个数位进行AND运算。按位与操作只有在两个数值的对应位都是1时才返回1,任何一位是0结果都是为0。

image.png

let n = 10;
let m = 15;
console.log(n & m);  //10
console.log(n.toString(2), m.toString(2)); //1010 1111

image.png

3、按位或(OR)

    按位或操作符由一个竖线符号()表示,同样也有两个操作符,按位或操作按照下表来进行。按位或操作在有一个位是1的情况下就返回1,而只有在两个位数都是0的情况下才返回0。一个整数与0按位或运算可以得到它本身,一个小数与0按位或者运算可以得到取整的效果。

image.png

let n = 10;
let m = 15;
console.log(n | m);    //15
console.log(3.9 | 0);  //3
console.log(4.1 | 0);   //4
console.log(n.toString(2), m.toString(2)); //1010 1111

image.png

4、按位异或(XOR)

    它由一个插入符号(^)表示,也需要两个操作数,以下是真值表。按位异或的两个数值相同时返回0,不同时返回1。一个整数与0按位异或可以保持自身,一个小数与0按位异或可以取整。

image.png

let n = 10;
let m = 15;
console.log(n ^ m);  //5
console.log(n.toString(2), m.toString(2)); //1010 1111

image.png

    ^有一个特殊运用,连续对两个数a和b进行三次异或运算,a^=b,b^=a,a^=b可以互换它们的值。这意味着,使用^可以在不引入临时变量的前提下,互换两个变量的值。

let a = 10, b = 11;
a ^= b, b ^= a, a ^= b;
console.log(a, b); //11 10

5、左移(<<)

    左移操作符由两上小于号(<<)表示,这个操作符会把数值的所有位向左移动指定的位数。

let n=3;
let m=n<<5;
console.log(n.toString(2)); //11
console.log(m); //96

image.png

6、右移(>>)

    在有符号右移中,由两个大于号(>>)表示,将数值向右移动,但保留符号位。在符号的右移操作与左移操作正好相反,如果96向右移动5位变成3。

let n = 96;
console.log(n >> 5);//3

    在无符号右移中它是由三个大于号来表示(>>>),这个操作符会将数值的所有32位都向右移动,对于正数来说无符号右移结果和有符号右移结果相同。但对于负数来说无符号右移是以0来填充空位而不是像有符号那样以符号位的值来填充空位。还有一点是,无符号右移操作符会把负数的二进制码当成正数的二进制码且由于负数以其绝对值的二进制补码形式表示,所以会导致无符号右移后的结果非常的大。

let n = -96;
console.log(n >>> 5);//134217725

image.png

    我们常常利用<<来实现乘法运算;利用>>实现除法运算;利用^来实现值互换,小数取整。

console.log(4 << 1);//8 
console.log(4 << 2);//16 
console.log(4 << 3);//32 
console.log(4 << 4);//64 
console.log(5 >> 3);//0
console.log(14 >> 2);//3
let a = 20, b = 4;
a ^= b, b ^= a, a ^= b;
console.log(a, b);//4 20

console.log(~~9.9);//9
console.log(9.8 | 0);//9
console.log(9.7 ^ 0);//9
console.log(9.6 << 0);//9
console.log(9.3 >> 0);//9