跳至主要內容

基础运算

javascriptInfo, Hirsun大约 27 分钟

基础运算

术语

在正式开始前,我们先简单浏览一下常用术语。

  • 运算元 —— 运算符应用的对象。比如说乘法运算 5 * 2,有两个运算元:左运算元 5 和右运算元 2。有时候人们也称其为“参数”而不是“运算元”。

  • 如果一个运算符对应的只有一个运算元,那么它是 一元运算符。比如说一元负号运算符(unary negation)-,它的作用是对数字进行正负转换:

    let x = 1;
    x = -x; // 符号反转运算符
    
  • 如果一个运算符拥有两个运算元,那么它是 二元运算符。减号还存在二元运算符形式:

    let x = 1, y = 3;
    alert( y - x ); // 2,二元运算符减号做减运算
    

数学

支持以下数学运算:

  • 加法 +,
  • 减法 -,
  • 乘法 *,
  • 除法 /,
  • 取余 %,
  • 求幂 **

类型转换

用二元运算符 + 连接字符串

如果加号 + 被应用于字符串,它将合并(连接)各个字符串:

let s = "my" + "string";
alert(s); // mystring

注意:只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
alert(2 + 2 + '1' ); // "41",不是 "221"
alert('1' + 2 + 2); // "122",不是 "14"

下面是减法和除法运算的示例:

alert( 6 - '2' ); // 4,将 '2' 转换为数字
alert( '6' / '2' ); // 3,将两个运算元都转换为数字

数字转化,一元运算符 +

还有一种 +是一元运算符。如果运算元不是数字,加号 + 则会将其转化为数字。

// 转化非数字
alert( +true ); // 1
alert( +"" );   // 0

二元运算符加号会把它们合并成字符串:

let apples = "2";
let oranges = "3";

alert( apples + oranges ); // "23",二元运算符加号合并字符串

如果我们想把它们当做数字对待,我们需要转化它们,然后再求和:

let apples = "2";
let oranges = "3";

// 在二元运算符加号起作用之前,所有的值都被转化为了数字
alert( +apples + +oranges ); // 5

// 更长的写法 alert( Number(apples) + Number(oranges) ); // 5

一元运算符先于二元运算符作用于运算元

赋值运算符

我们知道赋值符号 = 也是一个运算符。从优先级表中可以看到它的优先级非常低,只有 2

这也是为什么,当我们赋值时,比如 x = 2 * 2 + 1,所有的计算先执行,然后 = 才执行,将计算结果存储到 x

let x = 2 * 2 + 1;
alert( x ); // 5

赋值 = 返回一个值了,语句 x = value 将值 value 写入 x 然后返回 x

下面是一个在复杂语句中使用赋值的例子:

let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

alert( a ); // 3
alert( c ); // 0

上面这个例子,(a = b + 1) 的结果是赋给 a 的值(也就是 3)。然后该值被用于进一步的运算。

不过,请不要写这样的代码。这样的技巧绝对不会使代码变得更清晰或可读。

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

运算符的优先级

如果一个表达式拥有超过一个运算符,执行的顺序则由 优先级 决定。换句话说,所有的运算符中都隐含着优先级顺序。

在 JavaScript 中有众多运算符。每个运算符都有对应的优先级数字。数字越大,越先执行。如果优先级相同,则按照由左至右的顺序执行

记住一元运算符优先级高于二元运算符

优先级名称符号
.........
15一元加号+
15一元负号-
14求幂**
13乘号*
13除号/
12加号+
12减号-
.........
2赋值符=
.........

原地修改

let n = 2;
n += 5; // 现在 n = 7(等同于 n = n + 5)
n *= 2; // 现在 n = 14(等同于 n = n * 2)

alert( n ); // 14

let counter = 2;
counter++;      // 和 counter = counter + 1 效果一样,但是更简洁
alert( counter ); // 3

counter = 2;
--counter; 

位运算符

位运算符把运算元当做 32 位整数,并在它们的二进制表现形式上操作。

这些运算符不是 JavaScript 特有的。大部分的编程语言都支持这些运算符。

下面是位运算符:

  • 按位与 ( & )
  • 按位或 ( | )
  • 按位异或 ( ^ )
  • 按位非 ( ~ )
  • 左移 ( << )
  • 右移 ( >> )
  • 无符号右移 ( >>> )

逻辑运算符

  • ||(或)
  • &&(与)
  • !(非)
  • ??(空值合并运算符)

这些运算符和c++一样,存在短路的情况。

我们需要注意的是,在逻辑运算符中的返回值。js中的逻辑运算不一定返回 ture或者false,还会返回其它类型。

或运算寻找第一个真值

给定几个参与或运算的值:

result = value1 || value2 || value3;

或运算符 || 做了如下的事情:

  • 从左到右依次计算操作数。
  • 处理每一个操作数时,都将其转化为布尔值。如果结果是 true,就停止计算,返回这个操作数的初始值
  • 如果所有的操作数都被计算过(也就是,转换结果都是 false),则返回最后一个操作数

