2012-02-28 14 views
27

¿Hay alguna manera de crear una función con un nombre real que se determine en tiempo de ejecución sin usar eval, y utilizando solo JavaScript puro? (Por lo tanto, no genera script elementos, como los que son específicos para el entorno del navegador [y de muchas maneras sería eval disfrazada de todas formas]; no hay que usan características no estándar de un motor de JavaScript en particular, etc.)¿Hay alguna forma no-eval para crear una función con un nombre determinado en tiempo de ejecución?

Tenga en cuenta que estoy específicamente no preguntar sobre funciones anónimas referenciados por variables o propiedades que tienen nombres, por ejemplo:

// NOT this 
var name = /* ...come up with the name... */; 
var obj = {}; 
obj[name] = function() { /* ... */ }; 

Allí, mientras que la propiedad objeto tiene un nombre, la funciónno lo hace. Las funciones anónimas están bien para muchas cosas, pero no es lo que estoy buscando aquí. Quiero que la función tenga un nombre (por ejemplo, que aparezca en las pilas de llamadas en los depuradores, etc.).

Respuesta

39

La respuesta de ECMAScript 2015 (también conocido como "ES6"):

. A partir de ES2015, la función creada por una expresión de función anónima asignada a una propiedad de objeto toma el nombre de esa propiedad de objeto. Mientras escribo esto el 11 de mayo de 2015, ningún motor JavaScript en la naturaleza que no sea el "Proyecto Spartan" de Microsoft para Windows 10 Preview admite este (sí, lo leyó, M $ llegó antes de Mozilla o Google) , pero está en la especificación y las implementaciones se pondrán al día.Actualización: a partir de octubre de 2016, las versiones recientes de Chrome implementan Function#name y las diversas reglas para asignar nombres; Firefox aún no lo hace, pero llegarán allí.

Así, por ejemplo, en ES2015 esto crea una función llamada "foo ###", donde ### es de 1-3 dígitos:

const dynamicName = "foo" + Math.floor(Math.random() * 1000); 
 
const obj = { 
 
    [dynamicName]() { 
 
    throw new Error(); 
 
    } 
 
}; 
 
const f = obj[dynamicName]; 
 
// See its `name` property (Edge and Chrome for now, eventually Firefox will get it) 
 
console.log("Function's `name` property: " + f.name + " (see compatibility note)"); 
 
// We can see that it truly has a name (even in Firefox which doesn't have the `name` property yet) via an exception: 
 
try { 
 
    f(); 
 
} catch (e) { 
 
    console.log(e.stack); 
 
}

que utiliza el nuevo ES2015 evaluados nombre de propiedad y nueva sintaxis de método para crear la función y darle un nombre dinámico, luego obtiene una referencia en f. (También funciona con [dynamicName]: function() { }, no se requiere sintaxis del método, sintaxis de la función está bien.)


La respuesta de ECMAScript 5(de 2012):

No. No puedes hazlo sin eval o su primo constructor Function. Sus opciones son:

  1. Vivo con una función anónima en su lugar. Los motores modernos hacen cosas para ayudar a la depuración con aquellos.

  2. Use eval.

  3. Utilice el constructor Function.

