当前位置: 首页 > 科技观察

前端进阶:如何用Javascript存储函数?

时间:2023-03-19 10:18:51 科技观察

任何Saas公司都需要有自己的低代码平台。在可视化低代码前端研发过程中,发现了很多有趣的技术需求,而在解决这些需求的过程中,往往会给你带来很多收获。今天分享一下Dooring开发过程中遇到的前端技术问题——javascript函数存储。背景介绍我们都知道,构建一个前端页面基本上需要以下三个元素:元素(UI)数据(Data)事件/交互(Event)在数据驱动的视图时代,这三个元素之间的关系是常如下图所示:趣谈前端可视化平台的设计思路,往往是基于以上流程来展开的,我们需要提供一个编辑器环境,供用户创建视图和交互。最终用户保存的产品可能是这样的:{"name":"Dooringform","bgColor":"#666","share_url":"http://xxx.cn","mount_event":[{"id":"123","func":()=>{//初始化逻辑GamepadHapticActuator();},"sourcedata":[]}],"body":[{"name":"header","event":[{"id":"123","type":"click","func":()=>{//component自定义交互逻辑showModal();}}]}]}然后问题是,我们可以保存json字符串(可以通过JSON.stringify序列化),但是函数怎么一起保存呢?保存函数如何让js在页面渲染时正常运行这个函数?想着实现方案说说前端我们都知道js对象转json可以用JSON.stringify来实现,但是它也会有限制,比如:转换后的值如果有toJSON()方法,那么toJson()定义了哪些值会被序列化非数组对象的属性不保证以特定顺序出现序列化后的字符串中的布尔值、数字、字符串包装对象在序列化过程中会自动转换为对应的原始值undefined、任意函数、符号值,在序列化过程中会被忽略(当它们出现时在非数组对象的属性值中)或转换为null(数组中出现时)的函数,undefined单独转换时会返回undefined,如JSON.stringify(function(){})或JSON.stringify(undefined)所有以symbol作为属性键的属性都将被完全忽略,即使它们是在replacer参数中强制指定的。Date调用toJSON()将其转换为字符串字符串(与Date.toISOString()相同),因此将其视为字符串。NaN和Infinity格式的Values和null会被当作null其他类型的对象,包括Map/Set/WeakMap/WeakSet,只会序列化可枚举属性我们可以看到第4条,如果我们序列化的对象中有一个函数,它will会被忽略!所以一般情况下,我们不能使用JSON.stringify来保存函数。还有别的办法吗?可能你会想到先把函数转成字符串,然后用JSON.stringify序列化保存到后端。最后,组件使用时,使用eval或Function将字符串转为函数。大致流程是这样的:趣话前端说的不错,理想很美好,现实却_______。接下来我们分析关键链接func2string和string2func是如何实现.js存储函数方案设计的,熟悉JSONAPI的朋友可能知道JSON.stringify支持3个参数,第二个参数replacer可以是函数也可以是数组。作为一个函数,它有两个参数,一个键和一个值,这两个参数将被序列化。函数需要返回JSON字符串中的值,如下:如果返回Number,则将其转换成对应的字符串作为属性值添加到JSON字符串中如果返回String,则将字符串添加到作为属性值的JSON字符String如果返回布尔值,则将“true”或“false”作为属性值添加到JSON字符串。如果返回任何其他对象,该对象将递归序列化为JSON字符串,为每个属性调用replacer方法。除非对象是一个函数,否则在这种情况下它不会被序列化为JSON字符。如果返回undefined,则属性值不会输出到JSON字符串中,所以对于值类型为函数的数据,我们可以使用第二个函数参数进行转换。如下:conststringify=(obj)=>{returnJSON.stringify(obj,(k,v)=>{if(typeofv==='function'){return`${v}`}returnv})}所以我们看功能好像可以保存到后台。接下来我们看看如何用函数字符串反序列化json。因为我们把函数转成了字符串,在deparsing的时候需要知道哪些字符串需要转成函数,如果不对函数做任何处理,可能需要人肉识别。人肉识别的缺点是需要用正则表达式来提取具有函数特征的字符串,但是函数的写法有很多种,要考虑很多情况,不能保证带函数的字符串特性必须是一个函数。所以我换了一种简单的方式来提取函数,不用写复杂的正则。方法是在函数序列化的时候注入标识符,这样我们就可以知道那些字符串需要解析成函数,如下:stringify:function(obj:any,space:number|string,error:(err:Error|unknown)=>{}){try{returnJSON.stringify(obj,(k,v)=>{if(typeofv==='function'){return`${this.FUNC_PREFIX}${v}`}returnv},space)}catch(err){error&&error(err)}}这个.FUNC_PREFIX是我们定义的标识符,方便我们在使用JSON.parse时快速解析函数。JSON.parse也支持第二个参数,其用法与JSON.stringify的第二个参数类似。我们可以这样转换:parse:function(jsonStr:string,error:(err:Error|unknown)=>{}){try{returnJSON.parse(jsonStr,(key,value)=>{if(value&&typeofvalue==='string'){returnvalue.indexOf(this.FUNC_PREFIX)>-1?newFunction(`return${value.replace(this.FUNC_PREFIX,'')}`)():value}returnvalue})}catch(err){error&&error(err)}}new函数可以将字符串转成js函数,只接受字符字符串参数,可选参数为方法的入参,必选参数为方法体的内容。一个形象的例子:趣味说说前端上面代码中函数体的内容:newFunction(`return${value.replace(this.FUNC_PREFIX,'')}`)()return的原因是要原封不动地恢复原来的功能,也可以用eval,但是舆论还是要慎用。上面的方案已经可以实现前端存储功能功能,但是为了更加工程化和健壮性,还需要做很多额外的处理和优化,让更多人开箱即用的使用你的库。最后,为了让更多人可以直接使用这个功能,我把完整版的json序列化方案封装成一个类库,支持的功能如下:stringify在原生JSON.stringify的基础上支持序列化功能,错误回调parse在原生JSON.parse的基础上支持反序列化函数,错误回调funcParse一键序列化js对象中的函数,并保持js对象的类型不变。安装方法如下:#ornpminstallxijsyarnaddxijs使用:import{parser}from'xijs';alert(1)}}constjson=parser.stringify(a);constobj=parser.parse(json);//调用方法obj.b();关注二维码转载本文,请联系趣谈前端公众号。