执行动态函数的小技巧

813 阅读2分钟

一、前言

最近研究了一些低代码开发平台,发现里面都有代码编辑的能力。所以就有一个疑问,这些代码在应用预览时/上线后是怎么执行的?

简化一下,用下面这段代码表示(这段代码只是一个字符串),就是我怎么在应用运行的时候去执行 addsubtract 方法。

export function add ( a, b ) {
  return a + b;
}

export function subtract ( a, b ) {
  return a - b;
}

二、前期的思考

2.1 正则 + eval

第一个想法是,用正则把所有的方法名及该方法对应的完整内容提取出来,然后用 eval 或者 new Function 去执行。

但是这个想法有一个问题,就是它没有方便的办法把和这个方法关联的函数也提取出来。所以就 放弃 了。

function log () {
  console.log('my log');
}

export function add ( a, b ) {
  log()
  return a + b;
}

2.2 export + babel

然后就是发现这些平台在定义函数的时候,都使用了 export 方式导出方法,并且没有限制 ES6 语法的使用,所以就想着用 babel 试一下。

使用的是 babel在线转码的工具,转换效果为:

image.png

在看到这两行代码的时候,整个思路也就清晰了:我们可以把这段代码中 export 的方法都挂载到 exports 对象上。

exports.add = add;
exports.subtract = subtract;

三、实现思路

3.1 引入babel

使用 @babel/standalone, 这个是在浏览器上使用的。

3.2 代码

// 编辑区的代码
const source = `
  export function add ( a, b ) {
    return a + b;
  }
`
// 创建 exports 对象
const exports = {};
// 使用 babel 转换
const output = Babel.transform(source, {
  presets: ['env']
}).code;
// 创建 function
const fn = new Function('exports', output);
// 执行fn, 并将 exports 对象传进去。执行后,add 方法就会绑定到 exports 对象上了。
fn(exports);
// 调用 exports 上的 add 方法
const result = exports['add'](1, 2);
console.log(result);

3.3 执行过程

  1. source 转码,即 output 为:
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.add = add;

function add(a, b) {
  return a + b;
}
  1. const fn = new Function('exports', output); 这行代码表示的是
function fn(exports) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    exports.add = add;

    function add(a, b) {
      return a + b;
    }
}
  1. 执行 fn(exports) 就和执行上面那个函数是一样的,这样 add 就赋值到 exports 上了。

  2. 最终执行 exports.add 就行了。

四、其他

执行上下文的绑定

在调用的时候,可以用 bind/apply/call 这种方式去处理。

获取方法列表

Object.keys(exports)

异步方法

export async function add ( a, b ) {
  return Promise.resolve(a + b);
}

考虑到兼容性的话,需要调整 babel 的配置。这个我本地没有测试,只是一个想法。

五、后记

这只是一种基础的实现方式,具体的还需要根据业务进行调整。当然 new Function 存在的安全问题和性能问题,也是需要在业务里考虑的。

六、参考