内容


面向 JavaScript 开发人员的 ECMAScript 6 指南,第 1 部分

新 JavaScript 中的变量声明等功能

语法糖增强了 ECMAScript 6 中的核心功能

Comments

系列内容:

此内容是该系列 4 部分中的第 # 部分: 面向 JavaScript 开发人员的 ECMAScript 6 指南,第 1 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:面向 JavaScript 开发人员的 ECMAScript 6 指南,第 1 部分

敬请期待该系列的后续内容。

ECMAScript(通常称为 JavaScript)是一种经历了许多波折的脚本语言。它最初是为了实现 Netscape Navigator(第一批 Web 浏览器之一)的可扩展性而创建的。JavaScript 最初的命名考虑到了与 Java 的关联(Java 那时正迅速发展成为一种重要语言),多年来,它一直被视为两种语言中的次要语言。

ECMAScript的发展历程

ECMA 标准的一种变体在 1997 年为该语言赢得了一定的信赖,但第一个主要的标准版本 ECMAScript 4 将社区一分为二。它通过一个谨慎的、勘误式的版本(ECMAScript 5,也称为 “Harmony”)重建了统一性的感觉。

但在最近,JavaScript 扮演了一个回头浪子角色。在 Node.js 的支撑下,它成为了一种举足轻重的服务器端工具。在 2015 年 6 月被正式采纳为一种标准后,ECMAScript 2015(也称为 ECMAScript 6 或 ES6)逐步进入主流应用领域。ECMAScript 2016 于 2016 年 6 月被作为规范采纳。

随着 npm 注册表中的库和包迁移到最新版本,JavaScript 开发人员也是时候了解一下目前标准中的新特征了。毋庸置疑,一旦了解了其中一些功能,您就会想开始在代码中使用它们。

我们首先来看看对代码的灵活性和效率产生重大影响的 6 大语言变化。在这些变化之中,新的解构赋值运算或许是最重要的,所以我将在这里和 第 2 部分 中介绍一些使用它的场景。

迁移战略

尽管 ECMAScript 6 向该语言中引入了一些令人兴奋的变化,但大部分变化都被划分为语法糖(syntactic sugar)。在许多情况下,新标准通过改变代码编写方式而不是代码的功能来提高效率。这些更新与开发人员已在执行的工作的关系最紧密 — 还未标准化的快捷方式和解决办法。理想情况下,应用新语言特性使编写相同代码变得更快更容易。

从迁移角度讲,专注于语法糖(syntactic sugar)具有一定的优势。与学习新功能相比,扩展您对现有功能的了解具有更少的认知负担。另一方面,目前有多种方式来实现相同的结果:新的 ECMAScript 6 方式和旧方式。有时可能很难理解这一点,至少在您的迁移达到转折点之前是这样的。由于可以灵活地采用如此多的变化,所以可采用一种更精细、更灵活的迁移战略。对许多企业而言,这将是最合理的路径。ECMAScript 带来了非常多的新功能,尝试一次性集成它们可能负担太重。

另一个要考虑的迁移问题是环境的兼容性。众所周知,旧浏览器永远都不会被废弃 — Internet Explorer 就是一个恰当的例子。幸运的是,如果您的客户端不是 100% 兼容 ECMAScript 6,您可以使用一个 “transpiler” 将 ECMAScript 6 源代码转换为兼容 ECMAScript 5 的代码。有关两个较流行的工具的更多信息,请参阅 TraceurBabelJS

我们也可能非常高兴服务器环境完全在我们的掌控之下。在编写本文时,Node.js 的最新版本实现了新标准的 92%。除非您考虑剩余 8% 中的功能,否则无需额外设置即可在 Node.js 中运行 ECMAScript 6。

暂时知道这么多就够了;我们开始看看引入的一些变化。

语法变化

语法变化是最明显的,在某些情况下也是最容易理解的。尽管 ECMAScript 6 没有对 JavaScript 整体语法进行太多改动(它仍是一种基于 C 的语言,包含分号和花括号),但它确实清理了一些对开发人员具有细微影响的 “小” 东西。这里将介绍其中的少数变化。

Unicode 8.0

首先,ECMAScript 6 现在需要符合 Unicode Standard 8.0 版或更高版本。这意味着兼容 ECMAScript 的环境必须接受笑脸表情,因为 8.0 拥有表情符号的字符代码。

因为大部分文本编辑器都尚未直接支持 Unicode 8.0 的所有字符代码,所以您需要将大部分 Unicode 字符实现为转义字符,比如字符串字面量中的 \u{1d306}。因为您还可以使 Unicode 作为标识符,所以 ECMAScript 6 也支持对变量名称使用 Unicode 转义语法。这是一个示例:

    var \u{102C0} = { \u{102C0} : 2 };
    return \u{102C0};

