运算符之巴拉巴拉

165 阅读6分钟

比较运算符

相等运算符和全等运算符的区别?

  • 等于运算符在进行比较的时候,如果遇到数据类型不同,会先进行数据类型的转换,再比较值。

    在对于两个不同类型的数值,内部遵循的转换规则如下:

    1. 两个数都是基本类型,全部转化为Number类型进行比较

    2. 一个值是基本数据类型,一个是引用数据类型,那么就会调用该引用数据类型的valueOf(),用得到的基本类型值按照前面的规则进行比较。

      比如:

      let obj = {
          valueOf(){
              return 1;
          }
      };
      let obj1 = {};
      ​
      obj == 1;       //true
      obj1 == 1;      //false
      

      valueOf()是对象原型上的方法,它返回的是该对象的原始值。

      但是javaScript上的许多内置对象都重写了这个方法,导致针对不同的内置对象,调用的valueOf()返回的值都是不一样的。

      image-20220106170400025.png

      当然我们也可以通过Object.prototype.valueOf = ()=>{...}来重写它。

    3. 如果两个都是引用类型就判断他们的地址指针是否相同,若相同再做进一步判断属性值是否相同。

    4. null和undefined是相等的

    5. NaN不等于自身

      也就说NaN在等于运算符之下,不等于任何值,包括它自身。所以我们判断一个值是不是NaN不能用==运算符,一般使用的是isNaN()函数。

  • 全等运算符就不会进行数值类型的转换,如果数值类型不一样直接返回false,如果数值类型还要继续判断他们的值是否相等,只有值相等了才会返回true。

    null 和 undefined比较,相等操作符(==)为true,全等为false;

综上所述,相等操作符会进行数值类型的转换,所以有的时候会让我们得到预料之外的结果,因此除了在比较null和undefined的情况下可以使用相等操作符,其他情况建议使用全等操作符。

算数运算符

前自增和后自增的区别?

  • 相同点:无论是a++,还是++a,都会立即使得原来的a+1
  • 不同点:++a返回的是a+1(新值),但是a++返回的是a(旧值)

👏PS:自减也是这个道理;

逻辑运算符

逻辑运算符最为我们常用的就是它的“短路赋值”机制,基于这个机制我们可以使用逻辑运算符作为条件判断的依据,我们还可以对一些值设置默认值。(解构赋值也可以设置默认值,我们可以根据语义的不同选择不一样的方法去设置默认值)

&&:遇到false就短路,直接返回false所在的原值

||:遇到true就短路,直接返回true所在的原值

链判断运算符(可选链运算符)

在编程实务中,我们会遇到这样的需求:定义一个变量,如果某个对象存在某个值就将该值赋值给某个变量,否则就赋值默认值。具体如下

const a = obj.name || 'default';

但是这种需求存在两个最为常见的痛点:

  1. 如果上述对象obj存在多层嵌套,比如我们要求的时候obj.father.son.name,那我们在取值的时候是需要一层层判断它的上层属性是否存在的。

    let obj = {
        father: {
            son: {
                name: 'kkk'
            }
        }
    }
    const name = obj.father.son.name || 'default';
    //或者
    const name = obj.father.son.name ? obj.father.son.name : 'default'
    /*
        但是像上面写是很不安全的,因为如果name的某个上层对象不存在,那么就会报错,
        所以我们一般会像下面这样写
    */
    const name = obj && obj.father && obj.father.son && obj.father.son.name || 'default';
    //但是这样的面临着一个问题,就是随着嵌套层数的增加,程序执行判断的次序也在不断增加
    

    为了解决这个问题,ES6引入了新的运算符: ?. (链判断运算符或者说是可选链运算符)

    这个运算符它是通过链式调用的时候进行判断,如果判断出来null或者undefined,就直接中断并返回undefined。那么上述的赋值就可以改成:

    const name = obj?.father?.son?.name || 'default'
    

    关于链判断运算符,我们还有一个十分常见的用法:用链判断运算符来尝试调用一个可能不存在的方法, 这是很有帮助的,因为当我们调用的方法真的不存在它也只是会返回undefined,而不是抛出异常。比如下述的例子。

    1 关于链判断表达式还需要注意以下几点:

    • 链判断运算符不能用于赋值

      obj.father?.son?.name = 'what';
      ​
      //🙅‍♀️上述语法是不允许的,会报错
      
    • 链判断运算符可以访问数组

      let arr = [1,2,3];
      arr?.[2]
      
    • 链判断运算符可以和表达式结合

      let obj = null;
      let a = 0;
      obj?.[a++]
      //这里的a是1
      

Null判断运算符

  1. 链判断运算符解决的问题是针对链式调用中,检测上层对象是否存在,防止对象不存在报错。但是即使使用了链判断也不能很好解决我们想要为值为null或者undefined的变量赋默认值。如下例:

       let obj = {
            num: {
                x: 0
            }
        }
        const y = obj?.num?.x || 90;
        console.log(y);     //输入90
        /*
            这里我们本来的需求是如果obj.num.x为null或者undefined,我们就为将y赋值90,
            但是从上述结果可以看到,因为obj?.num?.x返回的是0,
            那么在进行||逻辑运算符的时候,会将其转换为fasle,所以导致赋默认值
            这显然不符合需求
        */
    

    开发者的原意是,只要属性的值为null或undefined,默认值就会生效,但是属性的值如果为空字符串或false或0,默认值也会生效。

    为了避免这种情况,ES6引入了一个新的 Null 判断运算符 ?? 。它的行为类似 || ,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。

        let obj = {
                num: {
                    x: 0
                }
            }
            const y = obj?.num?.x ?? 90;
            console.log(y);     //0
    

    Null判断运算符经常和链判断运算符一起使用,这样就可以实现为一个值为null或者undefined的变量赋值默认值了。

    关于优先级:

    因为null判断运算符本质上也是逻辑运算符,所以和&& || 一起使用的时候就会面临一个优先级的问题,那么ES6也是规定这几个一起使用的时候一定要加括号,否则报错。

    x ?? y || z && t
    //🙅‍♀️上述报错
    (x ?? y ) || (z && t)