【译】Javascript转义字符

原文地址:https://mathiasbynens.be/notes/javascript-escapes

最近写了2篇文章,包括 HTML中的字符实体CSS中的转义序列, 我表示很有兴趣对Javascript的转义字符也研究一下。

1、字符码,码点,代码单元(Character codes, code points, and code units)

一个码点(code point也被称为字符码: character code)是一个特定Unicode字符的数值化表示。
例如,版权符号©的字符码是169,用十六进制写就是0xA9。

在Javascript里面,方法 String#charCodeAt() 能够获得任何字符的Unicode码点(最大到U+FFFF,也就是说最大的码点是0XFFFF,十进制就是65535)。
由于 JavaScript 内部使用UCS-2编码,更高的码点是由一对“surrogate”伪字符表示的。 要获得更高码点的实际字符码,你需要做 一些额外的工作

基本上说,JS使用代码单元而不是码点。

跳出上面的内容,让我们先看看JS字符串中不同类型的转义字符。

 

2、单字符转义序列(Single character escape sequences)

有一些 保留的 单字符转义序列:

  • \b: backspace (U+0008 退格)
  • \f: form feed (U+000C FORM FEED)
  • \n: line feed (U+000A LINE FEED)
  • \r: carriage return (U+000D CARRIAGE RETURN)
  • \t: horizontal tab (U+0009 CHARACTER TABULATION)
  • \v: vertical tab (U+000B LINE TABULATION)
  • \0: null character (U+0000 NULL) (only if the next character is not a decimal digit; else it’s an octal escape sequence)
  • \': single quote (U+0027 APOSTROPHE)
  • \": double quote (U+0022 QUOTATION MARK)
  • \\: backslash (U+005C REVERSE SOLIDUS)

