04_JavaScript常见的运算符

120 阅读10分钟

JavaScript常见的运算符

几乎所有的编程语言都有各种各样的运算符(也被称之为操作符,operators)

计算机最基本的操作就是执行运算,执行运算时就需要使用运算符来操作

JavaScript按照使用场景的不同将运算符分成了很多种类型:

  • 算术运算符
  • 赋值运算符
  • 关系(比较)运算符
  • 逻辑运算符
# + -> 运算符 
# 23 -> 运算元或运算目
$ 2 + 3

如果一个运算符对应的只有一个运算元,那么它是 一元运算符

  • 比如说一元负号运算符(unary negation)-,它的作用是对数字进行正负转换

如果一个运算符拥有两个运算元,那么它是 二元运算符

  • 比如 2 + 3

如果一个运算符拥有三个运算元,那么它是 三元运算符

  • 比如 isLogin ? 'loginOut' : 'loginIn'

1.算术运算符

算术运算符用在数学表达式中, 它的使用方式和数学中也是一致的

算术运算符是对数据进行计算的符号

Snipaste_2022-11-08_11-03-33.png

取余运算符是 %

  • a % b 的结果是 a 整除 b 的 余数

求幂运算 a ** b 将 a 提升至 a 的 b 次幂

  • a ** b运算符等价于Math.pow(a, b)
    console.log(2 ** 4)
    console.log(Math.pow(2, 4))
// **的优先级大于普通的数学运算符
console.log(2 * 3 ** 2)  // => 2 * 9 = 18

2.赋值运算符

= 其实也是一个运算符,被称之为** 赋值( assignments )运算符**

// 普通赋值
var num = 123

// 链式赋值(Chaining assignments)
// 链式赋值从右到左进行计算, 所有的变量共享一个值
var num1 = num2 = num3 = 321
console.log(num1, num2, num3)

let num
// 对于普通赋值运算符,其所赋的值,会作为整个语句的返回值
console.log(num = 2) // => 2

2.1原地修改 (Modify-in-place)

原地修改(Modify-in-place) 指的是对某一个变量进行修改后,将修改后的新值重新赋值给变量本身

所有算术和位运算符都有简短的“修改并赋值”运算符:如/=-=

Snipaste_2022-11-08_11-06-35.png

    var num = 100
    // 对于原地修改的赋值运算符,其最终所得到的结果 会作为整个语句的返回值
    // num = num + 10
    num += 10
    // num = num * 10
    num *= 10
    num **= 2
    console.log(num)

2.2自增/自减运算符

对一个数进行加一、减一是最常见的数学运算符之一

所以JS提供了对应的自增运算符和自减运算符

    var currentIndex = 5
    // 自己自增或者自减是没有区别
    // ++currentIndex
    // console.log(currentIndex)
    // --currentIndex
    // console.log(currentIndex)

    // 自增和自减表达式本身又在其他的表达式中, 那就有区别
    //当运算符置于变量后,被称为“后置形式”(postfix form)
    // var result1 = 100 + currentIndex++
    等价于
    //var result1 = 100 + currentIndex
    //currentIndex++
    
    // console.log(currentIndex)
    // console.log("result1:" + result1)
    //当运算符置于变量前,被称为“前置形式”(prefix form)
    var result2 = 100 + ++currentIndex
    等价于
    //var result2 = 100 + currentIndex
    //currentIndex ++
    console.log(currentIndex)
    console.log("result2:" + result2)

区别

有,但只有当我们使用 ++/-- 的返回值时才能看到区别

如果自增/自减的值不会被使用,那么两者形式没有区别

如果我们想要对变量进行自增操作,并且 需要立刻使用自增后的值,那么我们需要使用前置形式

前置形式返回一个新的值,但后置返回原来的值

3.比较运算符

在数学中有很多用于比较大小的运算符,在JavaScript中也有相似的比较

比较运算符的结果都是Boolean类型的

Snipaste_2022-11-08_11-12-33.png

3.1== vs ===

普通的相等性检查 == (又被称为抽象相等比较)存在一个问题,它不能区分出 0 和 false,或者空字符串和 false这类运算

