1.熟悉的JSON.stringify()
JSON.stringify()是浏览器或服务器上常用的方法:
将JSON对象保存在localStorage中;POST请求中的Jsonbody处理响应体中的JSON数据;甚至在某些条件下,我们会用它来实现简单的深度复制;……在一些对性能敏感的情况下(比如服务器处理大量并发),或者面对大量stringify操作时,我们会希望它的性能会更好更快。这也催生了一些优化的字符串模式/库。下图显示了它们与本机方法的性能比较:
绿色部分是本机JSON.stringify(),这表明性能比这些库低得多。那么,性能大幅提升背后的技术原理是什么?
2.弦比弦快
因为JavaScript是一种非常动态的语言,所以Object类型变量的键名、键值和键值类型只能在运行时确定。因此,在执行JSON.stringify()时,有很多工作要做。在一无所知的情况下,我们显然没有办法进行大的优化。
那么如果我们知道这个Object中的键名和键值信息,也就是知道它的结构信息,会有帮助吗?
看一个例子:
以下对象,
const obj={ name: 'alienzhou ',status: 6,work : true };我们将JSON.stringify()应用于它,结果是
JSON . stringify(obj);//{'name' :' alienzhou ',' status' :6,' working ' : true }现在如果我们知道这个obj的结构是固定的:
具有相同键名的键值类型必须如此真实,我可以创建一个“定制的”字符串方法
函数MyStrInify(o){ return(' { ' name ' : ' ' o . name ' ',' status':' o.status ',' isWorking ' : ' o . working ' } ');}看看我们的myStringify方法的输出:
myStringify({ name : ' alien Zhou ',status: 6,work : true });//{'name':'alienzhou ',' status':6,' is working ' : true } MyStringIfy({ name : '寿梦',status: 3,work : false });//{'name' : '寿梦',' status' :3,' isworking ' : false }可以得到正确的结果,但只能使用类型转换和字符串拼接,因此“自定义”方法可以使“stringify”更快。
综上所述,如何才能得到比stringfy更快的stringfy方法呢?
首先需要确定对象的结构信息。根据其结构信息,为具有这种结构的对象创建一个“自定义”的stringify方法,它实际上是通过字符串拼接生成结果;最后,使用这个“定制”的方法来字符串化对象。
这也是大多数字符串加速库的例行程序,它类似于转换成代码:
从“some _ library _ faster _ stringify”导入更快;//1.定义您的对象结构常量对象方案={//.}通过相应的规则;//2.根据该结构,获得定制的方法const stringfy=faster(对象方案)。//3.调用方法,fast stringify const target={//.};stringify(目标);3.如何生成“定制”的方法
根据上面的分析,核心功能是根据这个类对象的结构信息为其创建一个“自定义”的stringify方法,其实就是简单的属性访问和字符串拼接。
为了理解具体的实现,让我简单介绍两个开源库,并以略有不同的实现为例。
3.1.快速json字符串
下图显示了根据fast-JSON-stringfy提供的基准测试结果进行的性能比较。
可以看出,在大多数场景下,性能提高了2-5倍。
3.1.1.如何定义方案
Fast-json-stringify使用json模式验证来定义(JSON)对象的数据格式。由其方案本身定义的结构也是JSON格式的,比如对象
{name:' alienzhou ',status: 6,working: true }对应于该方案:
{ title: '示例架构,类型: '对象,属性: {名称: {类型: '字符串},status: { type: '整数},working: { type: '布尔值' } }其计划定义规则丰富,具体使用可以参考日十女这个数据校验库。
3.1.2.字符串化方法的生成
快速数据字符串会根据刚才定义的方案,拼接生成出实际的函数代码字符串,然后使用功能构造函数在运行时动态生成对应的字符串化函数。
在代码生成上,首先它会注入预先定义好的各类工具方法,这一部分不同的计划都是一样的:
var代码=` '使用严格的` code=` $ { $ asstring。tostring()} $ { $ asstringnullable。tostring()} $ { $ asstringsmall。tostring()} $ { $ asnumber。tostring()} $ { $ asnumber可空。tostring()} $ { $ asintegernullable。tostring()} $ { $ asnull。tostring()} $ { $ asbooleanullable。tostring()} `其次,就会根据计划定义的具体内容生成字符串化函数的具体代码。而生成的方式也比较简单:通过遍历计划。
遍历计划时,根据定义的类型,在对应代码处插入相应的工具函数用于键值转换。例如上面例子中名字这个属性:
定义变量访问器=key.indexOf('[')===0?head heakey(key): `[' $ { head heakey(key)} ']` switch(type){ case ' null ' :代码=` JSON=$ AsNull()` break case ' string ' :代码=可空?` json=obj${accessor}===null?null : $ AsString(obj $ { accessor })` : ` JSON=$ AsString(obj $ { accessor })'破格整数' :代码=可空?` json=obj${accessor}===null?null : $作为整数(obj $ { accessor })` : ` JSON=$作为整数(obj $ { accessor })` break……上面代码中的密码变量保存的就是最后生成的函数体的代码串。由于在计划定义中,名称为线类型,且不为空,所以会在密码中添加如下一段代码字符串:
json=$asString(obj['name'])'由于还需要处理数组、及联对象等复杂情况,实际的代码省略了很多。
然后,生成的完整的密码字符串大致如下:
函数$asString(str) { //…… }函数$asStringNullable(str) { //…… }函数$asStringSmall(str) { //…… }函数$asNumber(i) { //…… }函数$ AsNullable(I){//……}/*以上是一系列通用的键值转换方法*//* $main就是字符串化的主体函数*/function $ main(input){ var obj=输入类型。到JSON==='函数'?输入。tojson():输入var JSON=' { ' var逗号=false if(obj[' name ']!==未定义){ if (addComma) { json=',' } addComma=true JSON=' name ' : ' JSON=$ asString(obj[' name '])}//……其他属性(状态、工作)的拼接json='}' return json } return $main最后,将密码字符串传入功能构造函数来创建相应的字符串化函数。
//依赖项主要用于处理包含任何人与如果语法的情况dependenciesName.push(代码)返回(Function.apply(null,dependenciesName)).应用(空,依赖)3.2 .慢数据字符串
慢数据字符串虽然名字叫慢点,但其实是一个"快"的字符串化库(命名很调皮)。
已知宇宙中最慢的弦。开个玩笑,最快了(:
它的实现比前面提到的快速数据字符串更轻量级,思路也很巧妙。同时它在很多场景下效率会比快速数据字符串更快。
3.2.1.计划的定义方式
慢数据字符串的计划定义更自然与简单,主要就是将键值替换为类型描述。还是上面这个对象的例子,方案会变为
{ name: 'string ',status: 'number ',working: ' boolean ' }确实非常直观。
3.2.2.字符串化方法的生成
不知道你注意到没有
//scheme {name:' string ',status:' number ',work : ' boolean ' }//目标对象{name3360' alienzhou ',status3360 6,working3360 true }的结构是否与原始对象的结构相似?
这个方案的精妙之处在于,在这个定义之后,我们可以先放schemejonson.stringify,然后“扣除”所有的类型值,最后等待我们直接将实际值填充到方案对应的类型声明中。
具体怎么操作?
首先可以直接调用JSON.stringify()到方案中生成基本模板,借用JSON.stringify()的第二个参数作为遍历方法收集属性的访问路径:
让map={ };const str=JSON.stringify(schema,(prop,value)={ const isArray=array . isArray(value);if(值类型!==' object ' | | isArray){ if(isArray){ const current=value[0];arrais.set(prop,current);} _validator(值);map[prop]=_deepPath(schema,prop);props=` ' $ { prop } ' |}返回值;});此时,地图中会收集所有属性的访问路径。同时,生成的道具可以拼接成与相应类型的字符相匹配的正则表达式。例如,我们示例中的正则表达式是/name | status | working '(字符串|数字|布尔值| undf)' | [()。*?)\]/。
然后,根据正则表达式按顺序匹配这些属性,替换属性类型字符串,用统一的占位符字符串“__par__”替换它们,并根据“__par__”拆分字符串:
const queue=[];const chunks=str。replace(regex,(type)={ switch(type){ case ' ' string ' ' : case ' ' undefined ' ' : return ' ' _ _ par _ _ ' ';case ' ' number ' ' : case ' ' boolean ' ' : case '[' array-simple ']' : case '[null]' : return ' _ _ par _ _ ';default : const prop=type . match(/(?=').(?=')/)[0];queue . push(prop);返回类型;} }) .split(“_ _ par _ _”);这样,你会得到两个数组:组块和道具。组块包含一个分离的JSON字符串。举例来说,这两个阵列如下
//组块['{'name' : '','',' status' : ' ',' ',' working' : ' ',' ' } ']//道具['name ',' status ',' working']最后,
从代码量和实现来看,该方案将更轻、更巧妙,不需要借助函数和eval动态生成或执行Functions。
4.摘要
尽管不同库的实现有所不同,但实现高性能stringify的总体方式是相同的:
开发人员定义JSON方案;对象的;Stringify库根据方案生成对应的模板方法,其中属性和值由字符串拼接(显然属性访问和字符串拼接的效率要高得多);最后,开发人员将返回的方法调用到stringify Object。归根结底,它本质上是通过静态的结构信息来进行优化和分析。
技巧
最后,我还是想提一下
所有基准只能作为参考。是否有业绩提升或者提升多少,建议你在实际业务中测试;
fast-json-stringify中使用了Function构造函数,建议不要直接使用用户输入作为方案,以免出现一些安全问题。
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。