Como obter o nome da função de dentro dessa função?

263

Como posso acessar um nome de função de dentro dessa função?

// 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
fonte
bem, no pai, posso acessar outras funções por convenção, como ns [child] [schema] ou ns [child] [dbService]. Sem ele, eu tenho que codificar essas referências em todas as classes filho.
Scott Klarenbach
por que não passar a função filho como argumento para os pais? var pai = novo ns.parent (this);
plodder
porque existem dezenas dessas pesquisas e dezenas de crianças. Atualmente é o que estou fazendo, mas é sempre o mesmo e seria perfeito se essa lógica duplicada pudesse ser simplesmente colocada dentro do pai uma vez, com base na função derivada.
Scott Klarenbach
veja, não é a função filho que eu quero, é a convenção de nomenclatura usada, porque essa convenção de nomenclatura pode ser usada para carregar outras funções que não estão definidas atualmente no objeto filho, mas que estão relacionadas a essa criança em todo o sistema.
Scott Klarenbach
1
@ Scott Estou de acordo com todos os demais, pois você está gerenciando sua complexidade e estrutura de código incorretas para fazer isso. Esse tipo de acoplamento rígido é uma péssima decisão de design e vai fazer uma bagunça. @SimeVidas seu um grande necromante :)
Raynos

Respostas:

163

No ES5, a melhor coisa a fazer é:

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

O uso Function.callernão é padrão. Function.callere arguments.calleeambos são proibidos no modo estrito.

Edit: a resposta baseada em regex da nus abaixo consegue a mesma coisa, mas tem melhor desempenho!

No ES6, você pode apenas usar myFunction.name.

Nota: Lembre-se de que alguns minificadores JS podem jogar fora os nomes das funções, para compactar melhor; pode ser necessário ajustar as configurações para evitar isso.

Vlad A Ionescu
fonte
Embora isso não tenha respondido à pergunta suficientemente para mim (devido a funções anônimas), ele me deu uma idéia para fazer isso: o fun.toString().substr(0, 100)que, para minhas necessidades, será suficiente para localizar a função em questão. Então, obrigado pela inspiração!
Campbeln
3
Sim, myFunction.nameé a melhor coisa a fazer no ES6.
Vlad A Ionescu
15
Qual é o sentido myFunction.namese você digitar o nome da função? derrota o propósito de variáveis ​​mágicas.
adi518
2
@ adi518: não necessariamente .. suponha que você tenha qualquer variedade de funções e você iteração sobre ele, usando for / foreach ..then arr[index].nametambém funcionará :)
Debasish Chowdhury
2
@ adi518 Não é inútil. Se eu estou renomeando a função no meu IDE, myFunction.nametambém será atualizado. Certamente, o intelliJ suporta renomear identificadores dentro de strings, mas isso funciona muito menos consistentemente e fica silenciosamente desatualizado se alguém esquecer de marcar a opção "search in strings". myFunction.nameé uma escolha um pouco melhor do que codificar a string.
byxor
141

ES6 (inspirado na resposta de sendy halim abaixo):

myFunction.name

Explanação no MDN . A partir de 2015, trabalha no nodejs e nos principais navegadores, exceto no IE.

Nota: Nas funções ligadas, isso dará " bound <originalName>". Você precisará remover o "limite" se desejar obter o nome original.


ES5 (inspirado na resposta de Vlad):

Se você tiver uma referência à função, poderá:

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
}
  • Não executei testes de unidade nisso ou verifiquei diferenças de implementação, mas, em princípio, deve funcionar, se não deixar um comentário.
  • Nota: não funcionará em funções vinculadas
  • Nota: isso callere calleeé considerado obsoleto.

[1] Eu o incluo aqui porque é legal e muitas vezes as ferramentas de destaque de sintaxe falham em levar em consideração o espaço em branco entre o nome da função e os parênteses. Por outro lado, não conheço nenhuma implementação de .toString () que inclua espaço em branco aqui; é por isso que você pode omiti-lo.


Como resposta à pergunta original, eu descartaria a herança parasitária e buscaria alguns padrões de design de POO mais tradicionais. Eu escrevi um TidBits.OoJs para escrever confortavelmente código OOP em JavaScript com um conjunto de recursos que imitava C ++ (ainda não completo, mas principalmente).

Vejo pelos comentários que você gostaria de evitar a transmissão de informações parentnecessárias para seu construtor. Devo admitir que os padrões tradicionais de design não o salvam, pois geralmente é uma coisa boa tornar suas dependências óbvias e impostas.

Eu também sugeriria me afastar de funções anônimas. Eles apenas fazem da depuração e criação de perfil uma PITA porque tudo aparece como "função anônima", e não há nenhum benefício para eles que eu saiba.


