2022 年 11 月 14 日

基本运算符,数学

我们从学校中学到了很多运算符。它们包括加法 +、乘法 *、减法 - 等。

在本章中,我们将从简单的运算符开始,然后专注于学校算术中未涵盖的 JavaScript 特定方面。

术语:“一元”、“二元”、“操作数”

在继续之前,让我们掌握一些常用术语。

  • 操作数 – 是运算符所应用的对象。例如,在 5 * 2 的乘法中,有两个操作数:左操作数是 5,右操作数是 2。有时,人们将它们称为“参数”而不是“操作数”。

  • 如果运算符只有一个操作数,则该运算符为一元运算符。例如,一元取反 - 会反转数字的符号

    let x = 1;
    
    x = -x;
    alert( x ); // -1, unary negation was applied
  • 如果运算符有两个操作数,则该运算符为二元运算符。减号也以二元形式存在

    let x = 1, y = 3;
    alert( y - x ); // 2, binary minus subtracts values

    形式上,在上面的示例中,我们有两个共享相同符号的不同运算符:取反运算符(一元运算符,用于反转符号)和减法运算符(二元运算符,用于从一个数字中减去另一个数字)。

数学

支持以下数学运算

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

前四个运算符比较简单,而 %** 需要稍作解释。

取余 %

取余运算符 % 与百分比无关,尽管它看起来像百分号。

a % b 的结果是 a 除以 b 的整数除法的 余数

例如

alert( 5 % 2 ); // 1, the remainder of 5 divided by 2
alert( 8 % 3 ); // 2, the remainder of 8 divided by 3
alert( 8 % 4 ); // 0, the remainder of 8 divided by 4

幂运算 **

幂运算符 a ** ba 提升到 b 的幂。

在学校数学中,我们将其写为 ab

例如

alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16

与数学运算一样,幂运算符也适用于非整数。

例如,平方根是 ½ 的幂运算

alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root)
alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root)

使用二元 + 进行字符串连接

让我们了解一下 JavaScript 运算符超越学校算术运算的功能。

通常,加号运算符 + 对数字求和。

但是,如果将二元 + 应用于字符串,它会合并(连接)它们

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

请注意,如果任何一个操作数是字符串,那么另一个操作数也会被转换为字符串。

例如

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"

请看,第一个操作数是字符串还是第二个操作数并不重要。

这是一个更复杂的例子

alert(2 + 2 + '1' ); // "41" and not "221"

在这里,运算符一个接一个地工作。第一个 + 对两个数字求和,因此它返回 4,然后下一个 + 向其添加字符串 1,因此它就像 4 + '1' = '41'

alert('1' + 2 + 2); // "122" and not "14"

在这里,第一个操作数是字符串,编译器也将其他两个操作数视为字符串。2 连接到 '1',因此它就像 '1' + 2 = "12""12" + 2 = "122"

二元 + 是唯一以这种方式支持字符串的运算符。其他算术运算符只适用于数字,并且总是将其操作数转换为数字。

以下是减法和除法的演示

alert( 6 - '2' ); // 4, converts '2' to a number
alert( '6' / '2' ); // 3, converts both operands to numbers

数字转换,一元 +

加号 + 有两种形式:我们上面使用的二元形式和一元形式。

一元加号,或者换句话说,应用于单个值的加号运算符 +,对数字不起作用。但是,如果操作数不是数字,一元加号会将其转换为数字。

例如

// No effect on numbers
let x = 1;
alert( +x ); // 1

let y = -2;
alert( +y ); // -2

// Converts non-numbers
alert( +true ); // 1
alert( +"" );   // 0

它实际上与 Number(...) 做同样的事情,但更简洁。

经常需要将字符串转换为数字。例如,如果我们从 HTML 表单字段中获取值,它们通常是字符串。如果我们想对它们求和呢?

二元加号会将它们作为字符串相加

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

alert( apples + oranges ); // "23", the binary plus concatenates strings

