日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

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

發布時間:2023/12/10 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通过阅读 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" },一次完整的邏輯如下

  • ch=" ",at=0, text='{ "name" : "Frank" }'
  • 吃一個空格。由于 ch 一開始的默認值是空格,所以這個空格就被吃掉了,然后 ch 指向text 的第一個字符,at 指向 ch 后面一個字符(存下標,也就是1)。
  • 如果 ch 是空格就繼續吃,吃到 ch 不是空格為止。
  • 發現 ch 是 {,就說明這是一個對象,生成一個空對象 object 用來存儲 key 和 value。而且后面的字符就要按照對象的語法來吃。
  • 吃空格直到遇到非空格。理論上 { 后面應該接一個 "key",所以這個非空格必須是 "。
  • 吃一個 "
  • 吃 N 個非 " 的字符(N >= 0)
  • 吃一個 "
  • 把剛才吃到的 N 個字符作為一個 key,放到空對象 object 里
  • 吃空格直到遇到非空格。理論上 "key" 后面應該接 : 所以這個非空格必須是 :
  • 吃一個 :
  • 吃空格直到遇到非空格。理論上冒號后面應該接 value,value 的值可以是對象、數組、字符串、bool、null 等,所以不能預期這個非空格是什么
  • 發現是一個 ",吃掉這個 ",如果值是一個字符串
  • 吃 N 個非 " 的字符
  • 吃一個 "
  • 把剛才吃到的 N 個字符作為一個 value,放到空對象 object 里
  • 吃空格直到遇到非空格。理論上 value 后面可以接逗號或者 }
  • 發現 ch 是 },吃掉 },說明 object 的數據已經讀完了
  • 一直吃空格,如果發現非空格,說明語法錯誤,報錯。
  • 將 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; }; 復制代碼

    也就是說主邏輯其實很簡單

  • 用 value() 吃一個值,這個值就是 text 對應的數據
  • 繼續吃掉所有空格
  • 吃完發現還有字符(一定是非空格),就說明語法錯了(畫蛇添足)
  • 接下來我們看 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();} }; 復制代碼

    邏輯也很簡單:

  • 吃掉所有空格。
  • 看當前的字符(ch)是什么
  • 如果 ch 是 {,就吃一整個對象,然后把對象返回
  • 如果 ch 是 [,就吃一整個數組,然后把數組返回
  • 如果 ch 是 ",就吃一整個字符串,然后把字符串返回
  • 如果 ch 是 -,就吃一整個數字,然后把數字返回
  • 如果 ch 是 0~9,就吃一整個數字,然后把數字返回
  • 其他情況只可能是 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(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。