所有的转义字符可以通过下面的一个正则表达式记忆:\\[bfnrtv0'"\\]

注意反斜杠 \ 扮演特殊字符的角色,仅仅有一个例外:

'abc\
def' == 'abcdef'; // true

紧接在 \ 后面的新行不是一个转义字符, 而是一个行连接装置 LineContinuation新行没有变成字符串的一部分。这是一个简单的方法让一个字符串跨越多行(可能是为了方便代码编辑), 同时字符串本身并没有包含任何新行字符. 我想你可能认为一个紧接在 \ 后面的新行如同就是对一个空字符进行转义。

反斜线 \ 作为连接符不是符合标准的写法,但是很多浏览器已经实现了;犀牛书上提到ES3不支持这种多行的写法,ES5才支持;
反斜杠后面不能再添加空格了,容易出错;
还有缺点是:这种写法给js代码压缩器带来诟病。 
同时需要注意的是,如上文所描述,作为连接符的反斜线后面接着新行符(因为换行了),但这里反斜线和新行符都不作为字符字面量的一部分。

不是特殊字符也能够进行转义 (例如 '\a' == 'a'), 但这当然是不需要的。然而,在 Unicode转义序列 外面使用 \u,或者在 十六进制的转义 外面使用 \x,这在 规范中是不容许的, 并且这可能导致一些引擎抛出一个语法错误。

注意: IE < 9 中的'\v'就当作是'v',不表示垂直制表符 ('\x0B')。在意跨浏览器兼容性的话,就直接使用 \x0B 代替 \v
  另外要注意的是\v和\0转义是不容许在JSON字符串中使用的。


3、八进制的转义序列(Octal escape sequences)

任何字符码低于256的字符(也就是说包含在扩展ASCII范围内的任意字符)能通过它的八进制编码进行转义,使用前缀 \ 。(注意 十六进制转义 的字符范围也和这里的一致)。

使用相同的例子,版权符号('©')的字符码是169,八进制是251,所以你能够八进制转义方法写成 ‘\251’。

八进制转义方法能包含2个、3个或者4个字符。'\1', '\01''\001'是等价的,不需要强制补零。
当然,如果八进制转义(比如 '\1')是一个大字符串的一部分,并且这个转义字符后面直接跟着一个范围在 [0-7] 的字符(比如1),那么这个字符将被认为是转义序列的一部分,直到最多可以包含3个字符!换句话说,'\12' (等价于 '\012' )不同于 '\0012' ( '\0012' 等价于 '\001' 接了一个未转义的数字2)。所以,简单的对八进制转义进行补零,能够避免一些问题。

注意有一个意外的情况,就是它自己,\0 并不是一个八进制转义。它看起来就像1,并且它甚至等于 \00 和 \000 (这2个都是八进制转义) - 除非它后面紧接十进制数码,不然都还是作为 单字符转义序列. 或者, 照此规范的格式: EscapeSequence :: 0 [lookahead ∉ DecimalDigit]. 使用正则表达式来定义这个八进制转义语法,很简单: \\(?:[1-7][0-7]{0,2}|[0-7]{2,3})

注意 八进制转义已经在ES5中不推荐使用:

Past editions of ECMAScript have included additional syntax and semantics for specifying octal literals and octal escape sequences. These have been removed from this edition of ECMAScript. This non-normative annex presents uniform syntax and semantics for octal literals and octal escape sequences for compatibility with some older ECMAScript programs.

额外的, 在严格模式下会产生语法错误:

A conforming implementation, when processing strict mode code (see 10.1.1), may not extend the syntax of EscapeSequence to include OctalEscapeSequence as described in B.1.2.

TL;DR 不要使用八进制转义; 使用 十六进制转义 代替.

 

4、十六进制转义序列(Hexadecimal escape sequences)

任何字符码低于256的字符(也就是说包含在扩展ASCII范围内的任意字符)能通过它的十六进制编码进行转义,使用前缀 \x。(注意 octal escapes 转义的字符范围也和这里的一致)。

十六进制转义有4个字符长度。在\x后面需要接2个字符。如果十六进制字符码仅仅只有一个字符长度,需要前补0。

例如,版权符号 ('©') 的字符码是169,十六进制是A9,用十六进制,你能用 '\xA9'

转义的十六进制部分是大小写不敏感的,也就是说,'\xa9'和'\xA9'是等价的。

如果使用正则表达式来定义十六进制转义的语法,如下: \\x[a-fA-F0-9]{2}.

有一点需要分辨一下,规范 上提到这种转义序列是“十六进制化的”(“hexadecimal”),而下面要说的 Unicode转义 也是使用十六进制(注:当然这2个是不同的转义,但都是和十六进制相关)。

 

5、Unicode转义序列(Unicode escape sequences)

任何字符码低于65535的字符能通过它的十六进制字符码进行转义,使用前缀 \u。 (如前面提到的, 更高的字符码需要一对“surrogate”字符)

Unicode转义有6个字符长。也就是说在\u后面需要接4个字符。如果十六进制字符仅仅只有1个、2个或者3个长,需要在前面补足0(比如说 \u0001)。

版权符号('©')的字符码是169, 十六进制表示为A9 , 所以你能写作 '\u00A9'. 类似的, '♥' 能被写成 '\u2665'

这种字符转义类型的十六进制部分是大小写不敏感的;也就是说,'\u00a9''\u00A9' 是等价的。

使用正则表达式的方式来定义这种转义语法,如下: \\u[a-fA-F0-9]{4}.

注意:不像 一些简单的转义, Unicode转义是JSON规范仅支持的一种。

6、ECMAScript 6: Unicode码点转义(Unicode code point escapes)

ECMAScript 6 引入 了一种新的字符转义序列, 命名为 Unicode code point escapes。同时,ES6也定义了 String.fromCodePointString#codePointAt, 这些方法都接受码点而不是 类似UCS-2/UTF-16的代码单元.

如果完全实现的话,任何字符都可以用其码点的十六进制化的值来转义,使用前缀 \u{ 和后缀 }。这样,支持的码点将达到 0x10FFFF, 也即是Unicode定义的最大的码点了。

Unicode码点转义至少包含5个字符,至少一个十六进制的字符被包含在\u{…}中。这其中,十六进制数码的个数没有上限(如:'\u{000000000061}' == 'a'),但是实践中你可能不需要超过6以上的了,除非你弄个不必要的补零。

那个啥符号(The tetragram for centre symbol 𝌆)的码点是 U+1D306, 则可以写成 \u{1D306}. 作为比较,如果你使用 Unicode转义 去表达这个符号,你需要使用surrogate对: '\uD834\uDF06'

是太玄经中的某个符号,参考:U+1D306

这种字符转义类型的十六进制部分是大小写不敏感的;也就是说,'\u{1d306}''\u{1D306}' 是等价的。

你可以用下面的正则来表示Unicode码点转义的语法: \\u\{([0-9a-fA-F]{1,})\}

7、控制转义序列(Control escape sequences)

在正则表达式中 (不是在字符串中!),任何大于0而小于26的字符码都可以使用caret notation字符转义,使用前缀 \c 

caret notation参考:https://en.wikipedia.org/wiki/Caret_notation ,简单说,这个标识专门针对ASCII编码中不可打印的控制字符,比如控制字符EOT的值是4,那么对应的标识就是^D,因为字母D在字母表中排第4位!那么转成caret notation就是\cD

控制转义有3个字符长, \c 后面必须带一个字符。

例如,换行符U+000A 对应的caret notation是 ^J
所以,匹配这个换行符的有效的正则表达式可以是 /\cJ/, 例: /\cJ/.test('\n') == true

\c后面的字符是不区分大小写的,所以 /\cJ//\cj/ 是等价的。

下面所列的是所有的控制转义序列和对应的控制字符:

转义序列Unicode 码点
\cA or \ca U+0001 START OF HEADING
\cB or \cb U+0002 START OF TEXT
\cC or \cc U+0003 END OF TEXT
\cD or \cd U+0004 END OF TRANSMISSION
\cE or \ce U+0005 ENQUIRY
\cF or \cf U+0006 ACKNOWLEDGE
\cG or \cg U+0007 BELL
\cH or \ch U+0008 BACKSPACE
\cI or \ci U+0009 CHARACTER TABULATION
\cJ or \cj U+000A LINE FEED (LF)
\cK or \ck U+000B LINE TABULATION
\cL or \cl U+000C FORM FEED (FF)
\cM or \cm U+000D CARRIAGE RETURN (CR)
\cN or \cn U+000E SHIFT OUT
\cO or \co U+000F SHIFT IN
\cP or \cp U+0010 DATA LINK ESCAPE
\cQ or \cq U+0011 DEVICE CONTROL ONE
\cR or \cr U+0012 DEVICE CONTROL TWO
\cS or \cs U+0013 DEVICE CONTROL THREE
\cT or \ct U+0014 DEVICE CONTROL FOUR
\cU or \cu U+0015 NEGATIVE ACKNOWLEDGE
\cV or \cv U+0016 SYNCHRONOUS IDLE
\cW or \cw U+0017 END OF TRANSMISSION BLOCK
\cX or \cx U+0018 CANCEL
\cY or \cy U+0019 END OF MEDIUM
\cZ or \cz U+001A SUBSTITUTE

控制转义序列语法的正则表示: \\c[a-zA-Z]

8、A tool for character escapes

我写了一个Javascript字符转义工具,这个工具结合了这些不同种类的转义(除了不推荐使用的八进制转义)并返回了最小可能的结果字符串。试下它吧: mothereff.in/js-escapes!

JavaScript escapes tool

你可以用它转义任何字符,但是注意提供了一个选项是:仅转义非ASCII字符和不可打印的ASCII字符(这个选项可能是最常用的)。通过这个方法,你能很方便的转换字符串,如 'Ich ♥ Bücher'转换成等价的 'Ich \u2665 B\xFCcher' 。之前我在做 Punycode.js unit tests 的时候, 这个工具节省了我很多时间。

需要在你的Javascript应用中转义字符串吗? The JavaScript library that powers this tool 可以在Github上找到.

posted @ 2015-10-15 08:42  浮云浪子  阅读(620)  评论(0编辑  收藏  举报