如果我们想将它们视为数字,我们需要先转换,然后求和

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

// both values converted to numbers before the binary plus
alert( +apples + +oranges ); // 5

// the longer variant
// alert( Number(apples) + Number(oranges) ); // 5

从数学家的角度来看,大量加号可能看起来很奇怪。但从程序员的角度来看,这没什么特别的:一元加号先应用,它们将字符串转换为数字,然后二元加号对它们求和。

为什么一元加号在二元加号之前应用于值?正如我们即将看到的,那是因为它们具有更高的优先级

运算符优先级

如果一个表达式有多个运算符,执行顺序由它们的优先级定义,或者换句话说,由运算符的默认优先级顺序定义。

从学校我们都知道,表达式 1 + 2 * 2 中的乘法应该在加法之前计算。这正是优先级问题。乘法被认为比加法具有更高的优先级

括号会覆盖任何优先级,因此如果我们对默认顺序不满意,我们可以使用括号来更改它。例如,写 (1 + 2) * 2

JavaScript 中有很多运算符。每个运算符都有一个对应的优先级编号。编号较大的运算符先执行。如果优先级相同,执行顺序是从左到右。

以下是 优先级表 的摘录(你不必记住这个,但请注意一元运算符高于相应二元运算符)

优先级 名称 符号
14 一元加号 +
14 一元取反 -
13 指数 **
12 乘法 *
12 除法 /
11 加法 +
11 减法 -
2 赋值 =

正如我们所看到的,“一元加号”的优先级为 14,高于“加法”(二元加号)的 11。这就是为什么在表达式 "+apples + +oranges" 中,一元加号在加法之前起作用。

赋值

请注意,赋值 = 也是一个运算符。它在优先级表中列出,优先级非常低,为 2

这就是为什么当我们分配一个变量,比如 x = 2 * 2 + 1 时,首先进行计算,然后计算 =,将结果存储在 x 中。

let x = 2 * 2 + 1;

alert( x ); // 5

赋值 = 返回一个值

= 作为运算符,而不是“神奇的”语言结构,有一个有趣的含义。

JavaScript 中的所有运算符都返回一个值。对于 +- 来说很明显,但对于 = 也是如此。

调用 x = valuevalue 写入 x 然后返回它

这是一个使用赋值作为更复杂表达式一部分的示例

let a = 1;
let b = 2;

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

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

在上面的示例中,表达式 (a = b + 1) 的结果是分配给 a 的值(即 3)。然后将其用于进一步的计算。

有趣的代码,不是吗?我们应该了解它的工作原理,因为有时我们在 JavaScript 库中会看到它。

不过,请不要这样编写代码。这样的技巧肯定不会使代码更清晰或更易读。

链接赋值

另一个有趣的功能是链接赋值的能力

let a, b, c;

a = b = c = 2 + 2;

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

链接赋值从右到左计算。首先,计算最右边的表达式 2 + 2,然后将其分配给左边的变量:cba。最后,所有变量共享一个值。

同样,为了可读性,最好将此类代码分成几行

c = 2 + 2;
b = c;
a = c;

这样更容易阅读,尤其是在快速浏览代码时。

就地修改

我们经常需要对变量应用运算符并将新结果存储在同一个变量中。

例如

let n = 2;
n = n + 5;
n = n * 2;

可以使用运算符 +=*= 来缩短此表示法

let n = 2;
n += 5; // now n = 7 (same as n = n + 5)
n *= 2; // now n = 14 (same as n = n * 2)

alert( n ); // 14

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

此类运算符具有与普通赋值相同的优先级,因此它们在大多数其他计算之后运行

let n = 2;

n *= 3 + 5; // right part evaluated first, same as n *= 8

alert( n ); // 16

增量/减量

将数字增加或减少 1 是最常见的数字运算之一。

因此,有专门的运算符

  • 增量 ++ 将变量增加 1

    let counter = 2;
    counter++;        // works the same as counter = counter + 1, but is shorter
    alert( counter ); // 3
  • 减量 -- 将变量减少 1

    let counter = 2;
    counter--;        // works the same as counter = counter - 1, but is shorter
    alert( counter ); // 1