fonte
3
Nice RegEx! Aqui está um teste de desempenho mostrando que seu código é o mais rápido em muitos casos. Em geral, parece que o RegEx bate o JS nativo em cerca de duas vezes a velocidade aqui, em média. jsperf.com/get-function-name/2 #
Beejor
@ Tomasz Oi, obrigado por apontar isso. Eu não sabia disso. Uma função vinculada é realmente uma nova função que envolve sua função original. A norma ecmascript § 19.2.3.2 define que o nome da nova função deve ser "bound" + originalName. O método toString também não funcionará em funções vinculadas ... Você terá que remover a palavra vinculada.
Qual é o sentido de myFunction.name se você digitar o nome da função? derrota o propósito de variáveis ​​mágicas.
Borjovsky 14/09/18
Observe que, se você estiver usando o React Native, isso pode não funcionar corretamente. Eu tenho um projeto de RN no qual estou trabalhando com a expo versão 36 e a .namepropriedade de um método do componente estava aparecendo como a string "value", independentemente do nome. Por incrível que pareça, durante os testes no aplicativo expo, tudo estava bem, mas uma vez compilado em um aplicativo real, ele travava silenciosamente.
Andy Corman
64

o que você está fazendo é atribuir uma função sem nome a uma variável. você provavelmente precisará da expressão da função nomeada ( http://kangax.github.com/nfe/ ).

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

no entanto, não tenho certeza de quanto é o navegador; há um problema no IE6 que faz com que você vaze o nome da função para o escopo externo. Além disso, o argumentos.callee é obsoleto e resultará em erro se você estiver usando strict mode.

curinga
fonte
1
Isso não funciona nas versões antigas do navegador Safari.
Anubhav Gupta
17

Qualquer constructorum expõe uma propriedade name, que é o nome da função. Você acessa o constructorvia uma instância (usando new) ou um 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
fonte
6
A primeira console.logchamada registra windowou Objectpara mim, dependendo de onde eu a corro, não Person.
24516 Bart Bart
Tem certeza de que instanciou usando "novo"? Caso contrário, não funcionará.
Roland
14

Parece a coisa mais estúpida que escrevi na minha vida, mas é engraçado: 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- profundidade da pilha. 0- retorna este nome de função, 1- pai, etc .;
[0 + d]- apenas para entender - o que acontece;
firefoxMatch- trabalha para o safari, mas eu realmente tive um pouco de tempo para testar, porque o dono do mac voltou depois de fumar e me afastou: '(

Teste:

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

gluttony();

Resultado:
Chrome:
insira a descrição da imagem aqui

Fitefox:
insira a descrição da imagem aqui

Esta solução foi criada apenas por diversão ! Não o use para projetos reais. Não depende da especificação do ES, depende apenas da realização do navegador. Após a próxima atualização do chrome / firefox / safari, ele pode estar quebrado.
Mais do que isso, não há erro (ha) no processamento - se dfor maior que o comprimento da pilha - você receberá um erro;
Para o padrão de mensagem de erro de outros wrowsers - você receberá um erro;
Ele deve funcionar para as classes ES6 ( .split('.').pop()), mas você pode obter um erro;

user3335966
fonte
13

Isso pode funcionar para você:

function foo() { bar(); }

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

executar foo () produzirá "foo" ou indefinido se você chamar de uma função anônima.

Também funciona com construtores; nesse caso, seria exibido o nome do construtor que chamava (por exemplo, "Foo").

Mais informações aqui: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Caller

Eles alegam que não é padrão, mas também é suportado por todos os principais navegadores: Firefox, Safari, Chrome, Opera e IE.

Jacob Mouka
fonte
Não disponível no modo estrito ... mmm
Ka Mok
8

Você não pode. As funções não têm nomes de acordo com o padrão (embora o mozilla possua esse atributo) - elas podem ser atribuídas apenas a variáveis ​​com nomes.

Também o seu comentário:

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

está dentro da função my.namespace.myFunc.getFn

O que você pode fazer é retornar o construtor de um objeto criado por novos

Então você poderia dizer

var obj = new my.namespace.myFunc();
console.info(obj.constructor); //my.namespace.myFunc
plodder
fonte
1
Eu preciso disso dentro do func porque estou passando pela cadeia de herança e omiti esse material por questões de brevidade.
Scott Klarenbach
1
'this' será a instância que invocou a função. Portanto, se você 'isso' dentro da função pai, isso fornecerá a instância da função que a chamou. É tudo o que deve precisar - não tenho certeza por isso que você precisa do nome também
plodder
1
bem, no pai, posso acessar outras funções por convenção, como ns [child] [schema] ou ns [child] [dbService]. Sem ele, eu tenho que codificar essas referências em todas as classes filho.
Scott Klarenbach
1
meu caso de uso para localizar o nome é facilitar a construção de mensagens console.error que informam de onde a mensagem veio sem ter que reverter para o despejo de pilha. por exemplo console.error ({'error': {self.name reference} + "erro com o parâmetro de entrada"). Torna-se muito mais fácil cortar / colar mensagens de erro que se repetem em várias funções se você não precisar parar e alterar o texto dentro delas a cada vez. No meu caso, estou retornando erros de promessa de várias chamadas de promessa - é bom saber qual promessa / função gerou o erro.
Scott Scott
7

Você pode usar isso para navegadores que suportam Error.stack (não quase todos, provavelmente)

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

é claro que isso é para a função atual, mas você entendeu.

babando feliz enquanto você codifica

no_ripcord
fonte
4

Você pode usar a namepropriedade para obter o nome da função, a menos que esteja usando uma função anônima

Por exemplo:

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());

agora vamos tentar com a função nomeada

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

agora você pode usar

// will return "someMethod"
p.getSomeMethodName()
sendy halim
fonte
4

Você pode usar o nome do construtor como:

{your_function}.prototype.constructor.name

esse código simplesmente retorna o nome de um método.

muhamad zolfaghari
fonte
3

como parte como ECMAScript 6você pode usar o método Function.name

function doSomething() {}

alert(doSomething.name); // alerts "doSomething"
pleerock
fonte
No ReactJS, você precisa adicionar isso: console.log (this.doSomething.name);
Bitclaw
2
que é fora da função
Scott
3

Você poderia usarFunction.name :

Na maioria das implementações de JavaScript, depois de ter a referência do construtor no escopo, você pode obter o nome da string a partir da propriedade name (por exemplo, Function.name ou Object.constructor.name

Você poderia usarFunction.callee :

O arguments.callermétodo nativo foi descontinuado, mas a maioria dos navegadores suporta Function.caller, que retornará o objeto de chamada real (seu corpo de código): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ Função / chamador? Redirectlocale = pt-BR & redirectslug = JavaScript% 2FReference% 2FGlobal_Objects% 2FFunction% 2Fcaller

Você pode criar um mapa de origem :

Se o que você precisa é a assinatura da função literal (o "nome" dela) e não o objeto em si, talvez seja necessário recorrer a algo um pouco mais personalizado, como criar uma referência de matriz dos valores de string da API que você precisará acesse com freqüência. Você pode mapeá-los juntos usando Object.keys()sua matriz de strings, ou procurar na biblioteca de mapas de fontes do Mozilla no GitHub, para projetos maiores: https://github.com/mozilla/source-map

Benny
fonte
2

Sei que essa é uma pergunta antiga, mas ultimamente tenho enfrentado um problema semelhante ao tentar decorar alguns métodos do React Component, para fins de depuração. Como as pessoas já disseram, arguments.callere arguments.calleesão proibidas no modo estrito, que provavelmente está ativado por padrão na transpilação do React. Você pode desativá-lo ou eu pude criar outro hack, porque em React todas as funções de classe são nomeadas, você pode realmente fazer isso:

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

Isso funcionou para mim.

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 do teste:

var obj = new AbstractDomainClass();
expect(obj.className()).toBe('AbstractDomainClass');
user1379687
fonte
1

Eu tive um problema semelhante e resolvi-o da seguinte maneira:

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

Esse código implementa, de uma maneira mais confortável, uma resposta que eu já li aqui no início desta discussão. Agora eu tenho uma função de membro recuperando o nome de qualquer objeto de função. Aqui está o script 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
fonte
1

Se eu entendi o que você queria fazer, é isso que faço dentro de um construtor de funções.

if (!(this instanceof arguments.callee)) {
    throw "ReferenceError: " + arguments.callee.name + " is not defined";
}
Zibri
fonte
2
Nota: Descontinuado - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Helcio R. Macedo Pandelo
0

Isso funcionará no ES5, ES6, em todos os navegadores e funções de modo estrito.

Veja como fica com uma função nomeada.

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

Veja como fica com uma função anônima.

(() => {
  console.log(new Error().stack.split(/\r\n|\r|\n/g)[1].trim());
})();
at <anonymous>:2:15
Eric
fonte
Isso produz resultados diferentes por navegador. Chrome: at myName (<anonymous>:2:15)Firefox:@debugger eval code:3:3
jkdev
(função myName () {console.log (novo erro (). stack.split (/ \ r \ n | \ r | \ n / g) [1] .trim (). split ("") [1]) ;}) ();
Zibri 01/02
-2

veja aqui: http://www.tek-tips.com/viewthread.cfm?qid=1209619

arguments.callee.toString();

parece ser adequado para suas necessidades.

Manuel Bitto
fonte
3
argumentos.callee.toString () apenas retorna a fonte da função.
Scott Klarenbach
2
Não é permitido: 'chamador', 'receptor', e as propriedades 'argumentos' não pode ser acessado em funções modo restrito ou os objetos argumentos para chamadas para eles
Eric Hodonsky
-2

você pode usar o Error.stack para rastrear o nome da função e a posição exata de onde você está.

Veja stacktrace.js

kofifus
fonte
-3

Maneira fácil de obter o nome da função de dentro da função que você está executando.

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

Dave Brown
fonte
1
me dá um GUID
Tamir Daniely
1
estar ciente de que o conteúdo thisbasicamente pode ser qualquer coisa. tente(function(){console.log(this.name);}).bind({name: "put basically anything here even an object maybe a 1TB string maybe a class definition"})()
Valen