JavaScript 轉(zhuǎn)義字符JSON parse錯(cuò)誤研究
JSON 字符串轉(zhuǎn)換為 JavaScript 對象
JSON.parse 將一個(gè) JSON 字符串轉(zhuǎn)換為 JavaScript 對象。
JSON.parse('{"hello":"\world"}')
以上代碼輸出:
{
hello: "world"
}
是一個(gè) JavaScript 對象,但是仔細(xì)觀察會發(fā)現(xiàn),"\world" 變成了 "world"。
那么我們繼續(xù)運(yùn)行如下代碼:
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 個(gè)反斜杠:
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 個(gè)反斜杠:
JSON.parse('{"hello":"\\\\world"}')
結(jié)果正常:
{
hello: "\world"
}
- 1個(gè),"world"
- 2個(gè),Error
- 3個(gè),Error
- 4個(gè),"\world"
- 5個(gè),"\world"
- 6個(gè),Error
- 7個(gè),Error
- 8個(gè),"\\world"
- 。。。
我們換個(gè)思路,把 JSON.parse 去掉,只輸出 JavaScript 字符串:
> 'hello' "hello" > '\hello' "hello" > '\\hello' "\hello" > '\\\hello' "\hello" > '\\\\hello' "\\hello"
問題大概找到了。
把上面的規(guī)則帶入到之前的 JSON.parse 代碼,問題就解決了。
我們看看 JSON 的字符串解析規(guī)則:
根據(jù)這個(gè)規(guī)則,我們解析一下 "\hello",第 1 個(gè)字符是反斜杠(\),所以在引號后面走最下面的分支(紅線標(biāo)注):
第 2 個(gè)字符是 h,但是反斜杠后面只有 9 條路,這個(gè)不屬于任何一條路,所以這個(gè)是個(gè)非法字符。
不只是 JSON,在很多語言中都會拋出類似 Error:(7, 27) Illegal escape: '\h' 的錯(cuò)誤。
但是不知道為什么 JavaScript 偏偏可以解析這個(gè)非法轉(zhuǎn)義字符,而解決方式也很暴力:直接忽略。
在 es 規(guī)范我沒有找到具體的章節(jié)。去看看 V8 是怎么解析的吧。
引擎讀取 JavaScript 源碼后首先進(jìn)行詞法分析,文件 /src/parsing/scanner.cc 的功能是讀取源碼并解析(當(dāng)前最新版 6.4.286)。
找到 Scanner::Scan() 函數(shù)關(guān)鍵代碼:
case '"': case '\'': token = ScanString(); break;
是一個(gè)很長的 switch 語句:如果遇到雙引號(")、單引號(')則調(diào)用 ScanString() 函數(shù)。
簡單解釋下:以上代碼是 C++ 代碼,在 C++ 中單引號是字符,雙引號是字符串。所以表示字符時(shí),雙引號不需要轉(zhuǎn)義,但是單引號需要轉(zhuǎn)義;而表示字符串時(shí),正好相反。此處的 C++ 轉(zhuǎn)義并不是我們今天要研究的轉(zhuǎn)義。
ScanString() 函數(shù)
在 ScanString() 函數(shù)中我們也只看重點(diǎn)代碼:
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 個(gè)字符是不能轉(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; }
這個(gè)函數(shù)只有 2 處返回了 false。
1、如果轉(zhuǎn)義字符后面是 u,u 后面不是 Unicode 字符時(shí),返回 false
2、如果轉(zhuǎn)義字符后面是 x,x 后面不是十六進(jìn)制數(shù)字時(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;
前面的注釋也說明了這一點(diǎn):
Other escaped characters are interpreted as their non-escaped version.
其他轉(zhuǎn)義字符被解釋為對應(yīng)的非轉(zhuǎn)義版本。
綜上,問題的根源就是 JavaScript 和 JSON 對轉(zhuǎn)義字符的處理方式不同,導(dǎo)致了難以發(fā)現(xiàn)的 bug。JSON 遇到不能轉(zhuǎn)義的字符直接拋出異常,而 JavaScript 遇到不能轉(zhuǎn)義的字符直接解釋為對應(yīng)的非轉(zhuǎn)義版本。
以上就是JavaScript 轉(zhuǎn)義字符JSON parse錯(cuò)誤研究的詳細(xì)內(nèi)容,更多關(guān)于JavaScript JSON parse錯(cuò)誤的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js動態(tài)添加的DIV中的onclick事件簡單實(shí)例
下面小編就為大家?guī)硪黄猨s動態(tài)添加的DIV中的onclick事件簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07js實(shí)現(xiàn)帶三角符的手風(fēng)琴效果
本文主要介紹了js實(shí)現(xiàn)帶三角符手風(fēng)琴效果的實(shí)例。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-03-03JavaScript簡單獲取系統(tǒng)當(dāng)前時(shí)間完整示例
這篇文章主要介紹了JavaScript簡單獲取系統(tǒng)當(dāng)前時(shí)間的方法,涉及javascript針對日期與時(shí)間的判斷以及字符串組合的相關(guān)技巧,需要的朋友可以參考下2016-08-08jquery.rotate.js實(shí)現(xiàn)可選抽獎次數(shù)和中獎內(nèi)容的轉(zhuǎn)盤抽獎代碼
這篇文章主要介紹了jquery.rotate.js實(shí)現(xiàn)可選抽獎次數(shù)和中獎內(nèi)容的轉(zhuǎn)盤抽獎代碼,需要的朋友可以參考下2017-08-08JS基于Ajax實(shí)現(xiàn)的網(wǎng)頁Loading效果代碼
這篇文章主要介紹了JS基于Ajax實(shí)現(xiàn)的網(wǎng)頁Loading效果代碼,通過時(shí)間函數(shù)結(jié)合數(shù)學(xué)運(yùn)算實(shí)現(xiàn)頁面樣式的動態(tài)變換技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10怎么理解wx.navigateTo的events參數(shù)使用詳情
這篇文章主要介紹了怎么理解wx.navigateTo的events參數(shù)使用詳情,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05