重要

增量/减量只能应用于变量。尝试在值(如 5++)上使用它会产生错误。

运算符 ++-- 可以放在变量之前或之后。

  • 当运算符位于变量之后时,它处于“后缀形式”:counter++
  • 当运算符位于变量之前时,它是“前缀形式”:++counter

这两个语句都执行相同操作:将 counter 增加 1

有什么区别吗?是的,但只有在使用 ++/-- 的返回值时才能看到它。

让我们澄清一下。众所周知,所有运算符都会返回一个值。增量/减量也不例外。前缀形式返回新值,而后缀形式返回旧值(在增量/减量之前)。

为了了解差异,这里有一个示例

let counter = 1;
let a = ++counter; // (*)

alert(a); // 2

在行(*)中,前缀形式++countercounter进行增量并返回新值2。因此,alert显示2

现在,让我们使用后缀形式

let counter = 1;
let a = counter++; // (*) changed ++counter to counter++

alert(a); // 1

在行(*)中,后缀形式counter++也对counter进行增量,但返回值(在增量之前)。因此,alert显示1

总结一下

  • 如果不使用增量/减量的结果,则使用哪种形式没有区别

    let counter = 0;
    counter++;
    ++counter;
    alert( counter ); // 2, the lines above did the same
  • 如果我们想要增加一个值立即使用运算符的结果,我们需要前缀形式

    let counter = 0;
    alert( ++counter ); // 1
  • 如果我们想要对一个值进行增量但使用其前一个值,我们需要后缀形式

    let counter = 0;
    alert( counter++ ); // 0
增量/减量和其他运算符

运算符++/--也可以在表达式中使用。它们的优先级高于大多数其他算术运算。

例如

let counter = 1;
alert( 2 * ++counter ); // 4

与比较

let counter = 1;
alert( 2 * counter++ ); // 2, because counter++ returns the "old" value

虽然在技术上是正确的,但这种表示法通常会降低代码的可读性。一行执行多个操作 - 不好。

在阅读代码时,快速“垂直”扫视很容易错过类似counter++的内容,并且变量增加并不明显。

我们建议采用“一行 - 一个动作”的风格

let counter = 1;
alert( 2 * counter );
counter++;

按位运算符

按位运算符将参数视为 32 位整数,并在其二进制表示的级别上工作。

这些运算符不是 JavaScript 特有的。它们在大多数编程语言中都受支持。

运算符列表

  • AND ( & )
  • OR ( | )
  • XOR ( ^ )
  • NOT ( ~ )
  • LEFT SHIFT ( << )
  • RIGHT SHIFT ( >> )
  • ZERO-FILL RIGHT SHIFT ( >>> )

当我们需要在最低(按位)级别上摆弄数字时,这些运算符的使用非常少见。我们很快不需要这些运算符,因为 Web 开发几乎不需要它们,但在某些特殊领域,例如密码学,它们很有用。当有需要时,你可以在 MDN 上阅读按位运算符章节。

逗号

逗号运算符,是最稀有、最不寻常的运算符之一。有时,它用于编写更简洁的代码,因此我们需要了解它才能理解正在发生的事情。

逗号运算符允许我们求值多个表达式,用逗号,将它们分开。每个表达式都会求值,但只有最后一个表达式的结果才会返回。

例如

let a = (1 + 2, 3 + 4);

alert( a ); // 7 (the result of 3 + 4)

这里,第一个表达式1 + 2求值,其结果被丢弃。然后,3 + 4求值并作为结果返回。

逗号的优先级非常低

请注意,逗号运算符的优先级非常低,低于 =,因此在上述示例中,括号非常重要。

如果不使用括号:a = 1 + 2, 3 + 4 会首先计算 +,将数字相加得到 a = 3, 7,然后赋值运算符 =a = 3 赋值,其余部分将被忽略。这就像 (a = 1 + 2), 3 + 4

