¿Cómo obtener el nombre de la función desde esa función?

263

¿Cómo puedo acceder al nombre de una función desde dentro de esa función?

// parasitic inheritance
var ns.parent.child = function() {
  var parent = new ns.parent();
  parent.newFunc = function() {

  }
  return parent;
}

var ns.parent = function() {
  // at this point, i want to know who the child is that called the parent
  // ie
}

var obj = new ns.parent.child();
Scott Klarenbach
fuente
bueno, en el padre, puedo acceder a otras funciones por convención, como ns [child] [esquema] o ns [child] [dbService]. Sin ella, tengo que codificar estas referencias en cada clase secundaria.
Scott Klarenbach
¿por qué no simplemente pasar la función secundaria como argumento al padre? var parent = new ns.parent (this);
plodder
porque hay docenas de tales búsquedas y docenas de niños. Eso es lo que estoy haciendo actualmente, pero es lo mismo cada vez y sería perfecto si esa lógica duplicada simplemente pudiera colocarse dentro del padre una vez, en función de la función derivada.
Scott Klarenbach
vea, no es la función secundaria que quiero, es la convención de nomenclatura utilizada, porque esa convención de nomenclatura puede usarse para cargar otras funciones que no están definidas actualmente en el objeto secundario, pero están relacionadas con ese elemento secundario en todo el sistema.
Scott Klarenbach
1
@ Scott Estoy de acuerdo con todos los demás, tu manejo de tu complejidad y estructura de código es incorrecto para tener que hacer esto. Este tipo de acoplamiento rígido es una mala decisión de diseño y va a hacer un desastre. @SimeVidas eres un gran nigromante :)
Raynos

Respuestas:

163

En ES5, lo mejor que puede hacer es:

function functionName(fun) {
  var ret = fun.toString();
  ret = ret.substr('function '.length);
  ret = ret.substr(0, ret.indexOf('('));
  return ret;
}

El uso Function.callerno es estándar. Function.callery arguments.calleeambos están prohibidos en modo estricto.

Editar: la respuesta basada en expresiones regulares de nus a continuación logra lo mismo, ¡pero tiene un mejor rendimiento!

En ES6, solo puede usar myFunction.name.

Nota: Tenga en cuenta que algunos minificadores JS pueden descartar nombres de funciones, para comprimir mejor; Es posible que deba modificar su configuración para evitar eso.

Vlad A Ionescu
fuente
Si bien esto no respondió la pregunta lo suficiente para mí (debido a funciones anónimas), me dio la idea de hacer esto: lo fun.toString().substr(0, 100)que para mis necesidades será suficiente para ubicar la función en cuestión. Entonces, gracias por la inspiración!
Campbeln
3
Sí, myFunction.namees lo mejor que se puede hacer en ES6.
Vlad A Ionescu
15
¿Qué sentido tiene myFunction.namesi escribe el nombre de la función? derrota el propósito de las variables mágicas.
adi518
2
@ adi518: no necesariamente ... supongamos que tiene una serie de funciones y está iterando sobre ella, usando for / foreach ... arr[index].nameluego también funcionará :)
Debasish Chowdhury
2
@ adi518 No es inútil. Si cambio el nombre de la función dentro de mi IDE, myFunction.nametambién se actualizará. Claro, intelliJ admite cambiar el nombre de los identificadores dentro de las cadenas, pero eso funciona de manera mucho menos consistente y se quedará desactualizado en silencio si alguien olvida marcar la opción "buscar en cadenas". myFunction.namees una opción un poco mejor que codificar la cadena.
byxor
141

ES6 (inspirado por la respuesta de sendy halim a continuación):

myFunction.name

Explicación sobre MDN . A partir de 2015 funciona en nodejs y en todos los principales navegadores, excepto IE.

Nota: En las funciones enlazadas, esto dará " bound <originalName>". Tendrá que quitar el "límite" si desea obtener el nombre original.


ES5 (inspirado por la respuesta de Vlad):

Si tiene una referencia a la función, puede hacer:

