javascript
通过阅读 Douglas Crockford 的源码学习如何写 JSON parser(一)
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" },一次完整的邏輯如下
如果你能在大腦里過一遍這個過程,就可以看懂所有源碼了:
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; }; 復制代碼也就是說主邏輯其實很簡單
接下來我們看 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) { // 沒有遇到 } 說明有 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(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 某项目的双代号网络图如下所示_2019一
- 下一篇: 平板电脑可以插u盘吗_电视TV盒子安装a