比JSON.stringify快兩倍的fast-json-stringify性能對(duì)比分析
前言
相信大家對(duì)JSON.stringify
并不陌生,通常在很多場(chǎng)景下都會(huì)用到這個(gè)API,最常見的就是HTTP請(qǐng)求中的數(shù)據(jù)傳輸, 因?yàn)镠TTP 協(xié)議是一個(gè)文本協(xié)議,傳輸?shù)母袷蕉际亲址?,但我們?cè)诖a中常常操作的是 JSON 格式的數(shù)據(jù),所以我們需要在返回響應(yīng)數(shù)據(jù)前將 JSON 數(shù)據(jù)序列化為字符串。但大家是否考慮過使用JSON.stringify
可能會(huì)帶來性能風(fēng)險(xiǎn)??,或者說有沒有一種更快的stringify
方法。
JSON.stringify的性能瓶頸
由于 JavaScript 是動(dòng)態(tài)語言,它的變量類型只有在運(yùn)行時(shí)才能確定,所以 JSON.stringify 在執(zhí)行過程中要進(jìn)行大量的類型判斷,對(duì)不同類型的鍵值做不同的處理。由于不能做靜態(tài)分析,執(zhí)行過程中的類型判斷這一步就不可避免,而且還需要一層一層的遞歸,循環(huán)引用的話還有爆棧的風(fēng)險(xiǎn)。
我們知道,JSON.string的底層有兩個(gè)非常重要的步驟:
- 類型判斷
- 遞歸遍歷
既然是這樣,我們可以先來對(duì)比一下JSON.stringify與普通遍歷的性能,看看類型判斷這一步到底是不是影響JSON.stringify性能的主要原因。
JSON.stringify 與遍歷對(duì)比
const obj1 = {}, obj2 = {} for(let i = 0; i < 1000000; i++) { obj1[i] = i obj2[i] = i } function fn1 () { console.time('jsonStringify') const res = JSON.stringify(obj1) === JSON.stringify(obj2) console.timeEnd('jsonStringify') } function fn2 () { console.time("for"); const res = Object.keys(obj1).every((key) => { if (obj2[key] || obj2[key] === 0) { return true; } else { return false; } }); console.timeEnd("for"); } fn1() fn2()
從結(jié)果來看,兩者的性能差距在4倍左右,那就證明JSON.string
的類型判斷這一步還是非常耗性能的。如果JSON.stringify能夠跳過類型判斷這一步是否對(duì)類型判斷有幫助呢?
定制化更快的JSON.stringify
基于上面的猜想,我們可以來嘗試實(shí)現(xiàn)一下:
現(xiàn)在我們有下面這個(gè)對(duì)象
const obj = { name: '南玖', hobby: 'fe', age: 18, chinese: true }
上面這個(gè)對(duì)象經(jīng)過JSON.stringify
處理后是這樣的:
JSON.stringify(obj) // {"name":"南玖","hobby":"fe","age":18,"chinese":true}
現(xiàn)在假如我們已經(jīng)提前知道了這個(gè)對(duì)象的結(jié)構(gòu)
- 鍵名不變
- 鍵值類型不變
這樣的話我們就可以定制一個(gè)更快的JSON.stringify方法
function myStringify(obj) { return `{"name":"${obj.name}","hobby":"${obj.hobby}","age":${obj.age},"chinese":${obj.chinese}}` } console.log(myStringify(obj) === JSON.stringify(obj)) // true
這樣也能夠得到JSON.stringify一樣的效果,前提是你已經(jīng)知道了這個(gè)對(duì)象的結(jié)構(gòu)。
事實(shí)上,這是許多JSON.stringify
加速庫(kù)的通用手段:
- 需要先確定對(duì)象的結(jié)構(gòu)信息
- 再根據(jù)結(jié)構(gòu)信息,為該種結(jié)構(gòu)的對(duì)象創(chuàng)建“定制化”的
stringify
方法 - 內(nèi)部實(shí)現(xiàn)依然是這種字符串拼接
更快的fast-json-stringify
fast-json-stringify 需要JSON Schema Draft 7輸入來生成快速stringify
函數(shù)。
這也就是說fast-json-stringify
這個(gè)庫(kù)是用來給我們生成一個(gè)定制化的stringily函數(shù),從而來提升stringify
的性能。
這個(gè)庫(kù)的GitHub簡(jiǎn)介上寫著比 JSON.stringify() 快 2 倍,其實(shí)它的優(yōu)化思路跟我們上面那種方法是一致的,也是一種定制化stringify
方法。
語法
const fastJson = require('fast-json-stringify') const stringify = fastJson(mySchema, { schema: { ... }, ajv: { ... }, rounding: 'ceil' })
schema
: $ref 屬性引用的外部模式。
ajv
: ajv v8 實(shí)例對(duì)那些需要ajv
.
rounding
: 設(shè)置當(dāng)integer
類型不是整數(shù)時(shí)如何舍入。
largeArrayMechanism
:設(shè)置應(yīng)該用于處理大型(默認(rèn)情況下20000
或更多項(xiàng)目)數(shù)組的機(jī)制
scheme
這其實(shí)就是我們上面所說的定制化對(duì)象結(jié)構(gòu),比如還是這個(gè)對(duì)象:
const obj = { name: '南玖', hobby: 'fe', age: 18, chinese: true }
它的JSON scheme是這樣的:
{ type: "object", properties: { name: {type: "string"}, hobby: {type: "string"}, age: {type: "integer"}, chinese: {type: 'boolean'} }, required: ["name", "hobby", "age", "chinese"] }
AnyOf 和 OneOf
當(dāng)然除了這種簡(jiǎn)單的類型定義,JSON Schema 還支持一些條件運(yùn)算,比如字段類型可能是字符串或者數(shù)字,可以用 oneOf 關(guān)鍵字:
"oneOf": [ { "type": "string" }, { "type": "number" } ]
fast-json-stringify
支持JSON 模式定義的anyOf和**oneOf關(guān)鍵字。**兩者都必須是一組有效的 JSON 模式。不同的模式將按照指定的順序進(jìn)行測(cè)試。stringify
在找到匹配項(xiàng)之前必須嘗試的模式越多,速度就越慢。
anyOf和oneOf使用ajv作為 JSON 模式驗(yàn)證器來查找與數(shù)據(jù)匹配的模式。這對(duì)性能有影響——只有在萬不得已時(shí)才使用它。
關(guān)于 JSON Schema 的完整定義,可以參考 Ajv 的文檔,Ajv 是一個(gè)流行的 JSON Schema驗(yàn)證工具,性能表現(xiàn)也非常出眾。
當(dāng)我們可以提前確定一個(gè)對(duì)象的結(jié)構(gòu)時(shí),可以將其定義為一個(gè) Schema,這就相當(dāng)于提前告訴 stringify 函數(shù),需序列化的對(duì)象的數(shù)據(jù)結(jié)構(gòu),這樣它就可以不必再在運(yùn)行時(shí)去做類型判斷,這就是這個(gè)庫(kù)提升性能的關(guān)鍵所在。
簡(jiǎn)單使用
const fastJson = require('fast-json-stringify') const stringify = fastJson({ title: 'myObj', type: 'object', properties: { name: { type: 'string' }, hobby: { type: 'string' }, age: { description: 'Age in years', type: 'integer' }, chinese: { type: 'boolean' } } }) console.log(stringify({ name: '南玖', hobby: 'fe', age: 18, chinese: true })) //
生成 stringify 函數(shù)
fast-json-stringify
是跟我們傳入的scheme
來定制化生成一個(gè)stringily
函數(shù),上面我們了解了怎么為我們對(duì)象定義一個(gè)scheme
結(jié)構(gòu),接下來我們?cè)賮砹私庖幌氯绾紊?code>stringify。
這里有一些工具方法還是值得了解一下的:
const asFunctions = ` function $asAny (i) { return JSON.stringify(i) } function $asNull () { return 'null' } function $asInteger (i) { if (isLong && isLong(i)) { return i.toString() } else if (typeof i === 'bigint') { return i.toString() } else if (Number.isInteger(i)) { return $asNumber(i) } else { return $asNumber(parseInteger(i)) } } function $asNumber (i) { const num = Number(i) if (isNaN(num)) { return 'null' } else { return '' + num } } function $asBoolean (bool) { return bool && 'true' || 'false' } // 省略了一些其他類型...... `
從上面我們可以看到,如果你使用的是 any 類型,它內(nèi)部依然還是用的 JSON.stringify。 所以我們?cè)谟肨S進(jìn)行開發(fā)時(shí)應(yīng)避免使用 any 類型,因?yàn)槿绻腔?TS interface
生成 JSON Schema
的話,使用 any 也會(huì)影響到 JSON 序列化的性能。
然后就會(huì)根據(jù) scheme 定義的具體內(nèi)容生成 stringify 函數(shù)的具體代碼。而生成的方式也比較簡(jiǎn)單:通過遍歷 scheme,根據(jù)不同數(shù)據(jù)類型調(diào)用上面不同的工具函數(shù)來進(jìn)行字符串拼接。感興趣的同學(xué)可以在GitHub上查看源碼
總結(jié)
事實(shí)上fast-json-stringify
只是通過靜態(tài)的結(jié)構(gòu)信息將優(yōu)化與分析前置了,通過開發(fā)者定義的scheme
內(nèi)容可以提前知道對(duì)象的數(shù)據(jù)結(jié)構(gòu),然后會(huì)生成一個(gè)stringify
函數(shù)供開發(fā)者調(diào)用,該函數(shù)內(nèi)部其實(shí)就是做了字符串的拼接。
- 開發(fā)者定義 Object 的
JSON scheme
- stringify 庫(kù)根據(jù) scheme 生成對(duì)應(yīng)的模版方法,模版方法里會(huì)對(duì)屬性與值進(jìn)行字符串拼接
- 最后開發(fā)者調(diào)用生成的stringify 方法
以上就是比JSON.stringify快兩倍的fast-json-stringify性能對(duì)比分析的詳細(xì)內(nèi)容,更多關(guān)于JSON.stringify對(duì)比fast-json-stringify的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript控制圖片播放的實(shí)現(xiàn)代碼
這篇文章主要介紹了javascript控制圖片播放的實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-08-08解決layer 關(guān)閉當(dāng)前彈窗 關(guān)閉遮罩層 input值獲取不到的問題
今天小編就為大家分享一篇解決layer 關(guān)閉當(dāng)前彈窗 關(guān)閉遮罩層 input值獲取不到的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09javascript實(shí)現(xiàn)鼠標(biāo)拖尾特效
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)鼠標(biāo)拖尾特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09Bootstrap源碼解讀標(biāo)簽、徽章、縮略圖和警示框(8)
這篇文章主要源碼解讀了標(biāo)簽、徽章、縮略圖和警示框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12如何用JS實(shí)現(xiàn)網(wǎng)頁(yè)瀑布流布局
這篇文章主要介紹了如何用JS實(shí)現(xiàn)網(wǎng)頁(yè)瀑布流布局,幫助大家更好的利用JavaScript制作網(wǎng)頁(yè),感興趣的朋友可以了解下2021-04-043分鐘教你用JavaScript實(shí)現(xiàn)電子簽名效果
電子簽名已經(jīng)成為現(xiàn)代商業(yè)中不可或缺的一部分,它可以提高業(yè)務(wù)流程的效率和安全性。本文將介紹如何使用HTML5的canvas元素和JavaScript在前端實(shí)現(xiàn)電子簽名,需要的可以參考一下2023-04-04JS實(shí)現(xiàn)touch 點(diǎn)擊滑動(dòng)輪播實(shí)例代碼
這篇文章主要介紹了JS實(shí)現(xiàn)touch 點(diǎn)擊滑動(dòng)輪播實(shí)例代碼,需要的朋友可以參考下2017-01-01