JSON.parse
是浏览器内置的API,像v8内部就是用c++实现的。JSON.parse
在前端是个很常用的工具,用来将字符串转为json格式的数据。今天先来看个简单版的数组解析。其中使用到了编译原理中状态机的概念,没听过不要紧,看图就行。
数组类型的状态机
这就是数组类型的状态机图,只要能在这个图中从开始走到结束就是正确的数组类型。从一个简单的例子来看,[1,2,3],在状态图中描述的路径就是: 开始
-> [
-> num
-> ,
-> num
-> ,
-> num
-> ]
-> 结束
。这是数组只有一层的情况。
可以看到在[
上面还有一个自循环的箭头,表明[
的下一个字符还是[
,]
同理。这就是嵌套数组的情况,嵌套数组还可以通过,
-> [
的路径实现。在这个状态机里为了简单,没有标明[
和]
的数量,实际情况下只有[
和]
的数量和位置满足特定的要求,才会是一个合格的嵌套数组。
在这里我们先考虑输入是合理的情况。以[1, [2, [3]], 4]
嵌套数组为例,它在状态图中的路径是: 开始
-> [
-> num
-> ,
-> [
-> num
-> [
-> num
-> ]
-> ]
-> ,
-> num
-> ]
-> 结束
。
实现数组解析
首先是一个基本架构
function parseArrays(str){
let i = 0;
// TODO
}
初始化i作为遍历字符串的下标,当i到达字符串结束位置时,就结束遍历。
在状态图中没有出现空格
的状态,但是其实每次都需要处理前后的空格。接下来就写几个函数分别处理空格
,,
和num
数字。
function skipWhiteSpace(){
// 遇到空格,直接将i后移一个位置
while(str[i] === ' ') ++i;
}
function eatComma(){
// 首先跳过逗号前面的空格
skipWhiteSpace();
// 遇到空格,将i后移
if(str[i] === ','){
++i;
// 跳过逗号后面的空格
skipWhiteSpace();
}
}
function parseNum(){
// 这里只处理正整数,记录数字的起始位置
let first = i;
while(str[i] >= '0' && str[i] <= '9') ++i;
// 用parseInt处理从起始位置到结束位置的num区域
return parseInt(str.substr(first, i - first));
}
接下来就是对数组的一个处理了,在遇到嵌套数组时,使用递归调用的方式处理。在处理完数组后,需要++i
移动到下一个位置,就是为了在处理完嵌套的数组后,回到原来的数组处理逻辑。
function parseArray(){
// 跳过开始的空格
skipWhiteSpace();
// res存储结果
let res = [];
// 以`[`为开始
if(str[i] === '['){
i++;
let initial = true;
while(str[i] !== ']'){
// 开始的时候,不能有逗号,之后处理逗号
if(!initial){
eatComma();
}
// 遍历过程中又遇到`[`,说明是嵌套数组,用递归的方式处理
if(str[i] === '['){
let subArr = parseArray();
res.push(subArr);
}else{
// 处理遇到的数字
let value = parseNum();
// 数字有效时,存入结果中
if(value) res.push(value);
initial = false;
}
}
}
// 移动到`]`的下一个位置,跳回嵌套数组上一层的处理逻辑
++i;
return res;
}
完整的实现代码如下:
let str = '[1,[2, [[3], 4]],5,6]'
function parseArrays(str){
let i = 0;
function skipWhiteSpace(){
// 遇到空格,直接将i后移一个位置
while(str[i] === ' ') ++i;
}
function eatComma(){
// 首先跳过逗号前面的空格
skipWhiteSpace();
// 遇到空格,将i后移
if(str[i] === ','){
++i;
// 跳过逗号后面的空格
skipWhiteSpace();
}
}
function parseNum(){
// 这里只处理正整数,记录数字的起始位置
let first = i;
while(str[i] >= '0' && str[i] <= '9') ++i;
// 用parseInt处理从起始位置到结束位置的num区域
return parseInt(str.substr(first, i - first));
}
function parseArray(){
// 跳过开始的空格
skipWhiteSpace();
// res存储结果
let res = [];
// 以`[`为开始
if(str[i] === '['){
i++;
let initial = true;
while(str[i] !== ']'){
// 开始的时候,不能有逗号,之后处理逗号
if(!initial){
eatComma();
}
// 遍历过程中又遇到`[`,说明是嵌套数组,用递归的方式处理
if(str[i] === '['){
let subArr = parseArray();
res.push(subArr);
}else{
// 处理遇到的数字
let value = parseNum();
// 数字有效时,存入结果中
if(value) res.push(value);
initial = false;
}
}
}
// 移动到`]`的下一个位置,跳回嵌套数组上一层的处理逻辑
++i;
return res;
}
// 直接调用并返回parseArray的结果
return parseArray();
}
let r = parseArrays(str);
上面的程序可以在没有异常的情况下跑通,在之后的完整的JSON.parse原理中也将处理异常。