首页 > 代码库 > [译]理解 ECMAScript 6 基本知识(未完)

[译]理解 ECMAScript 6 基本知识(未完)

基本知识

ECMAScript 6在ECMAScript 5之上做了大量的改变。一些改变很大,比如添加新的类型或者语法,而其它的非常小,提供了语言之上的渐进改进。这个章节包含了那些渐进改进,它们可能不会获得很多关注但提供了一些重要的功能,使得某些类型的问题更容易解决。

更好的Unicode支持

ECMAScript 6之前, JavaScript是完全基于16位字符编码的想法。所有的字符串属性和方法,比如length与charAt() ,是基于每一个16位序列表示一个字符这一想法。ECMAScript 5 允许JavaScript 引擎来决定使用UCS-2 或者UTF-16 (两个编码都使用16位编码单元,所有的操作是一样的)这两个编码中的哪一个。确实世界上所有的字符串曾一度适合16位,然而情况已不再如此。就Unicode对世界上的每一个字符提供全球唯一标识的既定目标而言,保持16位是不可能的。这些全球唯一标识,被称为码点,只是简单的从0(你可能会认为这些是字符编码,但是有些微差异)开始的数字。字符编码负责将码点编码成内部一致的编码单元 。UCS-2 有一对一的映射,码点到编码单元,UTF-16则更加灵活。

在 UTF-16中,第一个 2^16 码点被表示为单个16位编码单元。称为基本多文种平面Basic Multilingual Plane (BMP)。一切超出这个范围的东西被认为是辅助平面,这里的码点不能再被表示为仅仅16位。UTF-16通过引入代理对解决了这个问题,在这里,一个单一的码点被表示为两个16位编码单元。这意味着字符串中的任意一个单一字符可以是一个编码单元(对于MBP,总共16位)或者两个编码单元 (对于复制平面字符,总共16位)。

ECMAScript 5保持所有的操作都工作在16位编码单元,这意味着你可以从包含了代理对的字符串中得到意想不到的结果。比如:

var text = "??";console.log(text.length);           // 2console.log(/^.$/.test(text));      // falseconsole.log(text.charAt(0));        // ""console.log(text.charAt(1));        // ""console.log(text.charCodeAt(0));    // 55362console.log(text.charCodeAt(1));    // 57271

在这个例子中,一个单一的Unicode字符用代理对来表示,因此,JavaScript字符串操作将这个字符串作为两个16位的字符处理。这意味着长度为2,常规的正则表达式尝试匹配单一字符失败,charAt()也不能返回一个有效的字符串。 charCodeAt()方法为每一编码单元返回对应的16位数字,这是你在ECMAScript5中能得到的最接近真实值的东西。

ECMAScript 6在UTF-16中执行字符串编码。字符编码的规范意味着这个语言现在可以支持功能性的设计来专门处理代理对。

codePointAt() 方法

完全支持UTF-16方法的第一个例子是codePointAt(),它可以用来检索字符串中映射到给定位置的Unicode码点。这个方法接收编码单元的位置(不是字符位置)作为参数然后返回一个整型值。

var text = "??a";console.log(text.charCodeAt(0));    // 55362console.log(text.charCodeAt(1));    // 57271console.log(text.charCodeAt(2));    // 97console.log(text.codePointAt(0));   // 134071console.log(text.codePointAt(1));   // 57271console.log(text.codePointAt(2));   // 97

除了非BMP字符,codePointAt()方法与charCodeAt()方法一样的结果是一样的。文本中的第一个字符是非BMP字符因此它由两个编码单元组成,这意味着整个字符串的长度是3而不是2。charCodeAt()方法仅仅返回位置0对应的第一个编码单元,而codePointAt()返回完整的码点尽管它跨越了多个编码单元。对于位置1和2,这两个方法返回相同的值(第一个字符的第二个编码单元)和(“a”)。

这个方法是判断一个给定的字符由一个编码单元组成还是两个编码单元组成的最简单的方式:

function is32Bit(c) {    return c.codePointAt(0) > 0xFFFF;}console.log(is32Bit("??"));         // trueconsole.log(is32Bit("a"));          // false

