为了更好的阅读体验,可以去掘金阅读本文。
https://link.zhihu.com/?target=https%3A//github.com/douglascrockford/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 character var ch; // The current character var escape = {...} var text var 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
接下来我们定义一个动作:吃。
好了,parser 的难点讲完了,接下来就是细节了,假设 text 是字符串 { "name" : "Frank" },一次完整的逻辑如下
如果你能在大脑里过一遍这个过程,就可以看懂所有源码了:
var json_parse = (function(){ 'use strict' var at; // The index of the current character var ch; // The current character var escape = {...} var text var 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; };
也就是说主逻辑其实很简单
接下来我们看 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(); } };
逻辑也很简单:
图示如下:
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) { // 没有遇到 } 说明有 key key = string(); // 吃一个 string 当做 key white(); // 吃掉所有空格 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。
饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com
本文作者:饥人谷方应杭老师