# 二、常量和作用域

大多数现代语言都提供了一些定义`constant`的方法。这确保了变量的值永远不会改变，并且是您知道在您的应用程序中永远不会改变的值的最佳用法。

我们来看几个`constants`的例子:

代码清单 5

```js
  const PI = 3.141593;
  console.log(PI);

```

该代码在升华中使用 Ctrl+B(适用于 Windows)或 Cmd+B(适用于 Mac)生成以下输出:

代码清单 6

```js
  3.141593

```

| ![](img/00003.gif) | 注意:除非另有说明，否则我们将使用崇高作为代码片段的首选编辑器。在 Git 存储库中，您会在它们自己的章节中找到相应的片段。 |

现在，让我们尝试为`PI`变量重新赋值。我们可以通过在编辑器中添加以下几行来实现这一点:

代码清单 7

```js
  PI =
  22;
  console.log(PI);

```

尝试运行前面的示例会产生语法错误。transpiler 足够聪明，能告诉我们原因。请看下图:

![](img/00005.jpeg)

图 2:常量只读

您可以在图 2 中看到，由于`PI`被声明为`constant`，因此它被认为是只读的。这很好，错误描述非常清楚。

| ![](img/00003.gif) | 注意:const 关键字只能用于块范围区域。我们将在下一节研究块范围。 |

以前，JavaScript 开发人员在使用`var`关键字时必须处理吊装。在运行时，任何声明的变量都将被提升到执行上下文的顶部。让我们看几个在运行时如何处理`var`关键字的例子:

代码清单 8

```js
  var submit = function()
  {
    var
  x = "foo";

    if
  (x == "foo")
  {
      var
  y = "bar";
    }
    console.log(x);
    console.log(y);
  }

  submit();

```

该代码产生以下输出:

代码清单 9

```js
  foo
  bar

```

从输出中可以看出，`x`和`y`变量在函数级都可用。这称为提升，与逻辑块无关。

有了`let`关键词，现在就不一样了。让我们修改前面的代码清单，但是在声明`y`变量时使用`let`关键字:

代码清单 10

```js
  var submit = function()
  {
    var
  x = "foo";

    if
  (x == "foo")
  {
      let
  y = "bar";
    }
    console.log(x);
    console.log(y);
  }

  submit();

```

代码在点击运行后在 Chrome 中产生如下输出:

代码清单 11

```js
  "ReferenceError: y
  is not defined

```

这个输出实际上非常好，因为它让我们知道我们试图引用一个超出范围的变量。

以下示例演示了`let`操作员如何在`for-loops`中工作:

代码清单 12

```js
  let shoppingCart = [
    {id:
  0, product:
  "DVD", price: 21.99},
    {id:
  1, product:
  "CD", price: 11.99}
  ];

  for (let i = 0; i <
  shoppingCart.length; i++) {
    let
  item = shoppingCart[i];
    console.log("Item:
  " + item.product + " - Price:
  " + item.price);
  }

```

让我们看看产生的输出:

代码清单 13

```js
  Item: DVD - Price: 21.99
  Item: CD - Price: 11.99

```

如您所见，我们仍然得到了预期的行为，但是项目变量有一个`for-loop`的范围边界。

当使用 transpiler 时，仅这一点就有助于减轻许多范围界定问题，这些问题在带提升的 ES5 中容易出错。

ES6 中为正则表达式引入了两个新标志:

*   /y
*   /u

新的/y 粘性标志允许您创建粘性搜索；它将正则表达式的每个匹配锚定到前一个匹配的末尾。

让我们看看下面的例子:

代码清单 14

```js
  var text = 'First
  line\nsecond line';
  var regex = /^(\S+)
  line\n?/y;

  var match = regex.exec(text);
  console.log(match[1]);        //
  logs 'First'
  console.log(regex.lastIndex); // logs '11'

  var match2 = regex.exec(text);
  console.log(match2[1]);       //
  logs 'Second'
  console.log(regex.lastIndex); // logs '22'

  var match3 = regex.exec(text);
  console.log(match3 === null);
  // logs 'true'

```

| ![](img/00003.gif) | 注意:这个例子只会在微软 Edge 或者火狐上运行。 |

我们从文本变量中的一个简单字符串开始。接下来，我们定义我们的正则表达式。如您所见，我们正在寻找以“行”结尾的匹配项，可能还有一个新行。然后我们执行正则表达式并抓取第一个匹配，它包含值`First`并记录`lastIndex`位置。

我们再次对文本变量运行`exec`方法，它从`lastIndex`位置继续，并找到“第二个”匹配。`lastIndex`位置被更新和记录。

最后，我们第三次运行`exec`方法，它返回`null` `,`，表示没有找到更多匹配。

这个新标志的主要用例是标记化，您希望每个匹配立即跟随其前身。

以下是对字符串进行标记的示例:

代码清单 15

```js
  const TOKEN = /\s*(\+|[0-9]+)\s*/y;

  function
  tokenize(TOKEN_REGEX, str) {
    let
  result = [];
    let
  match;
    while
  (match = TOKEN_REGEX.exec(str)) {
      result.push(match[1]);
    }
    return
  result;
  }

  let result = tokenize(TOKEN, '3
  + 4');
  console.log(JSON.stringify(result));

```

| ![](img/00003.gif) | 注意:这个例子只会在微软 Edge 或者火狐上运行。 |

以下是它产生的输出:

代码清单 16

```js
  ["3","+","4"]

```

虽然这是一个人为的例子，但是当您想要标记字符串时，您可以看到这在哪里非常有用。

/u 标志将正则表达式置于特殊的 Unicode 模式。这种模式有两个特点:

*   您可以使用 Unicode 代码点转义序列，如`\u{1F42A}`，通过代码点指定字符。普通的 Unicode 转义符，如`\u03B1`，只有四个十六进制数字的范围。
*   正则表达式模式和字符串中的“字符”是代码点(不是 UTF-16 代码单位)。

让我们看一些例子:

代码清单 17

```js
  console.log('\u{1F680}'
  === '\uD83D\uDE80');
  let codepoint = "\u{1F680}";
  console.log(codepoint);

```

执行此操作会产生以下结果:

代码清单 18

```
True
🚀
```

第一行代码演示了将 Unicode 代码点与两个正常的 Unicode 转义进行比较。第二行和第三行只是演示如何呈现 Unicode 代码点。

| ![](img/00003.gif) | 注意:不是所有浏览器都支持/y 或/u 标志。这些示例在火狐中使用过，但也可以在微软 Edge 中运行。 |