16位字符的上限由16进制FFFF表示,所以任何码点在这个数字之上的字符必定被表示为两个编码单元。

String.fromCodePoint()

当ECMAScript 提供了一个方法来做某件事, 它还会提供一个方法来做相反的事。你可以在字符串中使用codePointAt()来检索某个字符的码点,而String.fromCodePoint()为给定的码点提供一个单一字符字符串。比如:

console.log(String.fromCodePoint(134071));  // "??"

你可以把String.fromCodePoint认为是String.fromCharCode的更加完善的版本。对BMP字符而言,这两个方法返回同样的结果。唯一的不同便是当字符超出BMP范围的时候。

转义非BMP字符

ECMAScript5允许字符串包含由转义序列来表示的16位Unicode字符。转义序列是\u后面跟4位16进制值。比如,转义序列\u0061表示字母“a”:

console.log("\u0061");      // "a"

如果你试图使用数值超过FFFF(即BMP上限)的转义序列,你将得到一些令人吃惊的结果:

console.log("\u20BB7");     // "7"

因为Unicode转义序列被定义为总是有精确的四位16进制字符,ECMAScript将\u20bb7解析为两个字符: \u20BB 和 "7" 第一个字符不可打印,第二个就是数值7

ECMAScript6通过引入扩展的Unicode转义序列解决了这个问题,将16进制数字用一对花括号括起来。这使得我们可以用任何数量的十六进制字符来制定一个单一字符:

console.log("\u{20BB7}");     // "??"

使用扩展的转义序列,正确的字符会被包含在字符串中。

警告:确保你只在ECMAScript6环境中使用这个新的转义序列。在所有其他环境中,这样做会导致语法错误。你可以通过如下方法来检测和查看当前环境是否支持扩展转义序列:

function supportsExtendedEscape() {    try {        eval("‘\\u{00FF1}‘");        return true;    } catch (ex) {        return false;    }}

normalize() 方法

Unicode另一有趣的方面是不同的字符可能会因为排序或者其他基于比较的操作被视为等价。有两种方式来定义这些关系。第一,正则等价,意思是说两个序列的码点在各方面被认为是可互换的。这甚至意味着两个字符的结合对另一个字符而言可以是正则等价的。第二个关系是兼容性,意思是说两个序列的码点有不同的外观,但是在某些情况下可以互换使用。

重要的是了解,由于这些关系,两个字符串从本质上表示相同的文本但是有着各自不同码点序列的情况是可能的。举个例子,字符“æ”和字符串“ae”可以互换使用尽管它们有着不同的码点。在JavaScript中,这两个字符串不相等除非它们以某种方式规范化。

ECMAScript6通过字符串的新方法normalize()支持四位Unicode的标准化规范格式。这个方法选择性地接受一个单一的参数,"NFC" (默认), "NFD", "NFKC"或"NFKD"中的一个。解释这四种形式的区别超出了本书范围。 只是要记住,为了使用,你必须将这两个字符串以相同的形式进行标准化。比如:

var normalized = values.map(function(text) {    return text.normalize();});normalized.sort(function(first, second) {    if (first < second) {        return -1;    } else if (first === second) {        return 0;    } else {        return 1;    }});

在这段代码中,在values数组中的字符串被转为标准化的格式,因此数组可以被适当地排序。你可以通过调用normalize()作为比较器的一部分在原始数组上完成排序:

values.sort(function(first, second) {    var firstNormalized = first.normalize(),        secondNormalized = second.normalize();    if (firstNormalized < secondNormalized) {        return -1;    } else if (firstNormalized === secondNormalized) {        return 0;    } else {        return 1;    }});

再次强调,要记住的最重要的事情是两个值必须以同样的方式标准化。下面的例子使用了默认的NFC,但是你可以简单地指定其它中的一个:

如果你之前从未担心过Unicode标准化,可能你使用这个方法的时候不会有很多。然而,知道它是可用的会有助于你在国际化的应用中工作更好。

正则表达式u标识

//待续。。。

[译]理解 ECMAScript 6 基本知识(未完)