JSON-js

Douglas Crockford 是 JSON 的发明者,所以通过 DC 的代码来学习 JSON 和 parser 绝对是上乘之选。这个仓库里面有四个 JS 文件,今天我们先研究 json_parse.js。

json_parse 定义了如下 API:

json_parse(string) => object
json_parse(string, (key,value)=>newValue ) => object
复制代码

今天我们只研究第一种 API。

代码结构

用 WebStorm 打开源码方便阅读,把主要函数折叠起来,就会发现代码结构非常清晰,完整结构如下:

var json_parse = (function(){'use strict'var at;     // The index of the current charactervar ch;     // The current charactervar escape = {...}var textvar error = function(){...}var next = function(){...}var number = function(){...}var string = function(){...}var white = function(){...}var word = function(){...}var array = function(){...}var object = function(){...}var value = function(){...}return function parser(source, reciver){...}
}())
复制代码

代码首先用一个立即执行函数造出一个局部作用域,ES 6 中我们只需要用 block 和 let 代替就行了。

思路

主要思路在最后一个 parser 函数里,我们来看一下:

return function (source, reviver) {var result;text = source;at = 0;ch = " ";result = value();white();if (ch) {error("Syntax error");}return result;
};
复制代码

看起来毫无逻辑呀。

为什么我老是说「看源码的投入产出比很低」呢,因为你需要看完所有代码,才知道主要逻辑是在做什么。

还好代码不多,我看完之后总结作者的思路如下。

有三个重要的变量,ch、at 和 text

  • ch 指向一个字符(实际上是复制了字符的值,但是用指向更好理解源码),ch 默认指向一个空字符串(不要问这个空字符串有什么意义,主要是为了让代码简洁)
  • at 指向下一个字符,at 存储了下一个字符的索引(index)
  • text 包含了所有字符,也就是一个符合 JSON 语法的字符串

接下来我们定义一个动作:吃。

  • 吃,表示将 ch 指向 at 所指的字符,然后 at 指向下一个字符。
  • 吃一个空格,表示 ch 指向的字符必须是一个空格,然后吃(吃的定义见第一条);换句话说,吃一个空格的意思就是:我吃掉的字符必须是空格,不是空格就报错。
  • 吃一个{,表示我吃掉的字符必须是{,否则就报错
  • 吃一个},表示我吃掉的字符必须是},否则就报错
  • 以此类推……

好了,parser 的难点讲完了,接下来就是细节了,假设 text 是字符串 { "name" : "Frank" },一次完整的逻辑如下

  1. ch=" ",at=0, text='{ "name" : "Frank" }'
  2. 吃一个空格。由于 ch 一开始的默认值是空格,所以这个空格就被吃掉了,然后 ch 指向text 的第一个字符,at 指向 ch 后面一个字符(存下标,也就是1)。
  3. 如果 ch 是空格就继续吃,吃到 ch 不是空格为止。
  4. 发现 ch 是 {,就说明这是一个对象,生成一个空对象 object 用来存储 key 和 value。而且后面的字符就要按照对象的语法来吃。
  5. 吃空格直到遇到非空格。理论上 { 后面应该接一个 "key",所以这个非空格必须是 "
  6. 吃一个 "
  7. 吃 N 个非 " 的字符(N >= 0)
  8. 吃一个 "
  9. 把刚才吃到的 N 个字符作为一个 key,放到空对象 object 里
  10. 吃空格直到遇到非空格。理论上 "key" 后面应该接 : 所以这个非空格必须是 :
  11. 吃一个 :
  12. 吃空格直到遇到非空格。理论上冒号后面应该接 value,value 的值可以是对象、数组、字符串、bool、null 等,所以不能预期这个非空格是什么
  13. 发现是一个 ",吃掉这个 ",如果值是一个字符串
  14. 吃 N 个非 " 的字符
  15. 吃一个 "
  16. 把刚才吃到的 N 个字符作为一个 value,放到空对象 object 里
  17. 吃空格直到遇到非空格。理论上 value 后面可以接逗号或者 }
  18. 发现 ch 是 },吃掉 },说明 object 的数据已经读完了
  19. 一直吃空格,如果发现非空格,说明语法错误,报错。
  20. 将 object 返回,这个 object 就是 text 对应的数据了。

