Jak uzyskać nazwę funkcji z tej funkcji?

263

Jak mogę uzyskać dostęp do nazwy funkcji z wnętrza tej funkcji?

// 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
źródło
cóż, w rodzicu mogę następnie uzyskać dostęp do innych funkcji zgodnie z konwencją, takich jak ns [child] [schemat] lub ns [child] [dbService]. Bez tego muszę kodować te odwołania w każdej klasie podrzędnej.
Scott Klarenbach,
dlaczego nie przekazać funkcji potomnej jako argumentu rodzicowi? var parent = new ns.parent (this);
plodder
ponieważ istnieją dziesiątki takich wyszukiwań i dziesiątki dzieci. Właśnie to robię, ale za każdym razem jest to samo i byłoby idealnie, gdyby taka zduplikowana logika mogła zostać po prostu umieszczona w obiekcie nadrzędnym raz, na podstawie funkcji pochodnej.
Scott Klarenbach,
widzę, to nie jest funkcja potomna, której chcę, to zastosowana konwencja nazewnictwa, ponieważ ta konwencja nazewnictwa może być używana do ładowania innych funkcji, które nie są obecnie zdefiniowane w obiekcie potomnym, ale są powiązane z tym dzieckiem w całym systemie.
Scott Klarenbach,
1
@ Scott Zgadzam się ze wszystkimi innymi, że zarządzanie swoją złożonością i strukturą kodu jest złe, jeśli trzeba to zrobić. Ten rodzaj sztywnego połączenia jest złą decyzją projektową i spowoduje bałagan. @SimeVidas twój wielki nekromanta :)
Raynos

Odpowiedzi:

163

W ES5 najlepiej jest:

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

Używanie Function.callerjest niestandardowe. Function.calleri arguments.calleeoba są zabronione w trybie ścisłym.

Edycja: poniższa odpowiedź wyrażenia regularnego nus osiąga to samo, ale ma lepszą wydajność!

W ES6 możesz po prostu użyć myFunction.name.

Uwaga: Uwaga: niektóre minizatory JS mogą wyrzucać nazwy funkcji, aby lepiej się kompresować; może być konieczne dostosowanie ich ustawień, aby tego uniknąć.

Vlad A Ionescu
źródło
Chociaż nie odpowiedziałem na to pytanie w wystarczający sposób (z powodu anonimowych funkcji), dał mi pomysł, aby to zrobić: fun.toString().substr(0, 100)co dla moich potrzeb wystarczy, aby zlokalizować daną funkcję. Dzięki za inspirację!
Campbeln,
3
Tak, myFunction.namenajlepiej robić w ES6.
Vlad A Ionescu,
15
Po co myFunction.namewpisywać nazwę funkcji? pokonuje cel magicznych zmiennych.
adi518
2
@ adi518: niekoniecznie ... załóżmy, że masz jakąkolwiek tablicę funkcji i iterujesz po niej, używając for / foreach ..także arr[index].nameteż zadziała :)
Debasish Chowdhury
2
@ adi518 To nie jest bezużyteczne. Jeśli zmienię nazwę funkcji w moim IDE, myFunction.namerównież zostanie zaktualizowany. Jasne, intelliJ obsługuje zmianę nazw identyfikatorów w łańcuchach, ale działa to o wiele mniej konsekwentnie i po cichu stanie się przestarzałe, jeśli ktoś zapomni zaznaczyć opcję „szukaj w łańcuchach”. myFunction.namejest nieco lepszym wyborem niż kodowanie łańcucha na stałe.
byxor
141

ES6 (zainspirowany odpowiedzią sendy halim poniżej):

myFunction.name

Objaśnienie dotyczące MDN . Od 2015 roku działa w nodejs i wszystkich głównych przeglądarkach oprócz IE.

Uwaga: W przypadku funkcji związanych daje to „ bound <originalName>”. Będziesz musiał usunąć „związany”, jeśli chcesz uzyskać oryginalną nazwę.


ES5 (zainspirowany odpowiedzią Vlada):

Jeśli masz odwołanie do funkcji, możesz:

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
}
  • Nie przeprowadziłem testów jednostkowych na tym, ani nie zweryfikowałem różnic implementacyjnych, ale w zasadzie powinno działać, jeśli nie zostawić komentarza.
  • Uwaga: nie działa na powiązanych funkcjach
  • Uwaga: to calleri calleesą uważane za przestarzałe.