返回的值是操作数的初始形式,不会做布尔转换。

换句话说,一个或运算 || 的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值。

例如:

alert( 1 || 0 ); // 1(1 是真值)

alert( null || 1 ); // 1(1 是第一个真值)
alert( null || 0 || 1 ); // 1(第一个真值)

alert( undefined || null || 0 ); // 0(都是假值,返回最后一个值)
// 短路用法
let firstName = "";
let lastName = "";
let nickName = "SuperCoder";
alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder 

与运算寻找第一个假值

也存在短路

!

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

alert( !!"non-empty string" ); // true
alert( !!null ); // false

空值合并运算符

This is a recent addition to the language. Old browsers may need polyfills.

由于它对待 nullundefined 的方式类似,所以在本文中我们将使用一个特殊的术语对其进行表示。为简洁起见,当一个值既不是 null 也不是 undefined 时,我们将其称为“已定义的(defined)”。

a ?? b 的结果是:

  • 如果 a 是已定义的,则结果为 a
  • 如果 a 不是已定义的,则结果为 b

换句话说,如果第一个参数不是 null/undefined,则 ?? 返回第一个参数。否则,返回第二个参数。

我们可以使用我们已知的运算符重写 result = a ?? b,像这样:

result = (a !== null && a !== undefined) ? a : b;

用法案例

let user;
alert(user ?? "匿名"); // 匿名(user 未定义)

// 当 height 的值为 null 或 undefined 时,将 height 的值设置为 100
height = height ?? 100;
  • || 返回第一个 值。
  • ?? 返回第一个 已定义的 值。

换句话说,|| 无法区分 false0、空字符串 ""null/undefined

出于安全原因,JavaScript 禁止将 ?? 运算符与 &&|| 运算符一起使用,除非使用括号明确指定了优先级。

逗号运算符

逗号运算符 , 是最少见最不常使用的运算符之一。有时候它会被用来写更简短的代码,因此为了能够理解代码,我们需要了解它。

逗号运算符能让我们处理多个语句,使用 , 将它们分开。每个语句都运行了,但是只有最后的语句的结果会被返回

举个例子:

let a = (1 + 2, 3 + 4);
alert( a ); // 7(3 + 4 的结果)

但是通常它并不能提升代码的可读性,使用它之前,我们要想清楚。

值的比较

  • 相等性检查 == , 不会发生数字和null之间的转换
  • 严格相等性检查 === ,不会发生转换
  • 普通比较符 > < >= <=,会发生转换

常规比较

alert( 2 > 1 );  // true(正确)
alert( 2 == 1 ); // false(错误)
alert( 2 != 1 ); // true(正确)

// string 也可比较,字符串是按 Unicode 编码顺序个进行比较的。
alert( 'Z' > 'A' ); // true
alert( 'Glow' > 'Glee' ); // true
alert( 'Bee' > 'Be' ); // true

当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。

alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2
alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1

严格相等

普通的相等性检查 == 存在一个问题,它不能区分出 0false, 也同样无法区分空字符串和 false

**严格相等运算符 === 在进行比较时不会做任何的类型转换。**换句话说,如果 ab 属于不同的数据类型,那么 a === b 不会做任何的类型转换而立刻返回 false

对 null 和 undefined 进行比较

null/undefined 会被转化为数字:null 被转化为 0,undefined 被转化为 NaN。

alert( null == undefined ); // true, JavaScript 存在一个特殊的规则,会判定它们非严格下相等。
alert( null === undefined ); // false

null vs 0

// 进行值的比较时,`null` 会被转化为数字,因此它被转化为了 0
alert( null > 0 );  // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true

相等性检查 == 和普通比较符 > < >= <= 的代码逻辑是相互独立的。

这就是为什么(3)中 null >= 0 返回值是 true,(1)中 null > 0 返回值是 false。

undefined

undefined 不应该被与其他值进行比较:

alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)

原因如下:

  • (1)(2) 都返回 false 是因为 undefined 在比较中被转换为了 NaN,而 NaN 是一个特殊的数值型值,它与任何值进行比较都会返回 false
  • (3) 返回 false 是因为这是一个相等性检查,而 undefined 只与 null 相等,不会与其他值相等。

避免问题

我们为何要研究上述示例?我们需要时刻记得这些古怪的规则吗?不,其实不需要。虽然随着代码写得越来越多,我们对这些规则也都会烂熟于胸,但是我们需要更为可靠的方法来避免潜在的问题:

  • 除了严格相等 === 外,其他但凡是有 undefined/null 参与的比较,我们都需要格外小心。
  • 除非你非常清楚自己在做什么,否则永远不要使用 >= > < <= 去比较一个可能为 null/undefined 的变量。对于取值可能是 null/undefined 的变量,请按需要分别检查它的取值情况。