Me gustaría definir dos funciones (o clases) en Javascript con exactamente el mismo cuerpo de función, pero que sean objetos completamente diferentes. El caso de uso para esto es que tengo una lógica común en el cuerpo que es polimórfica (la función puede aceptar múltiples tipos), pero al llamar a la función con un solo tipo, la función termina más rápido, supongo que ya que el JIT puede tomar un camino rápido más feliz en cada caso.
Una forma de hacerlo es simplemente repetir el cuerpo de la función por completo:
function func1(x) { /* some body */ } function func2(x) { /* some body */ } Otra forma de lograr lo mismo con menos repetición es eval() :
function func(x) { /* some body */ } function factory() { return eval("(" + func.toString() + ")") } let func1 = factory(), func2 = factory() La desventaja de eval() , por supuesto, es que cualquier otra herramienta (minificadores, optimizadores, etc.) se toman completamente por sorpresa y tienen el potencial de alterar mi código para que esto no funcione.
¿Hay alguna forma sensata de hacer esto dentro de los límites de una cadena de herramientas estándar (yo uso Typescript, esbuild y Vite), sin usar trucos de eval() o simplemente copiar y pegar el código? También tengo la pregunta análoga sobre las definiciones de clase.
Editar: para resumir lo que ha estado sucediendo en los comentarios:
function factory() { function func() { /* some body */ } return func } let func1 = factory(), func2 = factory() como lo demuestra este segundo microbenchmark . Esto se debe a que un JIT solo compilará el cuerpo de una función una vez, incluso si es un cierre.puedes probar de esta manera:
function func1(x) { /* some body */ } var func2 = new Function("x", func1.toString().match(/{.+/g)[0].slice(1,-1)); Estoy definiendo la nueva función func2(x) usando el constructor de funciones donde los primeros n-1 argumentos son parámetros y el último parámetro es el cuerpo de la función
para el cuerpo de la función, utilicé expresiones regulares para extraer todas las líneas en el alcance de func1 es decir, entre las llaves de función { y }
puede leer más sobre el constructor de funciones aquí
El único requisito de la cadena de herramientas es esbuild, pero también funcionarán otros paquetes como rollup:
Producción:
// 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);Con estos archivos como entrada:
// func.js export function func(x) { return x+1 } // main.js import {func as func1} from './.func1'; import {func as func2} from './.func2'; func1(x) + func2(x)Los archivos importados son en realidad enlaces duros al mismo archivo generado por este script:
#!/bin/sh # generate-func.sh ln func.js .func1.js ln func.js .func2.jsPara evitar que los enlaces duros arruinen tu repositorio, dile a git que ignore los enlaces duros generados. De lo contrario, los enlaces duros pueden divergir como archivos separados si se registran y desprotegen nuevamente:
# .gitignore .func*
notas
Su idea de tener una "fábrica" o una función maestra que produciría funciones independientes, físicamente separadas, en lugar de hacer referencia a la misma es un muy buen comienzo...
Antes de las animaciones CSS, teníamos que usar JavaScript para crear efectos cronometrados, etc. La idea de pasar el mouse sobre elementos que los iluminarían, pero a medida que el mouse deja ese elemento sobre el otro, querrás que se desvanezcan lentamente (dejando un rastro suave de luz a la moda), no abruptamente sobre cientos de elementos, sería imposible hacerlo con una sola función, mientras que reescribir el mismo cuerpo de función con nombres ligeramente diferentes cientos de veces no tendría sentido, y mucho menos asignar cada función al elemento de destino individualmente.
Nos enfrentamos al mismo problema...
Para acortar la historia, teníamos que ir con una 'solución sensata' como dices, o abandonar la idea por completo... ¡Yo no lo hice!
Aquí hay una lógica detrás de la solución (de fábrica) y un contenido de registro de la consola para demostrar que estas funciones gemelas idénticas están separadas físicamente (según sea necesario) y no hacen referencia a las mismas.
function Factory( x ){ return function( ){ console.log( x ) } }; func1 = new Factory("I'm the first born!"); func2 = new Factory("I'm the second born!"); func1(); func2();Espero que encuentre esta solución lo suficientemente sensata.
pd: puede agregar tantos argumentos como necesite a Factory y proporcionar sus valores específicos durante la creación de las funciones que estarán disponibles durante la sesión en todo momento, tal como lo está la cadena console.log que vemos en la demostración.
Saludos.
Jugando con la Function constructora de funciones, supongo que esto haría el trabajo
function func(x) { /* some body */ } function factory() { return ( new Function('return ' + func.toString()) )(); } let func1 = factory(), func2 = factory()