[1] Zamieszczam to tutaj, ponieważ jest to legalne i często wystarczające narzędzia do podświetlania składni nie uwzględniają białych znaków między nazwą funkcji a nawiasami. Z drugiej strony nie znam żadnej implementacji .toString (), która zawierałaby tutaj białe znaki, dlatego możesz to pominąć.


W odpowiedzi na pierwotne pytanie porzuciłbym pasożytnicze dziedzictwo i wybrałem bardziej tradycyjne wzorce projektowania OOP. Napisałem TidBits.OoJs, aby wygodnie pisać kod OOP w JavaScript z zestawem funkcji naśladujących C ++ (jeszcze nie ukończony, ale głównie).

Z komentarzy wynika, że ​​chciałbyś uniknąć przekazywania informacji parentkonstruktorowi. Muszę przyznać, że tradycyjne wzorce projektowe nie uchronią cię przed tym, ponieważ generalnie dobrze jest uwidocznić i narzucić swoje zależności.

Sugerowałbym również odejście od anonimowych funkcji. Sprawiają, że debugowanie i profilowanie jest PITA, ponieważ wszystko pojawia się jako „anonimowa funkcja” i nie mam dla nich żadnych korzyści, o których jestem świadomy.


źródło
3
Nice RegEx! Oto test wydajności pokazujący, że Twój kod jest najszybszy w wielu przypadkach. Ogólnie wydaje się, że RegEx bije natywną JS średnio o około 2x prędkość. jsperf.com/get-function-name/2
Beejor
@Tomasz Cześć, dziękuję za zwrócenie na to uwagi. Nie wiedziałem tego Funkcja powiązana jest w rzeczywistości nową funkcją, która otacza oryginalną funkcję. Standard ecmascript § 19.2.3.2 określa, że ​​nazwa nowej funkcji będzie „związana” + nazwa oryginalna. Metoda toString nie działa również na powiązanych funkcjach ... Będziesz musiał usunąć słowo powiązane.
Jaki jest sens w myFunction.name, jeśli wpiszesz nazwę funkcji? pokonuje cel magicznych zmiennych.
Borjovsky
Pamiętaj, że jeśli używasz React Native, może to nie działać poprawnie. Mam projekt RN, nad którym pracuję z wersją expo 36, a .namewłaściwość metody komponentu była wyświetlana jako ciąg „wartość”, bez względu na to, jak się nazywała. Co dziwne, podczas testowania w aplikacji expo wszystko było w porządku, jednak po skompilowaniu w prawdziwą aplikację ulegałoby awarii po cichu.
Andy Corman
64

