在 Javascript 中制作函数的真实副本

Jop*_*ppy 18 javascript ecmascript-6

我想在 Javascript 中定义两个函数(或类),它们具有完全相同的函数体,但它们是完全不同的对象。用例是我在主体中有一些通用逻辑,它是多态的(函数可以接受多种类型),但是通过只调用具有单一类型的函数,函数最终会更快,我假设因为 JIT 可以在每种情况下都采取更快乐的快速路径。

一种方法是简单地完全重复函数体:

function func1(x) { /* some body */ }
function func2(x) { /* some body */ }
Run Code Online (Sandbox Code Playgroud)

另一种以较少重复完成相同事情的方法是eval()

function func(x) { /* some body */ }
function factory() { return eval("(" + func.toString() + ")") }
let func1 = factory(), func2 = factory()
Run Code Online (Sandbox Code Playgroud)

eval()当然,缺点是任何其他工具(缩小器、优化器等)都完全出乎意料,并且有可能破坏我的代码,因此这不起作用。

在标准工具链(我使用 Typescript、esbuild 和 Vite)的范围内,是否有任何明智的方法可以做到这一点,而不使用技巧eval()或只是复制粘贴代码?我也有关于类定义的类似问题。


编辑:总结评论中发生的事情:

  1. 是的,性能差异是真实且可衡量的(尤其是在 Chrome 上,在 Firefox 和 Safari 上不那么明显),正如这个微基准所证明的那样。引发这个问题的真实程序要大得多,性能差异也更明显,我怀疑是因为 JIT 可以为单态函数做更多的内联,这会产生许多连锁反应。
  2. 返回闭包的明显解决方案不起作用,即
    function factory() { function func() { /* some body */ } return func }
    let func1 = factory(), func2 = factory()
    
    Run Code Online (Sandbox Code Playgroud) 正如第二个微基准所证明的那样。这是因为 JIT 只会编译一个函数体一次,即使它是一个闭包。
  3. 这可能已经是最好的解决方案,至少在标准 JS/Typescript 工具链(不包括代码生成或宏工具)中工作时如此。

Lef*_*ium 5

  1. 使用文件系统使单个 ESM 模块文件显示为多个不同的文件。
  2. 然后多次导入您的函数。

唯一的工具链要求是 esbuild,但其他捆绑器(如 rollup)也可以工作:

输出:

// From command: esbuild  main.js --bundle
(() => {
  // .func1.js
  function func(x2) {
    return x2 + 1;
  }

  // .func2.js
  function func2(x2) {
    return x2 + 1;
  }

  // main.js
  func(x) + func2(x);
})();



// From command: rollup main.js
function func$1(x) { return x+1 }

function func(x) { return x+1 }

func$1(x) + func(x);
Run Code Online (Sandbox Code Playgroud)

使用这些文件作为输入:

// func.js
export function func(x) { return x+1 }
Run Code Online (Sandbox Code Playgroud)
// main.js
import {func as func1} from './.func1';
import {func as func2} from './.func2';

func1(x) + func2(x)
Run Code Online (Sandbox Code Playgroud)

导入的文件实际上是指向此脚本生成的同一文件的硬链接

#!/bin/sh
# generate-func.sh

ln func.js .func1.js
ln func.js .func2.js

Run Code Online (Sandbox Code Playgroud)

为了防止硬链接弄乱您的存储库,请告诉 git 忽略生成的硬链接。否则,如果硬链接被签入并再次签出,它们可能会作为单独的文件出现分歧:

# .gitignore
.func*

笔记

  • 为了简单起见,我将所有内容都放在同一个文件夹中,但您可以在自己的文件夹中生成硬链接以进行组织。
  • 如果您使用指向同一 JS 文件的符号链接,Rollup 将“看穿”这个技巧。然而,目录的符号链接工作正常。
  • 在适用于 Windows 的 git-bash 上进行了测试;其他平台上的YMMV。


小智 -1

你可以尝试这样:

function func1(x) { /* some body */ }

var func2 = new Function("x", func1.toString().match(/{.+/g)[0].slice(1,-1));
Run Code Online (Sandbox Code Playgroud)

我正在func2(x)使用函数构造函数定义新函数,其中前 n-1 个参数是参数,最后一个参数是函数体

对于函数体,我使用正则表达式提取func1函数大括号{和ie 范围内的所有行}

您可以在此处阅读有关 Function 构造函数的更多信息