JavaScript 转义字符JSON parse错误研究

   2023-03-08 学习力0
核心提示:目录JSON 字符串转换为 JavaScript 对象找到 Scanner::Scan() 函数关键代码:ScanString() 函数JSON 字符串转换为 JavaScript 对象JSON.parse 将一个 JSON 字符串转换为 JavaScript 对象。JSON.parse('{"hello":"\world"}')以上代码输出:{  hello: "world"}

JSON 字符串转换为 JavaScript 对象

JavaScript 转义字符JSON parse错误研究

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。

好奇心不死,继续试,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 的字符串解析规则:

JavaScript 转义字符JSON parse错误研究

根据这个规则,我们解析一下 "\hello",第 1 个字符是反斜杠(\),所以在引号后面走最下面的分支(红线标注):

JavaScript 转义字符JSON parse错误研究

第 2 个字符是 h,但是反斜杠后面只有 9 条路,这个不属于任何一条路,所以这个是个非法字符。

不只是 JSON,在很多语言中都会抛出类似 Error:(7, 27) Illegal escape: '\h' 的错误。

但是不知道为什么 JavaScript 偏偏可以解析这个非法转义字符,而解决方式也很暴力:直接忽略。

在 es 规范我没有找到具体的章节。去看看 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错误的资料请关注其它相关文章!

原文地址:https://juejin.cn/post/6844903511222648845
 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • JavaScript翻转字符串方法 js翻转一个字符串
    先把字符串转化成数组String.prototype.split(),再借助数组的reverse方法翻转数组顺序(Array.prototype.reverse()),然后把数组转化成字符串。使用的API方法:String.prototype.split(' ')使用指定的分隔符字符串将一个String对象分割成字符串数组Array.prot
    03-08
  • javascript常见面试题之一:将字符串'get-
    var str='get-element-by-id'; function strToupper(str) { //利用split将字符串分割成数组var arr= str.split('-');for (var i = 1; iarr.length; i++) {      //1.利用for循环获取数组的每个元素,2.用charAt(0)获取每个元素的第一个字符;3.用substr
    03-08
  • JavaScript清除空格、换行,把双引号转换成单引号
    JavaScript清除空格、换行,把双引号转换成单引
    1、页面   2、源码 1 !DOCTYPE2 html3 head4meta charset="utf-8"5 title清除字符串的空格和双引号/title6 style type="text/css"7 textarea{8 padding:10px;9 font-size:18px; 10 width:100%; 11 resize:none; 12 } 13 .main{ 14 padding:40px 10px; 15
    03-08
  • javaScript的Date函数 javascript date(
    1、获取当前时间  Date()获取到的时间是当前设备的显示的时间,开发中要考虑到用户的设备时间是否正确let nowTime = new Date(); // 获取当前时间  把data时间转换成常规格式scriptlet getTimeNow = () = {let nowTime = new Date(); // 获取当前时间——
    03-08
  • JavaScript中什么是闭包
    JavaScript中什么是闭包
    概念:当一个内部函数被调用,就会形成闭包,闭包就是能够读取其他函数内部变量的函数  就是一个函数去访问了另外一个函数的中的变量的函数例子:!DOCTYPE htmlhtmlheadmeta charset="UTF-8"title闭包/title/headbodyscript type="text/javascript"//允许函
    03-08
  • 关于Javascript中通过实例对象修改原型对象属性
    Javascript中的数据值有两大类:基本类型的数据值和引用类型的数据值。基本类型的数据值有5种:null、undefined、number、boolean和string。引用类型的数据值往大的说就1种,即Object类型。往细的说有:Object类型、Array类型、Date类型、Regexp类型、Functio
    03-08
  • javascript中defer的作用(转)
    script src=".js.js" defer/scriptdefer的作用就是作用是文档加载完毕了再执行脚本,这样回避免找不到对象的问题 加上 defer 等于在页面完全在入后再执行,相当于 window.onload ,但应用上比 window.onload 更灵活! defer是脚本程序强大功能中的一个“无名英
    03-08
  • JavaScript Array map() 方法
    JavaScript Array map() 方法
    一、定义map() 方法返回一个新数组,不会改变原始数组。同时新数组中的元素为原始数组元素调用函数处理后的值,并按照原始数组元素顺序依次处理元素。注意:map() 不会对空数组进行检测。二、语法array.map(function(currentValue,index,arr), thisValue)四、
    03-08
  • JavaScript中的arguments,callee,caller(转)
    在提到上述的概念之前,首先想说说javascript中函数的隐含参数:argumentsArguments该对象代表正在执行的函数和调用它的函数的参数。[function.]arguments[n]参数function:选项。当前正在执行的 Function 对象的名字。 n :选项。要传递给 Function 对象的从
    03-08
  • 前台javascript排序 js排序的几种方式
     script type="text/javascript"$(function () {$('.Sorthead-ShowUp').click(function () { var filed = $(this).attr("name"); $(".issorting").removeClass("issorting"); $(this).addClass("issorting"); D
    03-08
点击排行