JavaScript 轉(zhuǎn)義字符JSON parse錯誤研究
JSON 字符串轉(zhuǎn)換為 JavaScript 對象

JSON.parse 將一個 JSON 字符串轉(zhuǎn)換為 JavaScript 對象。
JSON.parse('{"hello":"\world"}')
以上代碼輸出:
{
hello: "world"
}
是一個 JavaScript 對象,但是仔細觀察會發(fā)現(xiàn),"\world" 變成了 "world"。
那么我們繼續(xù)運行如下代碼:
JSON.parse('{"hello":"\\world"}')
出拋出異常:
VM376:1 Uncaught SyntaxError: Unexpected token w in JSON at position 11
at JSON.parse (<anonymous>)
at <anonymous>:1:6
Unexpected token w。
好奇心不死,繼續(xù)試,3 個反斜杠:
JSON.parse('{"hello":"\\\world"}')
結(jié)果是:
VM16590:1 Uncaught SyntaxError: Unexpected token w in JSON at position 11
at JSON.parse (<anonymous>)
at <anonymous>:1:6
繼續(xù),4 個反斜杠:
JSON.parse('{"hello":"\\\\world"}')
結(jié)果正常:
{
hello: "\world"
}
- 1個,"world"
- 2個,Error
- 3個,Error
- 4個,"\world"
- 5個,"\world"
- 6個,Error
- 7個,Error
- 8個,"\\world"
- 。。。
我們換個思路,把 JSON.parse 去掉,只輸出 JavaScript 字符串:
> 'hello' "hello" > '\hello' "hello" > '\\hello' "\hello" > '\\\hello' "\hello" > '\\\\hello' "\\hello"
問題大概找到了。
把上面的規(guī)則帶入到之前的 JSON.parse 代碼,問題就解決了。
我們看看 JSON 的字符串解析規(guī)則:

根據(jù)這個規(guī)則,我們解析一下 "\hello",第 1 個字符是反斜杠(\),所以在引號后面走最下面的分支(紅線標注):

第 2 個字符是 h,但是反斜杠后面只有 9 條路,這個不屬于任何一條路,所以這個是個非法字符。
不只是 JSON,在很多語言中都會拋出類似 Error:(7, 27) Illegal escape: '\h' 的錯誤。
但是不知道為什么 JavaScript 偏偏可以解析這個非法轉(zhuǎn)義字符,而解決方式也很暴力:直接忽略。
在 es 規(guī)范我沒有找到具體的章節(jié)。去看看 V8 是怎么解析的吧。
引擎讀取 JavaScript 源碼后首先進行詞法分析,文件 /src/parsing/scanner.cc 的功能是讀取源碼并解析(當前最新版 6.4.286)。
找到 Scanner::Scan() 函數(shù)關(guān)鍵代碼:
case '"': case '\'': token = ScanString(); break;
是一個很長的 switch 語句:如果遇到雙引號(")、單引號(')則調(diào)用 ScanString() 函數(shù)。
簡單解釋下:以上代碼是 C++ 代碼,在 C++ 中單引號是字符,雙引號是字符串。所以表示字符時,雙引號不需要轉(zhuǎn)義,但是單引號需要轉(zhuǎn)義;而表示字符串時,正好相反。此處的 C++ 轉(zhuǎn)義并不是我們今天要研究的轉(zhuǎn)義。
ScanString() 函數(shù)
在 ScanString() 函數(shù)中我們也只看重點代碼:
while (c0_ != quote && c0_ != kEndOfInput && !IsLineTerminator(c0_)) {
uc32 c = c0_;
Advance();
if (c == '\\') {
if (c0_ == kEndOfInput || !ScanEscape<false, false>()) {
return Token::ILLEGAL;
}
} else {
AddLiteralChar(c);
}
}
if (c0_ != quote) return Token::ILLEGAL;
literal.Complete();
如果已經(jīng)到了末尾,或者下 1 個字符是不能轉(zhuǎn)義的字符,則返回 Token::ILLEGAL。那么我們看看 ScanEscape 是不是返回了 false 呢?
template <bool capture_raw, bool in_template_literal>
bool Scanner::ScanEscape() {
uc32 c = c0_;
Advance<capture_raw>();
// Skip escaped newlines.
if (!in_template_literal && c0_ != kEndOfInput && IsLineTerminator(c)) {
// Allow escaped CR+LF newlines in multiline string literals.
if (IsCarriageReturn(c) && IsLineFeed(c0_)) Advance<capture_raw>();
return true;
}
switch (c) {
case '\'': // fall through
case '"' : // fall through
case '\\': break;
case 'b' : c = '\b'; break;
case 'f' : c = '\f'; break;
case 'n' : c = '\n'; break;
case 'r' : c = '\r'; break;
case 't' : c = '\t'; break;
case 'u' : {
c = ScanUnicodeEscape<capture_raw>();
if (c < 0) return false;
break;
}
case 'v':
c = '\v';
break;
case 'x': {
c = ScanHexNumber<capture_raw>(2);
if (c < 0) return false;
break;
}
case '0': // Fall through.
case '1': // fall through
case '2': // fall through
case '3': // fall through
case '4': // fall through
case '5': // fall through
case '6': // fall through
case '7':
c = ScanOctalEscape<capture_raw>(c, 2);
break;
}
// Other escaped characters are interpreted as their non-escaped version.
AddLiteralChar(c);
return true;
}
這個函數(shù)只有 2 處返回了 false。
1、如果轉(zhuǎn)義字符后面是 u,u 后面不是 Unicode 字符時,返回 false
2、如果轉(zhuǎn)義字符后面是 x,x 后面不是十六進制數(shù)字時,返回 false
也就是說:'\u'、'\uhello'、'\u1'、'\x'、'\xx' 都拋出異常。
Uncaught SyntaxError: Invalid Unicode escape sequence
或
Uncaught SyntaxError: Invalid hexadecimal escape sequence
而其它非轉(zhuǎn)義字符,都直接執(zhí)行了后面的代碼:
AddLiteralChar(c); return true;
前面的注釋也說明了這一點:
Other escaped characters are interpreted as their non-escaped version.
其他轉(zhuǎn)義字符被解釋為對應的非轉(zhuǎn)義版本。
綜上,問題的根源就是 JavaScript 和 JSON 對轉(zhuǎn)義字符的處理方式不同,導致了難以發(fā)現(xiàn)的 bug。JSON 遇到不能轉(zhuǎn)義的字符直接拋出異常,而 JavaScript 遇到不能轉(zhuǎn)義的字符直接解釋為對應的非轉(zhuǎn)義版本。
以上就是JavaScript 轉(zhuǎn)義字符JSON parse錯誤研究的詳細內(nèi)容,更多關(guān)于JavaScript JSON parse錯誤的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用snowfall.jquery.js實現(xiàn)愛心滿屏飛的效果
這篇文章主要介紹了使用snowfall.jquery.js實現(xiàn)愛心滿屏飛的效果的相關(guān)資料,需要的朋友可以參考下2017-01-01
微信小程序嵌入H5頁面(webview)的基本用法和父子傳參數(shù)詳細說明
微信小程序中嵌入H5頁面通常指的是在小程序中使用Web-view組件來加載外部的網(wǎng)頁,下面這篇文章主要給大家介紹了關(guān)于微信小程序嵌入H5頁面(webview)的基本用法和父子傳參數(shù)的相關(guān)資料,需要的朋友可以參考下2024-08-08

