【译】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})
。
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 includeOctalEscapeSequence
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.fromCodePoint
和 String#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!
你可以用它转义任何字符,但是注意提供了一个选项是:仅转义非ASCII字符和不可打印的ASCII字符(这个选项可能是最常用的)。通过这个方法,你能很方便的转换字符串,如 'Ich ♥ Bücher'
转换成等价的 'Ich \u2665 B\xFCcher'
。之前我在做 Punycode.js unit tests 的时候, 这个工具节省了我很多时间。
需要在你的Javascript应用中转义字符串吗? The JavaScript library that powers this tool 可以在Github上找到.