function functionName( func )
{
    // Match:
    // - ^          the beginning of the string
    // - function   the word 'function'
    // - \s+        at least some white space
    // - ([\w\$]+)  capture one or more valid JavaScript identifier characters
    // - \s*        optionally followed by white space (in theory there won't be any here,
    //              so if performance is an issue this can be omitted[1]
    // - \(         followed by an opening brace
    //
    var result = /^function\s+([\w\$]+)\s*\(/.exec( func.toString() )

    return  result  ?  result[ 1 ]  :  '' // for an anonymous function there won't be a match
}
  • No he realizado pruebas unitarias sobre esto, ni he verificado diferencias de implementación, pero en principio debería funcionar, si no deja un comentario.
  • Nota: no funcionará en funciones enlazadas
  • Nota: eso callery calleese consideran obsoletos.

[1] Lo incluyo aquí porque es legal y, a menudo, suficientes herramientas de resaltado de sintaxis no tienen en cuenta el espacio en blanco entre el nombre de la función y el paréntesis. Por otro lado, no conozco ninguna implementación de .toString () que incluya espacios en blanco aquí, por lo que puede omitirlo.


Como respuesta a la pregunta original, dejaría la herencia parasitaria y elegiría algunos patrones de diseño OOP más tradicionales. Escribí un TidBits.OoJs para escribir cómodamente el código OOP en JavaScript con un conjunto de características que imita C ++ (aún no está completo, pero en su mayoría).

Veo por los comentarios que le gustaría evitar pasar las parentnecesidades de información a su constructor. Sin embargo, debo admitir que los patrones de diseño tradicionales no lo salvarán de ese, ya que generalmente se considera bueno hacer que sus dependencias sean obvias y forzadas.

También sugeriría alejarse de las funciones anónimas. Solo realizan depuración y creación de perfiles de un PITA porque todo aparece como "función anónima", y no hay ningún beneficio para ellos que yo sepa.


fuente
3
Nice RegEx! Aquí hay una prueba de rendimiento que muestra que su código es el más rápido en muchos casos. En general, parece que RegEx supera a JS nativo en aproximadamente 2 veces la velocidad aquí en promedio. jsperf.com/get-function-name/2
Beejor
@Tomasz Hola, gracias por señalarlo. No lo sabia. Una función enlazada es en realidad una nueva función que envuelve su función original. El estándar ecmascript § 19.2.3.2 define que el nombre de la nueva función debe estar "enlazado" + nombre original. El método toString tampoco funcionará en funciones enlazadas ... Tendrá que quitar la palabra enlazada.
¿Cuál es el punto en myFunction.name si escribe el nombre de la función? derrota el propósito de las variables mágicas.
Borjovsky
Tenga en cuenta que si está utilizando React Native, esto puede no funcionar correctamente. Tengo un proyecto RN en el que estoy trabajando con la versión 36 de la expo y la .namepropiedad de un método componente se mostraba como el "valor" de la cadena, sin importar cómo se llamara. Por extraño que parezca, mientras estaba probando en la aplicación de la exposición, estuvo bien, sin embargo, una vez compilada en una aplicación real, se bloqueaba en silencio.
Andy Corman
64

lo que estás haciendo es asignar una función sin nombre a una variable. probablemente necesite una expresión de función con nombre en su lugar ( http://kangax.github.com/nfe/ ).

var x = function x() {
    console.log( arguments.callee.name );
}
x();

sin embargo, no estoy seguro de cuánto es el navegador cruzado; Hay un problema con IE6 que hace que el nombre de su función se filtre al ámbito externo. Además, argumentos.callee está en desuso y resultará en un error si lo está utilizando strict mode.

comodín
fuente
1
Esto no funciona en versiones anteriores del navegador Safari.
Anubhav Gupta
17

Cualquiera constructorexpone una propiedad name, que es el nombre de la función. Accede a constructortravés de una instancia (usando new) o un prototype:

function Person() {
  console.log(this.constructor.name); //Person
}

var p = new Person();
console.log(p.constructor.name); //Person

console.log(Person.prototype.constructor.name);  //Person
Roland
fuente
66
Los console.logregistros de la primera llamada windowo Objectpara mí dependiendo de dónde lo ejecute, no Person.
Bart S
¿Estás seguro de que has creado una instancia con "nuevo"? De lo contrario, no puede funcionar.
roland
14

Parece lo más estúpido que escribí en mi vida, pero es divertido: D

function getName(d){
  const error = new Error();
  const firefoxMatch = (error.stack.split('\n')[0 + d].match(/^.*(?=@)/) || [])[0];
  const chromeMatch = ((((error.stack.split('at ') || [])[1 + d] || '').match(/(^|\.| <| )(.*[^(<])( \()/) || [])[2] || '').split('.').pop();
  const safariMatch = error.stack.split('\n')[0 + d];

  // firefoxMatch ? console.log('firefoxMatch', firefoxMatch) : void 0;
  // chromeMatch ? console.log('chromeMatch', chromeMatch) : void 0;
  // safariMatch ? console.log('safariMatch', safariMatch) : void 0;

  return firefoxMatch || chromeMatch || safariMatch;
}

d- profundidad de la pila. 0- devolver el nombre de esta función, 1- padre, etc .;
[0 + d]- solo para entender - lo que sucede;
firefoxMatch- funciona para un safari, pero realmente tuve un poco de tiempo para las pruebas, porque el dueño de Mac había regresado después de fumar y me llevó lejos: '(

Pruebas:

function limbo(){
  for(let i = 0; i < 4; i++){
    console.log(getName(i));
  }
}
function lust(){
  limbo();
}
function gluttony(){
  lust();
}

gluttony();

Resultado:
Chrome:
ingrese la descripción de la imagen aquí

Fitefox:
ingrese la descripción de la imagen aquí

¡Esta solución fue creada solo por diversión ! No lo use para proyectos reales. No depende de la especificación ES, solo depende de la realización del navegador. Después de la próxima actualización de Chrome / Firefox / Safari, puede estar roto.
Más que eso, no hay procesamiento de error (ha) - si dserá mayor que la longitud de la pila - obtendrá un error;
Para el patrón de mensaje de error de otros wrowsers, obtendrá un error;
Debe funcionar para las clases ES6 ( .split('.').pop()), pero aún puede obtener un error;

usuario3335966
fuente
13

Esto podría funcionar para usted:

function foo() { bar(); }

function bar() { console.log(bar.caller.name); }

ejecutar foo () generará "foo" o indefinido si llama desde una función anónima.

También funciona con constructores, en cuyo caso generaría el nombre del constructor que realiza la llamada (por ejemplo, "Foo").

Más información aquí: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Caller

Afirman que no es estándar, pero también que es compatible con todos los principales navegadores: Firefox, Safari, Chrome, Opera e IE.

Jacob Mouka
fuente
No disponible en modo estricto ... mmm
Ka Mok
8

No puedes Las funciones no tienen nombres según el estándar (aunque mozilla tiene dicho atributo), solo pueden asignarse a variables con nombres.

También tu comentario:

// access fully qualified name (ie "my.namespace.myFunc")

está dentro de la función my.namespace.myFunc.getFn

Lo que puede hacer es devolver el constructor de un objeto creado por new

Entonces podrías decir

var obj = new my.namespace.myFunc();
console.info(obj.constructor); //my.namespace.myFunc
empollón
fuente
1
lo necesito dentro del func porque lo estoy pasando por la cadena de herencia y omití esas cosas por brevedad.
Scott Klarenbach el
1
'this' será la instancia que invocó la función. Entonces, si 'esto' dentro de la función principal que le dará la instancia de la función que lo llamó. Eso es todo lo que debe necesitar, no estoy seguro de por qué necesita el nombre también
plodder
1
bueno, en el padre, puedo acceder a otras funciones por convención, como ns [child] [esquema] o ns [child] [dbService]. Sin ella, tengo que codificar estas referencias en cada clase secundaria.
Scott Klarenbach
1
mi caso de uso para encontrar el nombre es facilitar la construcción de mensajes de console.error que indican de dónde vino el mensaje sin tener que volver al volcado de la pila. ej. console.error ({'error': {self.name reference} + "error con parámetro de entrada"). Se vuelve mucho más fácil cortar / pegar mensajes de error que se repiten en múltiples funciones si no tiene que parar y cambiar el texto dentro de ellas cada vez. En mi caso, estoy devolviendo errores de promesa de múltiples llamadas de promesa, es bueno saber qué promesa / función generó el error.
Scott
7

Podría usar esto, para navegadores que admitan Error.stack (probablemente no todos)

function WriteSomeShitOut(){ 
  var a = new Error().stack.match(/at (.*?) /);
  console.log(a[1]);
} 
WriteSomeShitOut();

Por supuesto, esto es para la función actual, pero se entiende la idea.

feliz babeando mientras codificas

no_ripcord
fuente
4

Puede usar la namepropiedad para obtener el nombre de la función, a menos que esté usando una función anónima

Por ejemplo:

var Person = function Person () {
  this.someMethod = function () {};
};

Person.prototype.getSomeMethodName = function () {
  return this.someMethod.name;
};

var p = new Person();
// will return "", because someMethod is assigned with anonymous function
console.log(p.getSomeMethodName());

ahora intentemos con una función con nombre

var Person = function Person () {
  this.someMethod = function someMethod() {};
};

ahora puedes usar

// will return "someMethod"
p.getSomeMethodName()
Sendy Halim
fuente
4

Puede usar el nombre del constructor como:

{your_function}.prototype.constructor.name

este código simplemente devuelve el nombre de un método.

muhamad zolfaghari
fuente
3

como parte ya ECMAScript 6que puedes usar el método Function.name

function doSomething() {}

alert(doSomething.name); // alerts "doSomething"
pleerock
fuente
En ReactJS necesita agregar esto: console.log (this.doSomething.name);
Bitclaw
2
eso está fuera de la función
Scott
3

Podrías usarFunction.name :

En la mayoría de las implementaciones de JavaScript, una vez que tiene el alcance de la referencia de su constructor, puede obtener su nombre de cadena a partir de su propiedad de nombre (por ejemplo, Function.name u Object.constructor.name

Podrías usarFunction.callee :

El arguments.callermétodo nativo ha quedado en desuso, pero la mayoría de los navegadores son compatibles Function.caller, lo que devolverá el objeto de invocación real (su cuerpo de código): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ Función / llamador? Redirectlocale = es-ES & redirectslug = JavaScript% 2FReference% 2FGlobal_Objects% 2FFunction% 2Fcaller

Podrías crear un mapa fuente :

Si lo que necesita es la firma de la función literal (el "nombre" del mismo) y no el objeto en sí, es posible que deba recurrir a algo un poco más personalizado, como crear una referencia de matriz de los valores de cadena API que necesitará. acceder con frecuencia. Puede mapearlos juntos Object.keys()y su conjunto de cadenas, o buscar en la biblioteca de mapas fuente de Mozilla en GitHub, para proyectos más grandes: https://github.com/mozilla/source-map

Benny
fuente
2

Sé que esta es una vieja pregunta, pero últimamente me he enfrentado a un problema similar al tratar de decorar algunos métodos de React Component, con fines de depuración. Como la gente ya dijo, arguments.callery arguments.calleeestán prohibidos en modo estricto, que probablemente esté habilitado de forma predeterminada en su Transpilación de React. Puede deshabilitarlo, o he podido encontrar otro truco, porque en React se nombran todas las funciones de clase, en realidad puede hacer esto:

Component.prototype.componentWillMount = function componentWillMount() {
    console.log('Callee name: ', this.__proto__.constructor.toString().substr(0,30));
...
}
frankies
fuente
1

Esto funcionó para mí.

function AbstractDomainClass() {
    this.className = function() {
        if (!this.$className) {
            var className = this.constructor.toString();
            className = className.substr('function '.length);
            className = className.substr(0, className.indexOf('('));
            this.$className = className;
        }
        return this.$className;
    }
}

Código de prueba:

var obj = new AbstractDomainClass();
expect(obj.className()).toBe('AbstractDomainClass');
usuario1379687
fuente
1

Tuve un problema similar y lo resolví de la siguiente manera:

Function.prototype.myname = function() {
   return this.toString()
       .substr( 0, this.toString().indexOf( "(" ) )
       .replace( "function ", "" ); 
}

Este código implementa, de una manera más cómoda, una respuesta que ya leí aquí en la parte superior de esta discusión. Ahora tengo una función miembro que recupera el nombre de cualquier objeto de función. Aquí está el guión completo ...

<script language="javascript" TYPE="text/javascript">

    Function.prototype.myname = function() { 
        return this.toString()
            .substr( 0, this.toString().indexOf( "(" ) )
            .replace("function ", "" ); 
    }
    function call_this( _fn ) { document.write( _fn.myname() ); }
    function _yeaaahhh() { /* do something */ }
    call_this( _yeaaahhh ); 

</script>
Sandro Rosa
fuente
0

Esto funcionará en ES5, ES6, todos los navegadores y funciones de modo estricto.

Así es como se ve con una función con nombre.

(function myName() {
  console.log(new Error().stack.split(/\r\n|\r|\n/g)[1].trim());
})();
at myName (<anonymous>:2:15)

Así es como se ve con una función anónima.

(() => {
  console.log(new Error().stack.split(/\r\n|\r|\n/g)[1].trim());
})();
at <anonymous>:2:15
Eric
fuente
Esto produce resultados diferentes por navegador. Chrome: at myName (<anonymous>:2:15)Firefox:@debugger eval code:3:3
jkdev
(function myName () {console.log (nuevo Error (). stack.split (/ \ r \ n | \ r | \ n / g) [1] .trim (). split ("") [1]) ;}) ();
Zibri
-2

mira aquí: http://www.tek-tips.com/viewthread.cfm?qid=1209619

arguments.callee.toString();

Parece ser adecuado para sus necesidades.

Manuel Bitto
fuente
3
argumentos.callee.toString () simplemente devuelve el origen de la función.
Scott Klarenbach
2
No permitido: no se puede acceder a las propiedades 'llamador', 'llamante' y 'argumentos' en funciones de modo estricto o en los objetos de argumentos para llamadas a ellos
Eric Hodonsky
-2

puede usar Error.stack para rastrear el nombre de la función y la posición exacta de dónde se encuentra.

Ver stacktrace.js

kofifus
fuente
-3

Manera fácil de obtener el nombre de la función desde la función que está ejecutando.

function x(){alert(this.name)};x()

Dave Brown
fuente
1
me da un GUID
Tamir Daniely
1
Tenga en cuenta que el contenido thisbásicamente puede ser cualquier cosa. probar(function(){console.log(this.name);}).bind({name: "put basically anything here even an object maybe a 1TB string maybe a class definition"})()
Valen