如何用javascript存储函数?

更新日期: 2021-11-28阅读: 700标签: 函数

❝ 任何一家Saas企业都需要有自己的低代码平台.在可视化低代码的前端研发过程中, 发现了很多有意思的技术需求, 在解决这些需求的过程中, 往往也会给自己带来很多收获, 今天就来分享一下在研发Dooring过程中遇到的前端技术问题——javascript函数存储.

背景介绍

我们都知道要想搭建一个前端页面基本需要如下3个要素:

  • 元素(UI)
  • 数据(Data)
  • 事件/交互(Event)

在 「数据驱动视图」 的时代, 这三个要素的关系往往如下图所示:


可视化搭建平台的设计思路往往也是基于上面的过程展开的, 我们需要提供编辑器环境给用户来创建 「视图」 和 「交互」 , 最终用户保存的产物可能是这样的:

{
    "name": "Dooring表单",
    "bgColor": "#666",
    "share_url": "http://xxx.cn",
    "mount_event": [
        {
            "id": "123",
            "func": () => {
                // 初始化逻辑
                GamepadHapticActuator();
            },
            "sourcedata": []
        }
    ],
    "body": [
        {
            "name": "header",
            "event": [
                {
                    "id": "123",
                    "type": "click",
                    "func": () => {
                        // 组件自定义交互逻辑
                        showModal();
                    }
                }
            ]
        }
    ]
}

那么问题来了, json 字符串我们好保存(可以通过 JSON.stringify 序列化的方式), 但是如何将 「函数」 也一起保存呢? 保存好了函数如何在页面渲染的时候能正常让 js 运行这个函数呢?

实现方案思考


我们都知道将 js 对象转化为 json 可以用 JSON.stringify 来实现, 但是它也会有局限性, 比如:

  1. 转换值如果有 toJSON() 方法,那么由 toJson() 定义什么值将被序列化
  2. 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中
  3. 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
  4. undefined 、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null (出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如 JSON.stringify(function(){}) or JSON.stringify(undefined)
  5. 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们
  6. Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理
  7. NaN 和 Infinity 格式的数值及 null 都会被当做 null
  8. 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性

我们可以看到第4条, 如果我们序列化的对象中有函数, 它将会被忽略! 所以常理上我们使用 JSON.stringify 是无法保存函数的, 那还有其他办法吗?

也许大家会想到先将函数转换成字符串, 再用 JSON.stringify 序列化后保存到后端, 最后在组件使用的时候再用 eval 或者 Function 将字符串转换成函数. 大致流程如下:


不错, 理想很美好, 但是现实很_______.

接下来我们就一起分析一下关键环节 func2string 和 string2func 如何实现的.

js存储函数方案设计

熟悉 JSON api 的朋友可能会知道 JSON.stringify 支持3个参数, 第二个参数 replacer 可以是一个函数或者一个数组。作为函数,它有两个参数,键(key)和值(value),它们都会被序列化。 函数需要返回 JSON 字符串中的 value , 如下所示:

Number
String
Boolean

所以我们可以在第二个函数参数里对 value类型为函数的数据进行转换。如下:

const stringify = (obj) => {
    return JSON.stringify(obj, (k, v) => {
      if(typeof v === 'function') {
          return `${v}`
      }
      return v
    })
}

这样我们看似就能把函数保存到后端了. 接下来我们看看如何反序列化带函数字符串的 json .

因为我们将函数转换为字符串了, 我们在反解析时就需要知道哪些字符串是需要转换成函数的, 如果不对函数做任何处理我们可能需要 「人肉识别」 .

❝ 「人肉识别」 的缺点在于我们需要用正则把具有函数特征的字符串提取出来, 但是函数写法有很多, 我们要考虑很多情况, 也不能保证具有函数特征的字符串一定是函数.

所以我换了一种简单的方式, 可以不用写复杂正则就能将函数提取出来, 方法就是在函数序列化的时候注入标识符, 这样我们就能知道那些字符串是需要解析为函数了, 如下:

stringify: function(obj: any, space: number | string, error: (err: Error | unknown) => {}) {
        try {
            return JSON.stringify(obj, (k, v) => {
                if(typeof v === 'function') {
                    return `${this.FUNC_PREFIX}${v}`
                }
                return v
            }, space)
        } catch(err) {
            error && error(err)
        }
}

this.FUNC_PREFIX 就是我们定义的标识符, 这样我们在用 JSON.parse 的时候就能快速解析函数了. JSON.parse 也支持第二个参数, 他的用法和 JSON.stringify 的第二个参数类似, 我们可以对它进行转换, 如下:

parse: function(jsonStr: string, error: (err: Error | unknown) => {}) {
        try {
            return JSON.parse(jsonStr, (key, value) => {
                if(value && typeof value === 'string') {
                    return value.indexOf(this.FUNC_PREFIX) > -1 ? new Function(`return ${value.replace(this.FUNC_PREFIX, '')}`)() : value
                }
                return value
            })
        } catch(err) {
            error && error(err)
        }
    }

new Function 可以把字符串转换成 js 函数, 它只接受字符串参数,其可选参数为方法的入参,必填参数为方法体内容, 一个形象的例子:


我们上述的代码中函数体的内容:

new Function(`return ${value.replace(this.FUNC_PREFIX, '')}`)()

之所以要 return 是为了把原函数原封不动的还原, 大家也可以用 eval , 但是出于舆论还是谨慎使用.

以上方案已经能实现前端存储函数的功能了, 但是为了更工程化和健壮性还需要做很多额外的处理和优化, 这样才能让更多人开箱即用的使用你的库.

最后

为了让更多人能直接使用这个功能, 我将完整版 json 序列化方案封装成了类库, 支持功能如下:

JSON.stringify
JSON.parse

安装方式如下:

# or npm install xijs
yarn add xijs

使用:

import { parser } from 'xijs';

const a = {
    x: 12,
    b: function() {
      alert(1)
    }
 }
 
 const json = parser.stringify(a);
 const obj = parser.parse(json);
 // 调用方法
 obj.b();
原文作者: 徐小夕,来源: 趣谈前端


链接: https://www.fly63.com/article/detial/10874

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!