如果你能在大脑里过一遍这个过程,就可以看懂所有源码了:

var json_parse = (function(){'use strict'var at;     // The index of the current charactervar ch;     // The current charactervar escape = {...}var textvar error = function(){...}var next = 吃(){}var number = 吃一个完整的数字(){...}var string = 吃一个完整的字符串(){...}var white = 吃N个空格(){...}var word = 吃true/false/null这几个单词(){...}var array = 吃一个完整的字符串(){...}var object = 吃一个对象(){...}var value = 吃一个值,包括对象数组字符串数组bool和null(){...}return function parser(source, reciver){...}
}())
复制代码

然后我们就可以重点看主逻辑了:

return function (source, reviver) {var result;text = source;at = 0;ch = " ";result = value(); // 吃一个值white(); // 吃掉后面的空格if (ch) { // 如果空格后面还有字符,就是语法错误了error("Syntax error");}return result;
};
复制代码

也就是说主逻辑其实很简单

  1. 用 value() 吃一个值,这个值就是 text 对应的数据
  2. 继续吃掉所有空格
  3. 吃完发现还有字符(一定是非空格),就说明语法错了(画蛇添足)

接下来我们看 value() 的逻辑

value = function () {white();switch (ch) {case "{":return object();case "[":return array();case "\"":return string();case "-":return number();default:return (ch >= "0" && ch <= "9")? number(): word();}
};
复制代码

