JavaScript 转义字符与 JSON.parse 解析差异分析

作者:代码拾荒时间:2026-10-25 22:26:21浏览:570

本文深入探讨 JavaScript 中 JSON.parse 与原生字符串解析在转义字符处理上的差异。通过实验不同数量的反斜杠,揭示 JSON 对非法转义严格报错而 JS 忽略转义的特性。结合 V8 引擎源码分析 ScanString 函数,解释为何 \h 等非法转义在 JS 中有效却在 JSON 中报错,帮助开发者避免此类隐蔽 Bug。

JSON 字符串转换为 JavaScript 对象

JSON 字符串转换为 JavaScript 对象

JSON.parse 函数的作用是将符合 JSON 格式的字符串解析并转换为对应的 JavaScript 对象。

JSON.parse('{"hello":"\world"}')

上述代码执行后的输出结果如下:

{ hello: "world" }

这确实是一个合法的 JavaScript 对象。但仔细观察可以发现,字符串中的 "\world" 在转换后变成了 "world",反斜杠被自动移除。

接下来我们尝试运行以下代码进行验证:

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(意外的令牌 w)。

出于好奇,我们继续测试增加反斜杠的情况,尝试使用 3 个反斜杠:

JSON.parse('{"hello":"\\\world"}')

运行结果如下:

VM16590:1 Uncaught SyntaxError: Unexpected token w in JSON at position 11 at JSON.parse (<anonymous>) at <anonymous>:1:6

继续测试,尝试使用 4 个反斜杠:

JSON.parse('{"hello":"\\\\world"}')

此时结果正常输出:

{ 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"

问题原因大致已经找到。

将上述发现的规则应用到之前的 JSON.parse 代码中,即可解决该问题。

我们需要了解 JSON 字符串的具体解析规则:

JSON 字符串转换为 JavaScript 对象

根据该规则,我们来解析字符串 "\hello"。第 1 个字符是反斜杠(\),因此需要进入引号后的最下方分支(图中红线标注部分):

JSON 字符串转换为 JavaScript 对象

第 2 个字符是 h,但反斜杠后仅定义了 9 种合法的转义路径,h 不属于其中任何一条,因此被视为非法字符。

不仅在 JSON 中,在许多其他编程语言中也会抛出类似 Error:(7, 27) Illegal escape: '\h' 的错误。

然而,JavaScript 却允许解析这种非法转义字符,其解决方式相当直接:直接忽略转义符号,将其视为普通字符。

在 ECMAScript 规范中未找到具体章节说明,因此我们查看 V8 引擎的具体实现。

引擎在读取 JavaScript 源码后首先进行词法分析,文件 /src/parsing/scanner.cc 负责读取源码并解析(当前最新版 6.4.286)。

找到 Scanner::Scan() 函数的关键代码:

case '"':
case '\'':
  token = ScanString();
break;

这是一个很长的 switch 语句:如果遇到双引号(")、单引号(')则调用 ScanString() 函数。

简单解释下:以上代码是 C++ 代码,在 C++ 中单引号是字符,双引号是字符串。所以表示字符时,双引号不需要转义,但是单引号需要转义;而表示字符串时,正好相反。此处的 C++ 转义并不是我们今天要研究的转义。

ScanString() 函数

在 ScanString() 函数中我们也只看重点代码:

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();

如果已经到了末尾,或者下 1 个字符是不能转义的字符,则返回 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;
}

这个函数只有 2 处返回了 false。

1、如果转义字符后面是 u,u 后面不是 Unicode 字符时,返回 false

2、如果转义字符后面是 x,x 后面不是十六进制数字时,返回 false

也就是说:'\u'、'\uhello'、'\u1'、'\x'、'\xx' 都抛出异常。

Uncaught SyntaxError: Invalid Unicode escape sequence

或

Uncaught SyntaxError: Invalid hexadecimal escape sequence

而其它非转义字符,都直接执行了后面的代码:

AddLiteralChar(c);
return true;

前面的注释也说明了这一点:

Other escaped characters are interpreted as their non-escaped version.

其他转义字符被解释为对应的非转义版本。

综上,问题的根源就是 JavaScript 和 JSON 对转义字符的处理方式不同,导致了难以发现的 bug。JSON 遇到不能转义的字符直接抛出异常,而 JavaScript 遇到不能转义的字符直接解释为对应的非转义版本。

以上就是JavaScript 转义字符JSON parse错误研究的详细内容,更多关于JavaScript JSON parse错误的资料请关注本站其它相关文章!

作者最新文章