这是因为在比较不同类型的值时,处于判断符号 == 两侧的值会先被转化为数字

空字符串和 false 也是如此,转化后它们都为数字 0;

    var foo1 = 0
    var foo2 = ""

    // ==运算符, 在类型不相同的情况下, 会将运算元先转成Number的值, 再进行比较(隐式转换)
    // null比较特殊: null在进行比较的时候, 应该是会被当成一个对象和原生类型进行比较的
    console.log(Number(foo1))
    console.log(Number(foo2))
    console.log(foo1 == foo2)

    // ===运算符, 在类型不相同的情况, 直接返回false
    console.log(foo1 === foo2)

但是实际比较的时候,我们并不希望其帮助我们进行隐式类型转换

此时可以使用严格相等运算符 ===,或被称之为全等运算符

严格相等运算符===在进行比较时不会做任何的类型转换

换句话说,如果 a 和 b 属于不同的数据类型,那么 a === b 不会做任何的类型转换而立刻返回 false

同样的,“不相等”符号 != 类似,“严格不相等”表示为 !==

严格相等的运算符虽然写起来稍微长一些,但是它能够很清楚地显示代码意图,降低你犯错的可能性。

所以推荐在实际开发中,使用全等运算符或全不等运算符,而不去使用相等运算符或不等运算符

// 在相等比较的时候 有一个值的比较 比较特殊 那就是null
// 在比较的时候null会作为特殊的对象(0x0)
// null在和其它类型值进行相等运算的时候,返回的结果都是false
// 只有当null和null本身或undefined进行比较的时候,其结果才是true

console.log(0 === null) // => false

console.log(null == null) // => true
console.log(null == undefined) // => true

4.三元运算符

有时我们需要根据一个条件去赋值一个变量。

  • 比如比较数字大小的时候,获取较大的数字

  • 这个时候if else语句就会显得过于臃肿,有没有更加简介的方法呢?

条件运算符:’?’

  • 这个运算符通过问号 ? 表示;

  • 有时它被称为三元运算符,被称为“三元”是因为该运算符中有三个操作数(运算元)

  • 实际上它是 JavaScript 中唯一一个有这么多操作数的运算符;

    // 案例一: 比较两个数字
    var num1 = 12*6 + 7*8 + 7**4
    var num2 = 67*5 + 24**2

    // 三元运算符
    var result = num1 > num2 ? num1: num2
    //计算条件结果,如果结果为真,则返回 value1,否则返回 value2。
    console.log(result)
    
       // 案例二: 给变量赋值一个默认值(了解)
    var info = {
      name: "wjl"
    }
    var obj = info ? info: {}
    console.log(obj)

    // 案例三: 让用户输入一个年龄, 判断是否成年人
    var age = prompt("请输入您的年龄:")
    age = Number(age)
    // if (age >= 18) {
    //   alert("成年人")
    // } else {
    //   alert("未成年人")
    // }
    var message = age >= 18 ? "成年人": "未成年人"
    alert(message)

5.逻辑运算符

逻辑运算符,主要是由三个: ||(或),&&(与),!(非)

逻辑运算符可以将多个表达式或者值放到一起来获取到一个最终的结果

Snipaste_2022-11-08_11-14-10.png

5.1逻辑或