Detalles:

  1. Vivo con una función anónima en su lugar. Muchos motores modernos mostrarán un nombre útil (por ejemplo, en las pilas de llamadas y demás) si tiene una expresión agradable, no ambigua var name = function() { ... }; (que muestra el nombre de la variable), aunque técnicamente la función no tiene un nombre. En ES6, las funciones creadas de esa manera tendrán realmente nombres si se pueden inferir del contexto. De cualquier manera, sin embargo, si quieres un verdadero nombre definido en el tiempo de ejecución (un nombre que proviene de una variable), estás muy atascado.

  2. Use eval. eval es malo cuando puede evitarlo, pero con cadenas de las que tiene el control total, en un alcance que controle, con una comprensión de los costos (está activando un analizador de JavaScript), para hacer algo usted no puede hacer lo contrario (como en este caso), está bien siempre que realmente necesite hacer eso. Pero si no tienes el control de la cadena o el alcance, o si no quieres el costo, tendrás que vivir con una función anónima.

    Así es como se ve la opción eval:

    var name = /* ...come up with the name... */; 
    var f = eval(
        "(function() {\n" + 
        " function " + name + "() {\n" + 
        "  console.log('Hi');\n" + 
        " }\n" + 
        " return " + name + ";\n" + 
        "})();" 
    ); 
    

    Live example | Live source

    Eso crea una función con el nombre que encontramos en tiempo de ejecución sin filtrar el nombre en el ámbito contenedor (y sin activar el manejo defectuoso de expresiones de funciones nombradas en IE8 y anteriores), asignando una referencia a esa función a f. (Y formatea el código muy bien, así que es sencillo pasar por él en un depurador).

    Esto no solía asignar correctamente el nombre (sorprendentemente) en versiones anteriores de Firefox. A partir de la versión actual de su motor de JavaScript en Firefox 29, lo hace.

    Dado que utiliza eval, la función que cree tiene acceso al ámbito en el que se creó, lo cual es importante si es un programador ordenado que evita los símbolos globales. Así que esto funciona, por ejemplo:

    (function() { 
        function display(msg) { 
         var p = document.createElement('p'); 
         p.innerHTML = String(msg); 
         document.body.appendChild(p); 
        } 
    
        var name = /* ...come up with the name... */; 
        var f = eval(
         "(function() {\n" + 
         " function " + name + "() {\n" + 
         "  display('Hi');\n" +   // <=== Change here to use the 
         " }\n" +       //  function above 
         " return " + name + ";\n" + 
         "})();" 
        ); 
    })(); 
    
  3. Uso del Function constructor, como se demuestra en this article by Marcos Cáceres:

    var f = new Function(
        "return function " + name + "() {\n" + 
        " display('Hi!');\n" + 
        " debugger;\n" + 
        "};" 
    )(); 
    

    Live example | Live source

    Ahí creamos una función anónima temporal (la creada a través del constructor Function) y la llamamos; esa función anónima temporal crea una función nombrada usando una expresión de función nombrada. Que activará el identificador defectuoso de expresiones de funciones nombradas en IE8 y anteriores, pero no importa, porque los efectos secundarios de eso están limitados a la función temporal.

    Esto es más corta que la versión eval, pero tiene un problema: Funciones creadas a través de la Function constructor de hacer no tienen acceso al ámbito en el que fueron creados.Por lo tanto, el ejemplo anterior que usa display fallaría, porque display no estaría dentro del alcance de la función creada. (Here's an example fallando. Source). Por lo tanto, no es una opción para los codificadores ordenados que evitan los símbolos globales, pero es útil para aquellos momentos en los que desea para disociar la función generada del ámbito en el que la está generando.

+0

¿Qué tiene de malo la función anónima? – PiTheNumber

+0

@PiTheNumber: No dije que fueran malas. :-) De hecho, dije * "Las funciones anónimas están bien para muchas cosas ..." * Pero las funciones anónimas aparecen en las pilas de llamadas y, por ejemplo, "(anónimo)" (o similar), lo cual es menos que útil. Me gusta [ayudar a que mis herramientas me ayuden] (http://blog.niftysnippets.org/2010/03/anonymouses-anonymous.html), así que doy mis nombres a las funciones siempre que puedo. En este caso, estoy escribiendo una función función de fábrica en una biblioteca, y la decisión de si vale la pena tenerlo (opcionalmente) asignar las funciones nombres reales, o vivir sin nombre o un nombre estático en este caso. –

+2

mh, ya veo tu punto. Bueno, pensaré en evitar las funciones anónimas. ¡Gracias! – PiTheNumber

6

Aquí hay una función de utilidad que surgió hace algún tiempo. Utiliza la técnica del constructor Function como se describe en la gran respuesta de @ T.J.Crowder, pero mejora sus desventajas y permite un control detallado sobre el alcance de la nueva función.

function NamedFunction(name, args, body, scope, values) { 
    if (typeof args == "string") 
     values = scope, scope = body, body = args, args = []; 
    if (!Array.isArray(scope) || !Array.isArray(values)) { 
     if (typeof scope == "object") { 
      var keys = Object.keys(scope); 
      values = keys.map(function(p) { return scope[p]; }); 
      scope = keys; 
     } else { 
      values = []; 
      scope = []; 
     } 
    } 
    return Function(scope, "function "+name+"("+args.join(", ")+") {\n"+body+"\n}\nreturn "+name+";").apply(null, values); 
}; 

que le permite ser ordenado y evitando el acceso completo a su alcance a través de eval, por ejemplo, en el escenario anterior:

var f = NamedFunction("fancyname", ["hi"], "display(hi);", {display:display}); 
f.toString(); // "function fancyname(hi) { 
       // display(hi); 
       // }" 
f("Hi"); 
Cuestiones relacionadas