逻辑也很简单:

  1. 吃掉所有空格。
  2. 看当前的字符(ch)是什么
  3. 如果 ch 是 {,就吃一整个对象,然后把对象返回
  4. 如果 ch 是 [,就吃一整个数组,然后把数组返回
  5. 如果 ch 是 ",就吃一整个字符串,然后把字符串返回
  6. 如果 ch 是 -,就吃一整个数字,然后把数字返回
  7. 如果 ch 是 0~9,就吃一整个数字,然后把数字返回
  8. 其他情况只可能是 true/false/null,见啥吃啥,然后返回

图示如下:

DC 用 ch >= "0" && ch <= "9" 来判断字符是不是 0~9,这用到了 ASCII 字符集,如果你不懂就去搜一下。

大家应该对如何吃一个对象最感兴趣,我们来看看 object() 的逻辑

 var object = function () {var key;var obj = {};if (ch === "{") { // 当前字符必然是 {next("{");    // 吃掉这个 {white();      // 吃掉所有空格if (ch === "}") {  // 遇到 } 说明对象结束了next("}");     // 吃掉这个 }return obj;    // 返回空对象}while (ch) {       // 没有遇到 } 说明有 keykey = string();  // 吃一个 string 当做 keywhite();         // 吃掉所有空格next(":");       // 吃掉一个 :if (Object.hasOwnProperty.call(obj, key)) {error("Duplicate key '" + key + "'");}                  // 如果这个 key 之前遇到过就报错obj[key] = value();// 把key当做object的key,然后吃一个value作为值white();           // 吃掉所有空格if (ch === "}") {  // 如果遇到 } 说明对象结束了next("}");     // 吃掉这个 }return obj;    // 返回对象}next(",");         // 没有遇到 } 说明还有 key,吃一个逗号white();           // 吃掉空格然后继续回到上面吃 key}}error("Bad object");       // 如果运行到这里说明语法有问题
};
复制代码

到此我们基本搞清楚 DC 的 json_parser 的思路了,大家可以自己看一下 white()array() 的源码,结构十分清晰。

下次我们讲 json_parse_state.js 如何使用状态机的思路重写了这个 parser。

我的微信公众号:搜索 XDML 四个字母即可,XDML 是「写代码啦」的拼音首字母。


通过阅读 Douglas Crockford 的源码学习如何写 JSON parser(一)相关推荐

  1. 小说阅读Autojs源码学习

    "ui"; var rootUrl = ""; var storaySign = ""; var woolStorage = storage ...

  2. DotText源码学习——ASP.NET的工作机制

    --本文是<项目驱动学习--DotText源码学习>系列的第一篇文章,在这之后会持续发表相关的文章. 概论 在阅读DotText源码之前,让我们首先了解一下ASP.NET的工作机制,可以使 ...

  3. Java 源码学习系列(三)——Integer

    Integer 类在对象中包装了一个基本类型 int 的值.Integer 类型的对象包含一个 int 类型的字段. 此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还 ...

  4. Deep Compression阅读理解及Caffe源码修改

    Deep Compression阅读理解及Caffe源码修改 作者:may0324 更新:  没想到这篇文章写出后有这么多人关注和索要源码,有点受宠若惊.说来惭愧,这个工作当时做的很粗糙,源码修改的比 ...

  5. java-HashMap源码学习

    阅读提示:HashMap源码在不同版本情况下,具体源码可能不一样(优化问题),但功能几乎是相同的(博主1.8) 什么是Hash? hash表是一种数据结构,它拥有惊人的效率,它的时间复杂度低到接近O( ...

  6. vue源码学习--vue源码学习入门

    本文为开始学习vue源码的思路整理.在拿到vue项目源码的之后看到那些项目中的文件夹,会很困惑,不知道每个文件夹内的世界,怎么变换,怎样的魔力,最后产生了vue框架.学习源码也无从学起.我解决了这些困 ...

  7. java Integer 源码学习

    转载自http://www.hollischuang.com/archives/1058 Integer 类在对象中包装了一个基本类型 int 的值.Integer 类型的对象包含一个 int 类型的 ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)(转)

    阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHandler.IHttpHandler.MvcHandler的说明 2.IHttpHandler解析 3.Mvc ...

  9. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

最新文章

  1. 导入sql时出现Invalid default value for ‘create_time‘报错处理方法
  2. ppwjs之bootstrap文字排版:排版常量
  3. 2020/Province_C_C++_A/F/成绩分析
  4. 使用Nexus搭建私有Nuget仓库
  5. c语言gets与fgetc,区分C语言中getch、getche、fgetc、getc、getchar、fgets、gets 转
  6. 西门子array数据类型_西门子S71200之间以太网通信(图文)
  7. 垃圾邮件过滤——学习笔记
  8. [2013.8.29]对于多线程编程的几点个人见解
  9. python电商用户购买力分析_Python + pandas + 不同客户购买力图形显示
  10. 微信公众开放平台开发03---百度BAE上搭建属于自己的微信公众平台 -JAVA,微信公众开放平台部署到百度云中BASE2.0,进行调试,木有钱买云服务器的亲们试试
  11. BluetoothLE-Multi-Library 一个能够连接多台蓝牙设备的库,它可以作为client端,也可以为server端。支持主机/从机,外围设备连接。...
  12. ENVI实验教程(8)实验八、高光谱与光谱分析
  13. H5调用摄像头拍照并下载照片
  14. Unity 导入高分辨率图片
  15. 虚拟试衣是什么及优势在哪
  16. 非度量多维标度_用R语言做非度量多维尺度分析(NMDS)
  17. 计算机科学的刊物卷号,期刊的卷号和期号怎么看
  18. 一个非常好用的图片切割工具(c# winform开发)
  19. html图片用什么软件打开,.svg是什么文件 用什么软件打开
  20. eclipes安装lombok

热门文章

  1. mysql 查询 汇总_Mysql-Sql查询汇总
  2. 【TensorFlow-windows】name_scope与variable_scope
  3. MySQL查询之聚合查询
  4. 第七章 字典和集合[DDT书本学习 小甲鱼]【2】
  5. WinSxS文件夹瘦身
  6. plsql查询数据中文乱码
  7. jquery 文件预览功能
  8. 097实战 关于ETL的几种运行方式
  9. update语句中使用子查询
  10. 再读《被神化的框架》