||(或)两个竖线符号表示“或”运算符(也称为短路或)

  • 从左到右依次计算操作数
  • 处理每一个操作数时,都将其转化为布尔值(Boolean)
  • 如果结果是 true,就停止计算返回这个操作数的初始值
  • 如果所有的操作数都被计算过(也就是,转换结果都是 false),则返回最后一个操作数
  1. 返回的值是操作数的初始形式,不会转换为Boolean类型
  2. 一个或运算 || 的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值
    // 脱离分支语句, 单独使用逻辑或
    /*
      1.先将运算元转成Boolean类型
      2.对转成的boolean类型进行判断
        * 如果为true, 直接将结果(原始值)返回
        * 如果为false, 进行第二个运算元的判断
        * 以此类推
      3.如果找到最后, 也没有找到, 那么返回最后一个运算元
    */
    // var name = "why"
    // name || 运算元2 || 运算元3
    
    
    // 本质推导一: 之前的多条件是如何进行判断的
    var chineseScore = 95
    var mathScore = 99
    // chineseScore > 90为true, 那么后续的条件都不会进行判断
    if (chineseScore > 90 || mathScore > 90) {}
    
    // 本质推导二: 获取第一个有值的结果
    var info = "abc"
    var obj = {name: "why"}
    var message = info || obj || "我是默认值"
    console.log(message.length)
    // -------------------------------------
    
    let nickName = 'Klaus'
    // 逻辑或可以用来给变量赋默认值
    // 如果username的值为undefined或null的时候
    // userName的值就会变成Alex
    let userName = nickName || 'Alex'
    console.log(userName);
    // -------------------------------------

    // 但是使用逻辑或进行默认值的赋值会存在一定的问题
    // 因为fasly的值除了null和undefined外,还有false,''. 0
    // 此时在进行默认值赋值的时候,就容易出现问题
    let num = 0

    // 此时num其实是有值的,可能我们就希望将count赋值为0
    // 但是因为0转换为Boolean类型值后结果为false
    // 所以此时count最终被赋予的值为18
    let count = num || 18



// -------------------------------------

// 所以ES11中提供了??运算符,也就是空值合并操作符
let price = 0
// 当且仅当 price的值为undefined或null的时候,才会赋予 ??操作符后边的那个值
// 因为price的值为0,并不是undefined或null
// 所以此时totalPrice的结果为0 而不是20
let totalPrice = price ?? 20 

5.2逻辑与

&&(或)两个竖线符号表示“与”运算符(也称为短路与)

  • 从左到右依次计算操作数
  • 在处理每一个操作数时,都将其转化为布尔值(Boolean)
  • 如果结果是 false,就停止计算,并返回这个操作数的初始值(虽然一般不需要获取到初始值)
  • 如果所有的操作数都被计算过(例如都是真值),则返回最后一个操作数

与运算 返回第一个假值,如果没有假值就返回最后一个值

    // 运算元1 && 运算元2 && 运算元3
    /*
      也可以脱离条件判断来使用
      逻辑与的本质
       1.拿到第一个运算元, 将运算元转成Boolean类型
       2.对运算元的Boolean类型进行判断
         * 如果false, 返回运算元(原始值)
         * 如果true, 查找下一个继续来运算
         * 以此类推
       3.如果查找了所有的都为true, 那么返回最后一个运算元(原始值)
    */
    
    // 本质推导一: 逻辑与, 称之为短路与
    var chineseScore = 80
    var mathScore = 99
    if (chineseScore > 90 && mathScore > 90) {}

    // 本质推导二: 对一些对象中的方法进行有值判断
    var obj = {
      name: "why",
      friend: {
        name: "kobe",
        eating: function() {
          console.log("eat something")
        }
      }
    }

    // 调用eating函数
    // obj.friend.eating()
    obj && obj.friend && obj.friend.eating && obj.friend.eating()
    

let user = {
  name: 'klaus',
  age: 23,
  running() {
    console.log('running')
  }
}

// 确保user.running一定存在,且user.running是函数可以被调用
user && user.running && typeof user.running === 'function' && user.running()

// 但是很明显上边的代码所对应的可读性很差
// 所以ES11中提供了对应的可选链操作符(?.)
// 如果?.之前的结果转换为boolean值后为false
// 那么就直接返回undefined,否则就继续往后执行
// ?. 可以用于对象属性的取值,函数的调用或数组元素值的调用
user?.running?.()

5.3逻辑非

逻辑非运算符接受一个参数,并按如下运算

  • 步骤一:将操作数转化为布尔类型:true/false
  • 步骤二:返回相反的值

两个非运算 !! 有时候用来将某个值转化为布尔类型

也就是,第一个非运算将该值转化为布尔类型并取反,第二个非运算再次取反, 最后我们就得到了一个任意值到布尔值的转化

    var message = "Hello World"
    // 转Boolean类型值
    // 方式一
    console.log(Boolean(message))
    // 方式二
    console.log(!!message)


    var obj = null
    console.log(Boolean(obj))
    console.log(!!obj)