Any Saas enterprise needs to have its own low-code platform. In the process of developing visual low-code front-end, I found a lot of interesting technical requirements. In the process of solving these requirements, I often brought myself a lot of gains. Today, I share a front-end technical problem encountered in the process of developing Dooring — javascript function storage.

background

We all know that to build a front-end page basically need the following three elements:

  • Elements (UI)
  • Data (Data)
  • Events/Interactions (Events)

In the age of data-driven views, the relationship between these three elements tends to look something like this:

Visual platform design ideas are often based on the above process, we need to provide an editor environment for users to create views and interactions, the end user save products may look like this:

{
    "name": "Dooring form"."bgColor": "# 666"."share_url": "http://xxx.cn"."mount_event": [{"id": "123"."func": () = > {
                // Initialize the logic
                GamepadHapticActuator();
            },
            "sourcedata": []}],"body": [{"name": "header"."event": [{"id": "123"."type": "click"."func": () = > {
                        // Component custom interaction logicshowModal(); }}]}Copy the code

The problem is that json strings are easy to save (via json.stringify serialization), but how to save functions as well? How can a function be saved to enable JAVASCRIPT to run properly during page rendering?

Implementation scheme thinking

We all know that converting js objects to JSON can be done with json.stringify, but it has limitations, such as:

  1. Conversion values If there is a toJSON() method, what values are serialized by toJSON()
  2. Properties of non-array objects are not guaranteed to appear in a serialized string in a particular order
  3. Booleans, numbers, and string wrapper objects are automatically converted to their original values during serialization
  4. undefined, arbitrary functions, and symbol values are ignored during serialization (when appearing in property values of non-array objects) or converted tonull(when appearing in an array). Function undefined returns undefined when converted separately, as inJSON.stringify(function(){}) or JSON.stringify(undefined)
  5. All properties with symbol as the property key are completely ignored, even thoughreplacerThey are mandatory in the parameter
  6. Date the Date is converted to a string by calling toJSON() (same as date.toisostring ()), so it is treated as a string
  7. Values and nulls in NaN and Infinity formats are treated as null
  8. Other types of objects, including Map/Set/WeakMap/WeakSet, serialize only enumerable properties

As we can see in item 4, if we serialize an object that has a function, it will be ignored! Json.stringify can’t save functions, so what else can we do?

You might think of converting a Function to a string, serializing it with json.stringify and saving it to the back end, and then using eval or Function to convert the string to a Function when the component uses it. The general process is as follows:

Good, ideal is very beautiful, but the reality is very _______.

Let’s take a look at how func2String and String2func are implemented.

Js storage function scheme design

Those familiar with the JSON API may know that json.stringify supports three arguments. The second argument, replacer, can be a function or an array. As a function, it takes two arguments, a key and a value, both of which are serialized. The function needs to return a value in a JSON string, as follows:

  • If I return aNumberIs converted to the corresponding string and appended to the JSON string as the property value
  • If I return aStringThe string is appended to the JSON string as an attribute value
  • If I return aBoolean, “true” or “false” is appended to the JSON string as the property value
  • If any other object is returned, it is recursively serialized into a JSON string, and the replacer method is called for each property. Unless the object is a function, this case will not be serialized to JSON characters
  • If undefined is returned, the property value is not printed in the JSON string

So we can convert values of type function in the second function argument. As follows:

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

So it looks like we can save the function to the back end. Let’s see how to deserialize JSON with function strings.

Since we converted the function to a string, we need to know which strings we need to convert to the function when we reverse parse, and if we don’t do anything with the function we may need human recognition.

The disadvantage of human flesh recognition is that we need to use the re to extract the string with function characteristics, but there are many function writing methods, we need to consider many situations, can not guarantee that the string with function characteristics is a function.

So I have a simple way to extract the function without writing a complex re by injecting an identifier when the function is serialized so that we know which strings need to be parsed as functions, as follows:

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

This.func_prefix is the identifier we defined so that we can quickly parse the function when using json.parse. Json.parse also supports the second argument, which is used similarly to the second argument to json.stringify, and can be converted as follows:

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)
        }
    }
Copy the code

New Function converts a string to a js Function. It takes only string arguments. The optional arguments are the input arguments to a method and the required arguments are the contents of the method body.

The contents of the function body in our code above:

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

You can also use eval, but you should be careful to use eval because of public opinion.

The above solution already provides front-end storage functions, but it requires a lot of additional processing and optimization to be more engineered and robust, so that more people can use your library right out of the box.

The last

In order for more people to use this functionality directly, I have packaged the full JSON serialization scheme into a class library, which supports the following functions:

  • Stringify in nativeJSON.stringifyBased on support for serialization functions, error callbacks
  • The parse in nativeJSON.parseDeserialization function based on support, error callback
  • FuncParse serializes functions in a JS object with one click and keeps the JS object type unchanged

The installation method is as follows:

# or npm install xijs
yarn add xijs
Copy the code

Use:

import { parser } from 'xijs';

const a = {
    x: 12.b: function() {
      alert(1)}}const json = parser.stringify(a);
 const obj = parser.parse(json);
 // Call the method
 obj.b();
Copy the code

More recommended

  • React loading animation library developed from scratch
  • Develop a lightweight sliding verification code plug-in from scratch
  • How to design a visual platform component store?
  • Build engine from zero design visualization large screen
  • Build desktop visual editor Dooring from zero using Electron
  • (low code) Visual construction platform data source design analysis
  • Build a PC page editor pc-dooring from scratch
  • How to build building blocks to quickly develop H5 pages?