大多数时候,开发人员将 Unicode 用于国际化用途,这意味着它将出现在字符串字面量内,而不是用作变量标识符。考虑在其代码中使用英语以外的语言的开发人员可能仍会喜欢变量标识符中新增的灵活性。

二进制和八进制字面量

考虑无处不在的整数字面量:9。由于 9 是一个字面量值,而且应考虑到阿拉伯记数制(十进制)的主导性,9 被视为一个十进制字面量。但是,计算机不会以十进制形式思考;它们以二进制、八进制或十六进制形式进行思考。ECMAScript 很早就支持十六进制字面量(通过在字面量中添加 0x 作为前缀来表示)。在 ECMAScript 6 中,您可以通过在字面量中分别添加 0b0o 作为前缀来表达二进制和八进制字面量。因此,在 ECMAScript 6 中,下面的表达式中的所有字面量都会打印 true,因为它们都是相同的值:

    var decimalLit = 15;
    var hexadecimalLit = 0xF;
    var octalLit = 0o17;
    var binaryLit = 0b1111;
    console.log(decimalLit == hexadecimalLit);
    console.log(decimalLit == octalLit);
    console.log(decimalLit == binaryLit);

字符串模板和插值

多年来,ECMAScript 开发人员使用了一些难看的字符串串联来将变量成员放入字符串中,比如:

    var message = "The user " + user.firstName + " " + user.lastName + 
      " cannot be " + action + " because " + validationError;

