比JSON.stringify快兩倍的fast-json-stringify
前言?
相信大家對JSON.stringify并不陌生,通常在很多場景下都會用到這個API,最常見的就是HTTP請求中的數據傳輸, 因為HTTP 協議是一個文本協議,傳輸的格式都是字符串,但我們在代碼中常常操作的是 JSON 格式的數據,所以我們需要在返回響應數據前將 JSON 數據序列化為字符串。但大家是否考慮過使用JSON.stringify可能會帶來性能風險??,或者說有沒有一種更快的stringify方法。
JSON.stringify的性能瓶頸?
由于 JavaScript 是動態語言,它的變量類型只有在運行時才能確定,所以 JSON.stringify 在執行過程中要進行大量的類型判斷,對不同類型的鍵值做不同的處理。由于不能做靜態分析,執行過程中的類型判斷這一步就不可避免,而且還需要一層一層的遞歸,循環引用的話還有爆棧的風險。
我們知道,JSON.string的底層有兩個非常重要的步驟:
- 類型判斷
- 遞歸遍歷
既然是這樣,我們可以先來對比一下JSON.stringify與普通遍歷的性能,看看類型判斷這一步到底是不是影響JSON.stringify性能的主要原因。
JSON.stringify 與遍歷對比
從結果來看,兩者的性能差距在4倍左右,那就證明JSON.string的類型判斷這一步還是非常耗性能的。如果JSON.stringify能夠跳過類型判斷這一步是否對類型判斷有幫助呢?
定制化更快的JSON.stringify
基于上面的猜想,我們可以來嘗試實現一下:
現在我們有下面這個對象
上面這個對象經過JSON.stringify處理后是這樣的:
現在假如我們已經提前知道了這個對象的結構
- 鍵名不變
- 鍵值類型不變
這樣的話我們就可以定制一個更快的JSON.stringify方法
這樣也能夠得到JSON.stringify一樣的效果,前提是你已經知道了這個對象的結構。
事實上,這是許多JSON.stringify加速庫的通用手段:
- 需要先確定對象的結構信息
- 再根據結構信息,為該種結構的對象創建“定制化”的stringify方法
- 內部實現依然是這種字符串拼接
更快的fast-json-stringify?
fast-json-stringify 需要JSON Schema Draft 7輸入來生成快速stringify函數。
這也就是說fast-json-stringify?這個庫是用來給我們生成一個定制化的stringily函數,從而來提升stringify的性能。
這個庫的GitHub簡介上寫著「比 JSON.stringify() 快 2 倍」,其實它的優化思路跟我們上面那種方法是一致的,也是一種定制化stringify方法。
語法
- schema: $ref 屬性引用的外部模式。
- ajv: ajv v8 實例對那些需要ajv.
- rounding: 設置當integer類型不是整數時如何舍入。
- largeArrayMechanism:設置應該用于處理大型(默認情況下20000或更多項目)數組的機制
scheme
這其實就是我們上面所說的定制化對象結構,比如還是這個對象:
它的JSON scheme是這樣的:
AnyOf 和 OneOf
當然除了這種簡單的類型定義,JSON Schema 還支持一些條件運算,比如字段類型可能是字符串或者數字,可以用 oneOf 關鍵字:
fast-json-stringify?支持JSON 模式定義的「anyOf」和「oneOf關鍵字。」兩者都必須是一組有效的 JSON 模式。不同的模式將按照指定的順序進行測試。stringify在找到匹配項之前必須嘗試的模式越多,速度就越慢。
anyOf和oneOf使用ajv作為 JSON 模式驗證器來查找與數據匹配的模式。這對性能有影響——只有在萬不得已時才使用它。
關于 JSON Schema 的完整定義,可以參考 Ajv 的文檔,Ajv 是一個流行的 JSON Schema驗證工具,性能表現也非常出眾。
當我們可以提前確定一個對象的結構時,可以將其定義為一個 Schema,這就相當于提前告訴 stringify 函數,需序列化的對象的數據結構,這樣它就可以不必再在運行時去做類型判斷,這就是這個庫提升性能的關鍵所在。
簡單使用?
生成 stringify 函數
fast-json-stringify是跟我們傳入的scheme來定制化生成一個stringily函數,上面我們了解了怎么為我們對象定義一個scheme結構,接下來我們再來了解一下如何生成stringify。
這里有一些工具方法還是值得了解一下的:
從上面我們可以看到,「如果你使用的是 any 類型,它內部依然還是用的 JSON.stringify。」所以我們在用TS進行開發時應避免使用 any 類型,因為如果是基于 TS interface? 生成 JSON Schema 的話,使用 any 也會影響到 JSON 序列化的性能。
然后就會根據 scheme 定義的具體內容生成 stringify 函數的具體代碼。而生成的方式也比較簡單:通過遍歷 scheme,根據不同數據類型調用上面不同的工具函數來進行字符串拼接。感興趣的同學可以在GitHub上查看源碼
總結?
事實上fast-json-stringify只是通過靜態的結構信息將優化與分析前置了,通過開發者定義的scheme內容可以提前知道對象的數據結構,然后會生成一個stringify函數供開發者調用,該函數內部其實就是做了字符串的拼接。
- 開發者定義 Object 的JSON scheme
- stringify 庫根據 scheme 生成對應的模版方法,模版方法里會對屬性與值進行字符串拼接
- 最后開發者調用生成的stringify 方法