我们为什么要使用一个运算符,除了最后一个表达式之外,它会丢弃所有内容?

有时,人们在更复杂的结构中使用它,以便在一行中放置多个操作。

例如

// three operations in one line
for (a = 1, b = 3, c = a * b; a < 10; a++) {
 ...
}

许多 JavaScript 框架中都使用了此类技巧。这就是我们提到它们的原因。但通常它们不会提高代码的可读性,因此在使用它们之前我们应该仔细考虑。

任务

重要性:5

在以下代码之后,所有变量 abcd 的最终值是什么?

let a = 1, b = 1;

let c = ++a; // ?
let d = b++; // ?

答案是

  • a = 2
  • b = 2
  • c = 2
  • d = 1
let a = 1, b = 1;

alert( ++a ); // 2, prefix form returns the new value
alert( b++ ); // 1, postfix form returns the old value

alert( a ); // 2, incremented once
alert( b ); // 2, incremented once
重要性:3

在以下代码之后,ax 的值是什么?

let a = 2;

let x = 1 + (a *= 2);

答案是

  • a = 4(乘以 2)
  • x = 5(计算为 1 + 4)
重要性:5

这些表达式的结果是什么?

"" + 1 + 0
"" - 1 + 0
true + false
6 / "3"
"2" * "3"
4 + 5 + "px"
"$" + 4 + 5
"4" - 2
"4px" - 2
"  -9  " + 5
"  -9  " - 5
null + 1
undefined + 1
" \t \n" - 2

仔细考虑,写下来,然后与答案进行比较。

"" + 1 + 0 = "10" // (1)
"" - 1 + 0 = -1 // (2)
true + false = 1
6 / "3" = 2
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
"  -9  " + 5 = "  -9  5" // (3)
"  -9  " - 5 = -14 // (4)
null + 1 = 1 // (5)
undefined + 1 = NaN // (6)
" \t \n" - 2 = -2 // (7)
  1. 使用字符串 "" + 1 进行加法会将 1 转换为字符串:"" + 1 = "1",然后我们得到 "1" + 0,应用相同的规则。
  2. 减法 -(就像大多数数学运算一样)只适用于数字,它将空字符串 "" 转换为 0
  3. 使用字符串进行加法会将数字 5 附加到字符串中。
  4. 减法始终转换为数字,因此它使 " -9 " 成为数字 -9(忽略其周围的空格)。
  5. 数字转换后,null 变成 0
  6. 数字转换后,undefined 变成 NaN
  7. 当字符串转换为数字时,字符串开始和结束处的空格字符会被修剪掉。这里整个字符串由空格字符组成,例如 \t\n 和它们之间的“常规”空格。因此,与空字符串类似,它变成 0
重要性:5

以下代码要求用户输入两个数字并显示它们的总和。

它工作不正常。以下示例中的输出为 12(对于默认提示值)。

为什么?修复它。结果应为 3

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(a + b); // 12

原因是提示将用户输入作为字符串返回。

因此,变量的值分别为 "1""2"

let a = "1"; // prompt("First number?", 1);
let b = "2"; // prompt("Second number?", 2);

alert(a + b); // 12

我们应该做的是在 + 之前将字符串转换为数字。例如,使用 Number() 或在前面加上 +

例如,就在 prompt 之前

let a = +prompt("First number?", 1);
let b = +prompt("Second number?", 2);

alert(a + b); // 3

或在 alert

let a = prompt("First number?", 1);
let b = prompt("Second number?", 2);

alert(+a + +b); // 3

在最新的代码中同时使用一元和二元 +。看起来很有趣,不是吗?

教程地图

评论

在评论之前阅读此内容…
  • 如果您有改进建议 - 请 提交 GitHub 问题或提交拉取请求,而不是评论。
  • 如果您无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行 - 将其包装在 <pre> 标记中,对于 10 行以上 - 使用沙盒(plnkrjsbincodepen…)