to, co robisz, to przypisywanie zmiennej bez nazwy do zmiennej. prawdopodobnie potrzebujesz zamiast tego nazwanego wyrażenia funkcyjnego ( http://kangax.github.com/nfe/ ).

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

jednak nie jestem pewien, ile to kosztuje przeglądarka; występuje problem z IE6, który powoduje wyciek nazwy funkcji do zakresu zewnętrznego. również arguments.callee jest trochę przestarzały i spowoduje błąd, jeśli go używasz strict mode.

dzika karta
źródło
1
To nie działa w starszych wersjach przeglądarki Safari.
Anubhav Gupta,
17

Każda constructorujawnia właściwość name, która jest nazwą funkcji. Uzyskujesz dostęp za constructorpośrednictwem instancji (za pomocą new) lub 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
źródło
6
Pierwsze console.logdzienniki połączeń windowlub Objectdla mnie, w zależności od tego, gdzie je uruchamiam, nie Person.
Bart S
Czy na pewno utworzyłeś instancję za pomocą „nowego”? W przeciwnym razie nie będzie działać.
roland
14

To wygląda jak najgłupsza rzecz, którą napisałem w życiu, ale to zabawne: 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- głębokość stosu. 0- zwróć nazwę tej funkcji, 1- rodzic, itp .;
[0 + d]- tylko dla zrozumienia - co się dzieje;
firefoxMatch- działa na safari, ale naprawdę miałem trochę czasu na testy, ponieważ właściciel Maca wrócił po paleniu i odwiózł mnie: ”(

Testowanie:

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

gluttony();

Wynik:
Chrome:
wprowadź opis zdjęcia tutaj

Fitefox:
wprowadź opis zdjęcia tutaj

To rozwiązanie było tworzone tylko dla zabawy ! Nie używaj go do prawdziwych projektów. Nie zależy to od specyfikacji ES, zależy tylko od realizacji przeglądarki. Po następnej aktualizacji chrome / firefox / safari może zostać uszkodzony.
Co więcej, nie ma przetwarzania błędów (ha) - jeśli dbędzie większa niż długość stosu - pojawi się błąd;
W przypadku innych wrowsers wzorzec komunikatów o błędach - otrzymasz błąd;
Musi działać dla klas ES6 ( .split('.').pop()), ale możesz dostać błąd;

użytkownik3335966
źródło
13

To może Ci pomóc:

function foo() { bar(); }

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

uruchomienie foo () spowoduje wyświetlenie „foo” lub niezdefiniowane, jeśli wywołasz z funkcji anonimowej.

Działa również z konstruktorami, w którym to przypadku wyświetli nazwę wywołującego konstruktora (np. „Foo”).

Więcej informacji tutaj: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Caller

Twierdzą, że jest niestandardowy, ale obsługuje również wszystkie główne przeglądarki: Firefox, Safari, Chrome, Opera i IE.

Jacob Mouka
źródło
Niedostępne w trybie ścisłym ... mmm
Ka Mok
8

Nie możesz Funkcje nie mają nazw zgodnie ze standardem (chociaż mozilla ma taki atrybut) - można je przypisać tylko do zmiennych o nazwach.

Również twój komentarz:

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

jest w funkcji my.namespace.myFunc.getFn

Co możesz zrobić, to zwrócić konstruktor obiektu utworzonego przez new

Więc możesz powiedzieć

var obj = new my.namespace.myFunc();
console.info(obj.constructor); //my.namespace.myFunc
kujon
źródło
1
potrzebuję go w func, ponieważ przekazuję go w górę łańcucha dziedziczenia i pominąłem to dla zwięzłości.
Scott Klarenbach,
1
„this” będzie instancją, która wywołała funkcję. Więc jeśli „to” wewnątrz funkcji nadrzędnej, da ci instancję funkcji, która ją wywołała. To wszystko, czego potrzebujesz - nie jestem pewien, dlaczego potrzebujesz także tej nazwy
plodder
1
cóż, w rodzicu mogę następnie uzyskać dostęp do innych funkcji zgodnie z konwencją, takich jak ns [child] [schemat] lub ns [child] [dbService]. Bez tego muszę kodować te odwołania w każdej klasie podrzędnej.
Scott Klarenbach,
1
moim przypadkiem użycia do znalezienia nazwy jest ułatwienie konstruowania console.error wiadomości, które mówią, skąd pochodzi wiadomość, bez konieczności powrotu do zrzutu stosu. np. console.error ({„error”: {self.name reference} + „error with input parameter”). O wiele łatwiej jest wycinać / wklejać komunikaty o błędach, które powtarzają się w wielu funkcjach, jeśli nie trzeba za każdym razem zatrzymywać się i zmieniać tekstu. W moim przypadku zwracam błędy obietnicy z wielu wywołań obietnic - miło wiedzieć, która obietnica / funkcja wygenerowała błąd.
Scott
7

Możesz tego użyć w przeglądarkach obsługujących Error.stack (prawdopodobnie nie wszystkie)

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

Oczywiście dotyczy to bieżącej funkcji, ale masz pomysł.

szczęśliwy ślinienie podczas pisania

no_ripcord
źródło
4

Możesz użyć namewłaściwości, aby uzyskać nazwę funkcji, chyba że używasz funkcji anonimowej

Na przykład:

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

teraz spróbujmy z nazwaną funkcją

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

teraz możesz użyć

// will return "someMethod"
p.getSomeMethodName()
Sendy Halim
źródło
4

Możesz użyć nazwy konstruktora, takiej jak:

{your_function}.prototype.constructor.name

ten kod po prostu zwraca nazwę metody.

muhamad zolfaghari
źródło
3

jako część, jak ECMAScript 6można użyć metody Function.name

function doSomething() {}

alert(doSomething.name); // alerts "doSomething"
pleerock
źródło
W ReactJS musisz dodać to: console.log (this.doSomething.name);
Bitclaw,
2
to jest poza funkcją
Scott
3

Możesz użyćFunction.name :

W większości implementacji JavaScript, gdy masz zasięg konstruktora w zakresie, możesz uzyskać jego nazwę ciągu z jego właściwości name (np. Function.name lub Object.constructor.name

Możesz użyćFunction.callee :

Metoda natywna arguments.callerjest przestarzała, ale większość przeglądarek obsługuje tę funkcję Function.caller, która zwróci rzeczywisty obiekt wywołujący (jego treść): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ Funkcja / osoba dzwoniąca? Redirectlocale = en-US & redirectslug = JavaScript% 2FReference% 2FGlobal_Objects% 2FFunction% 2Fcaller

Możesz utworzyć mapę źródłową :

Jeśli potrzebna jest dosłowna sygnatura funkcji (jej „nazwa”), a nie sam obiekt, być może trzeba będzie zastosować coś nieco bardziej dostosowanego, na przykład utworzyć odwołanie do tablicy wartości parametrów API, które trzeba będzie dostęp często. Możesz zmapować je razem za pomocą Object.keys()i swojej tablicy ciągów lub zajrzeć do biblioteki map źródłowych Mozilli na GitHub, w przypadku większych projektów: https://github.com/mozilla/source-map

Benny
źródło
2

Wiem, że to stare pytanie, ale ostatnio miałem do czynienia z podobnym problemem podczas próby udekorowania niektórych metod React Component do celów debugowania. Jak ludzie już mówili arguments.calleri arguments.calleesą zabronione w trybie ścisłym, który prawdopodobnie jest domyślnie włączony w transpilacji React. Możesz to wyłączyć albo mogłem wymyślić inny hack, ponieważ w React wszystkie funkcje klas są nazwane, możesz to zrobić:

Component.prototype.componentWillMount = function componentWillMount() {
    console.log('Callee name: ', this.__proto__.constructor.toString().substr(0,30));
...
}
franki
źródło
1

To zadziałało dla mnie.

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;
    }
}

Kod testowy:

var obj = new AbstractDomainClass();
expect(obj.className()).toBe('AbstractDomainClass');
użytkownik1379687
źródło
1

Miałem podobny problem i rozwiązałem go w następujący sposób:

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

Ten kod implementuje, w bardziej komfortowy sposób, jedną odpowiedź, którą już przeczytałem tutaj na początku tej dyskusji. Teraz mam funkcję członka pobierającą nazwę dowolnego obiektu funkcji. Oto pełny skrypt ...

<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
źródło
1

Jeśli zrozumiałem, co chcesz zrobić, to robię to w konstruktorze funkcji.

if (!(this instanceof arguments.callee)) {
    throw "ReferenceError: " + arguments.callee.name + " is not defined";
}
Zibri
źródło
2
Uwaga: Przestarzałe - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Helcio R. Macedo Pandelo
0

Działa to w ES5, ES6, wszystkich przeglądarkach i funkcjach trybu ścisłego.

Oto jak to wygląda z nazwaną funkcją.

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

Oto jak to wygląda z anonimową funkcją.

(() => {
  console.log(new Error().stack.split(/\r\n|\r|\n/g)[1].trim());
})();
at <anonymous>:2:15
Eric
źródło
Daje to różne wyniki dla przeglądarki. Chrome: at myName (<anonymous>:2:15)Firefox:@debugger eval code:3:3
jkdev
(function myName () {console.log (new Error (). stack.split (/ \ r \ n | \ r | \ n / g) [1] .trim (). split ("") [1]) ;}) ();
Zibri
-2

spójrz tutaj: http://www.tek-tips.com/viewthread.cfm?qid=1209619

arguments.callee.toString();

wydaje się być odpowiedni do twoich potrzeb.

Manuel Bitto
źródło
3
arguments.callee.toString () po prostu zwraca źródło funkcji.
Scott Klarenbach,
2
Niedozwolone: ​​właściwości „caller”, „callee” i „arguments” mogą nie być dostępne dla funkcji trybu ścisłego lub obiektów argumentów dla wywołań do nich
Eric Hodonsky
-2

możesz użyć Error.stack do śledzenia nazwy funkcji i dokładnej pozycji, w której się znajdujesz.

Zobacz plik stacktrace.js

Kofifus
źródło
-3

Łatwy sposób na uzyskanie nazwy funkcji z uruchomionej funkcji.

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

Dave Brown
źródło
1
daje mi GUID
Tamir Daniely
1
być świadomym jego zawartości w thiszasadzie może być wszystko. spróbuj(function(){console.log(this.name);}).bind({name: "put basically anything here even an object maybe a 1TB string maybe a class definition"})()
Valen,