尽管此策略不是 JavaScript 中最大的错误来源,但它容易出错,更别提难以读取了。在适用性方面,它使得 JavaScript 落后于支持字符串插值的语言。为了不继续落后,ECMAScript 6 引入了重音符字符串字面量(backtick string literal)。这个字面量允许使用重音符 (`) 表示支持字符串插值的字符串字面量。

    var message = `The user ${user.firstName} ${user.lastName} cannot be
      ${action} because ${validationError}`;

be${action} 之间使用硬换行符,是因为重音符字符串也是 “多行字面量”。这意味着字符串中保留了空格,所以上面的消息将显示在两行上。它将在 “be” 后断开,然后在继续之前显示两个空格(因为代码缩进了两个空格)。

当然,不幸的是 ECMAScript 6 需要引入一种新的字符串字面量,而不是简单地支持在现有的单引号或双引号字符串中进行插入。但是,支持插值的向后兼容性意义非常大。这可能是最佳解决方案。随着时间的推移,我们可以预期大部分字符串字面量都将变成重音符字符串。

变量声明:let 和 const

ECMAScript 在过去允许程序员使用变量而不声明它们。但是,这么做会隐式地让它们成为全局变量,这被认为是一种不可取的行为。(除非它被称为 “单例模式”,进而被视为一种模式。)为了解决此问题,JavaScript 程序员开始使用 var 声明样式来在使用前声明变量。

不同于其他许多语言,ECMAScript 从来没有出现特定变量被重新声明多次的问题。因此,在守旧的 ECMAScript 中,您可以编写以下没有用的代码:

    var msg = "Howdy";
    var msg = "Hello there"; // acceptable, just reassigns

第二个声明绝不会导致错误。在上面的情况中,会向最初的变量重新分配新值。这是一个细微错误来源,C/C++/Java/C# 开发人员对此感到很奇怪。除此之外,ECMAScript 从来没有提供一个工具来创建与 Java 中的 final 或 C# 或 C++ 中的 const 类似的不可变变量。

ECMAScript 6 解决了所有这些问题:首先,它建议将 var 替换为 let,let 不可以重新声明。除此之外,let 的操作与 var 完全相同:

    var msg = "Howdy";
    var msg = "Hello there"; // acceptable, just reassigns
    
    let message = `This is your message.`;
    let message = `This is another message.`; // ERROR!

在变量声明中使用 const,满足了对不可变性的需求。完成设置之后,使用 const 声明的变量绝对不能修改:

    const message = `This is your message.`;
    message = `This is your second message.`; // ERROR!

尽管使用 const 声明的变量不能更改其值,但该变量指向的 object 不是常量,所以它仍是可修改的:

    const user = request.user;
    user = new User(); // ERROR!
    user.authorized = true; // acceptable, changes a property

因此,我的建议是首先使用 const。如果确实需要修改该变量,始终可以将声明更改为 let

代码块范围

谈到变量声明,令许多 ECMAScript 开发人员感到奇怪的是声明的变量没有绑定到声明它们的 “代码块”。它们被绑定到函数。这是一个函数范围变量的示例:

  function bar() {
    var i = 10;
    while (i > 0) {
      var j = 1;
      while (j > 0) {
        var i = 1; // This is the same i as above
        j--;
        i--;
      }
    }
  }

上面的函数将进入嵌套循环一次,因为 i 的第二个 “声明” 仅将值 1 赋给它。或许这不符合程序员的意图,但其中已考虑到了所有方面。

从 ECMAScript 6 开始,使用 letconst 声明的变量都具有代码块范围,所以在当前表达式代码块结束时,而不是函数结束时,它们将超出范围。重写前面的代码,使用 let 代替 var,这将得到预期的行为:

  function bar() {
  let i = 10;
  while (i > 0) {
    let j = 1;
    while (j > 0) {
      let i = 1; // Different i
      j--;
      i--;
    }
  } // This loop will execute 10 times, as intended
}

更广义地讲,代码块范围意味着使用 letconst 声明的变量的行为将与其他基于 C 的语言中的变量相同。与 ECMAScript 目前为止管理变量的奇怪方式相比,这是一大改进。

解构赋值

还有最后一个与变量声明相关的变化,而且该变化是 ECMAScript 6 中最重要的更新之一。解构赋值(destructuring assignment) 允许从一个对象或数组向多个变量赋值。实质上,该操作将数组或对象 “解构” 为它的构成部分。

或许通过操作而不是通过文字才能更好地了解什么是解构。给定一个类似这样的数组

    let names = ["Ted", "Jenni", "Athen"];

您可以使用变量声明的解构形式,将各个数组元素分解为独立的变量,就像这样:

    let [ted, jenni, athen] = names;
    console.log(ted, jenni, athen); // prints "Ted Jenni Athen"

请注意,数组将变量声明放在括号中。这些括号告诉 ECMAScript,等号右侧需要一个数组。如果数组中的元素比声明的变量要多,那么数组中剩余的元素将被丢弃。(当然,这些值仍在数组中 — 数组的值被复制到变量中,最初的数组不受影响。)如果数组中的值比声明的变量少,ECMAScript 将为所有剩余的变量填入值 “undefined”。

在许多方面,解构赋值的数组形式只是相同操作的旧形式的语法糖:

    var ted = names[0];
    var jenni = names[1];
    var athen = names[2];

解构版本更短,而且可能表达得更清楚。

对象中的解构

解构赋值的最适用用例之一是,从一次正则表达式解析中提取值:

    var url = "http://www.newardassociates.com/#/speaking";
    var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
    var [, protocol, fullhost, fullpath] = parsedURL;
    console.log(protocol); // "http"

也可对对象执行类似的解构类型。先给定一个简单对象,然后就可以执行以下类似操作:

    let point = { x:2, y: 5 };
    let {x, y} = point;
    console.log(x, y); // prints 2, 5

在这里,解构发生在对象上,并通过在右侧对象中找到同名的变量来绑定变量。也就是说,即使我们以相反顺序编写变量,x 的值仍会收到 point.xy 仍会收到 point.y

    let point = { x:2, y: 5 };
    let {y, x} = point;
    console.log(x, y); // prints 2, 5

如果出于某种原因,您不关心对象字段名称是否匹配,可以使用字段式语法来重命名字段,左侧表示要匹配的名称,右侧表示实际声明的变量名:

    let {y: pty, x: ptx} = point;
    console.log(ptx, pty); // prints 2, 5

这使您在解构对象时能够对变量命名有更多的控制权。

解构也可以在多个层上发生;例如,矩形通常使用两点来表示:

    let rect = { lowerLeft: { x:0, y:0 }, upperRight: { x:3, y:4} };

如果您想提取矩形的 4 个值,可以将两个 x/y 对拉入 4 个不同的变量中,就像这样:

    let { lowerLeft: { x:llx, y: lly }, upperRight: { x:urx, y:ury } } 
        = rect;
    console.log(llx, lly, urx, ury); // prints "0 0 3 4"

在这种情况下,“lowerLeft” 和 “upperRight” 不是实际的变量;它们是占位符,表示其子字段应如何绑定到被解构的对象中具有对应名称的字段。本例中仅实际引入了变量 llxllyurxury

就目前而言,这些已足以帮助您理解解构了,但我们不会止步于此。在未来的文章中,您将了解如何在方法参数内使用这种新语法。

结束语

我们才刚刚开始着手调查 ECMAScript 6 带来的变化,尽管如此,我们已感觉到该语言与之前的版本之间的区别。最新一轮规范具有新的成熟水平,更正了困扰开发人员十年的一些缺陷。

ECMAScript 6 中的一些更改将可直接采用和快速应用,例如,可以轻松开始使用 letconst 代替 var。其他变化将需要更多时间来集成,比如重音符字符串语法。幸运的是,所有 ECMAScript 引擎仍向后兼容,所以仍然还有时间进行调整。

谈到时间,我们这里的介绍到底为止了。但在您准备好探索 ECMAScript 6 中的函数更新时,请阅读 第 2 部分


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=1039755
ArticleTitle=面向 JavaScript 开发人员的 ECMAScript 6 指南,第 1 部分: 新 JavaScript 中的变量声明等功能
publish-date=11142016