Как получить имя функции из этой функции?

263

Как я могу получить доступ к имени функции внутри этой функции?

// 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();
Скотт Кларенбах
источник
ну, в родителе я могу получить доступ к другим функциям по соглашению, таким как ns [child] [schema] или ns [child] [dbService]. Без этого мне придется жестко кодировать эти ссылки в каждом дочернем классе.
Скотт Кларенбах
почему бы просто не передать дочернюю функцию в качестве аргумента родителю? var parent = new ns.parent (this);
plodder
потому что есть десятки таких поисков и десятки детей. Это то, чем я сейчас занимаюсь, но каждый раз это одно и то же, и было бы идеально, если бы эту дублирующую логику можно было просто один раз поместить в родительский элемент на основе производной функции.
Скотт Кларенбах
видите, это не дочерняя функция, которую я хочу, это соглашение об именах, потому что это соглашение об именах может использоваться для загрузки других функций, которые в настоящее время не определены для дочернего объекта, но связаны с этим дочерним элементом во всей системе.
Скотт Кларенбах
1
@ Скотт Я согласен со всеми остальными, что вы неправильно управляете своей сложностью и структурой кода, чтобы это делать. Такая жесткая связь - плохое дизайнерское решение и может привести к путанице. @SimeVidas ваш великий некромант :)
Raynos

Ответы:

163

В ES5 лучше всего сделать следующее:

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

Использование не Function.callerявляется стандартным. Function.callerи arguments.calleeоба запрещены в строгом режиме.

Редактировать: ответ nus на основе регулярных выражений ниже достигает того же, но имеет лучшую производительность!

В ES6 вы можете просто использовать myFunction.name.

Примечание. Помните, что некоторые минификаторы JS могут выбрасывать имена функций для лучшего сжатия; вам может понадобиться настроить их настройки, чтобы избежать этого.

Влад А Ионеску
источник
Хотя это не дало мне достаточного ответа на вопрос (из-за анонимных функций), оно дало мне идею сделать это: fun.toString().substr(0, 100)чего для моих нужд будет достаточно, чтобы найти нужную функцию. Итак, спасибо за вдохновение!
Кэмпбелн
3
Да, myFunction.nameэто лучшее, что можно сделать в ES6.
Влад А Ионеску
15
Какой смысл, myFunction.nameесли вы вводите имя функции? побеждает цель магических переменных.
adi518
2
@ adi518: не обязательно .. предположим, что у вас есть какой-либо массив функций, и вы перебираете его, используя for / foreach ..then arr[index].nameтакже будет работать :)
Дебасиш Чоудхури
2
@ adi518 Это не бесполезно. Если я переименую функцию в моей IDE, myFunction.nameтакже будет обновлено. Конечно, intelliJ поддерживает переименование идентификаторов в строках, но это работает гораздо менее согласованно и устареет, если кто-нибудь забудет поставить галочку в опции «искать в строках». myFunction.nameэто немного лучший выбор, чем жесткое кодирование строки.
byxor
141

ES6 (вдохновленный ответом Сэнди Халима ниже):

myFunction.name

Пояснение по МДН . С 2015 года работает в nodejs и во всех основных браузерах, кроме IE.

Примечание: для связанных функций это даст " bound <originalName>". Вам придется лишить «связанного», если вы хотите получить оригинальное имя.


ES5 (вдохновленный ответом Влада):

Если у вас есть ссылка на функцию, вы можете сделать:

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
}
  • Я не запускал модульные тесты по этому вопросу или проверял различия в реализации, но в принципе это должно работать, если не оставить комментарий.
  • Примечание: не будет работать на связанных функциях
  • Примечание: это callerи calleeсчитается устаревшим.

[1] Я включил его сюда, потому что он является законным и достаточно часто инструменты подсветки синтаксиса не учитывают пробел между именем функции и круглыми скобками. С другой стороны, я не знаю ни о какой реализации .toString (), которая будет включать здесь пробелы, поэтому вы можете ее опустить.


В качестве ответа на первоначальный вопрос я бы отказался от паразитического наследования и остановился на некоторых более традиционных шаблонах проектирования ООП. Я написал TidBits.OoJs для удобного написания кода ООП на JavaScript с набором функций, имитирующим C ++ (еще не завершенным, но в основном).

Из комментариев я вижу, что вы хотели бы избежать передачи информационных parentпотребностей его конструктору. Я должен признать, что традиционные шаблоны проектирования не спасут вас от этого, хотя, как правило, считается хорошей идеей сделать ваши зависимости очевидными и обязательными.

Я также предложил бы держаться подальше от анонимных функций. Они делают только отладку и профилирование PITA, потому что все просто проявляется как «анонимная функция», и я не вижу в них никакой пользы.


источник
3
Хороший RegEx! Вот тест производительности, показывающий, что ваш код является самым быстрым во многих случаях. В целом, кажется, что RegEx опережает родной JS примерно в 2 раза. jsperf.com/get-function-name/2
Beejor
@ Tomasz Привет, спасибо, что указал на это. Я этого не знал. Связанная функция - это фактически новая функция, которая оборачивает вашу исходную функцию. Стандарт ecmascript § 19.2.3.2 определяет, что имя новой функции должно быть «bound» + originalName. Метод toString также не будет работать с привязанными функциями ... Вам придется удалить связанное слово.
Какой смысл в myFunction.name, если вы вводите имя функции? побеждает цель магических переменных.
Боржовский
Обратите внимание, что если вы используете React Native, это может работать неправильно. У меня есть проект RN, над которым я работаю, с expo-версией 36, и .nameсвойство метода компонента показывалось как строка «значение», независимо от того, как оно было вызвано. Как ни странно, во время тестирования в приложении expo все было в порядке, однако, после компиляции в реальное приложение, оно молча зависало.
Энди Корман,
64

то, что вы делаете, это присваиваете неназванную функцию переменной. вам, вероятно, вместо этого нужно использовать выражение именованной функции ( http://kangax.github.com/nfe/ ).

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

однако я не уверен, сколько это кросс-браузер; В IE6 есть проблема, из-за которой имя функции просачивается во внешнюю область. Кроме того, arguments.callee устарел и может привести к ошибке при использовании strict mode.

подстановочные
источник
1
Это не работает на старых версиях браузера Safari.
Анубхав Гупта
17

Любой constructorвыставляет свойство name, которое является именем функции. Вы constructorполучаете доступ через экземпляр (используя new) или 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
источник
6
Первые console.logжурналы вызовов windowили Objectдля меня в зависимости от того, где я его запускаю, нет Person.
Барт S
Вы уверены, что создали экземпляр "new"? В противном случае это не может работать.
Роланде
14

Это выглядит как самая глупая вещь, которую я написал в своей жизни, но это смешно: 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глубина стека. 0- вернуть имя этой функции, 1- родитель и т. д .;
[0 + d]- просто для понимания - что происходит;
firefoxMatch- работает для сафари, но у меня было очень мало времени на тестирование, потому что владелец Mac вернулся после курения и отогнал меня: '(

Тестирование:

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

gluttony();

Результат:
Chrome:
введите описание изображения здесь

Fitefox:
введите описание изображения здесь

Это решение создавалось только для удовольствия ! Не используйте его для реальных проектов. Это не зависит от спецификации ES, это зависит только от реализации браузера. После следующего обновления chrome / firefox / safari оно может быть повреждено.
Более того, при обработке ошибок (ха) dбольше длины стека не будет - вы получите ошибку;
Для других шаблонов сообщений об ошибках wrowsers - вы получите ошибку;
Он должен работать для классов ES6 ( .split('.').pop()), но вы можете получить erorr;

user3335966
источник
13

Это может работать для вас:

function foo() { bar(); }

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

Запуск foo () выдаст «foo» или undefined, если вы вызываете из анонимной функции.

Он также работает с конструкторами, в этом случае он будет выводить имя вызывающего конструктора (например, «Foo»).

Более подробная информация здесь: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Caller

Они утверждают, что он нестандартный, но также поддерживается всеми основными браузерами: Firefox, Safari, Chrome, Opera и IE.

Джейкоб Мука
источник
Недоступно в строгом режиме ... ммм
Ка Мок
8

Ты не можешь Функции не имеют имен в соответствии со стандартом (хотя у mozilla есть такой атрибут) - их можно назначать только переменным с именами.

Также ваш комментарий:

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

находится внутри функции my.namespace.myFunc.getFn

Что вы можете сделать, это вернуть конструктор объекта, созданного новым

Так что вы могли бы сказать,

var obj = new my.namespace.myFunc();
console.info(obj.constructor); //my.namespace.myFunc
флегматичный
источник
1
мне это нужно внутри функции, потому что я передаю его по цепочке наследования, и я для краткости опустил это.
Скотт Кларенбах
1
«this» будет экземпляром, который вызвал функцию. Так что, если вы «это» внутри родительской функции, это даст вам экземпляр функции, которая ее вызвала. Это все, что вам нужно - не знаю, зачем вам нужно это имя
plodder
1
ну, в родителе я могу получить доступ к другим функциям по соглашению, таким как ns [child] [schema] или ns [child] [dbService]. Без этого мне придется жестко кодировать эти ссылки в каждом дочернем классе.
Скотт Кларенбах
1
Мой вариант использования для нахождения имени состоит в том, чтобы упростить создание сообщений console.error, которые сообщают, откуда пришло сообщение, без необходимости возвращаться в дамп стека. например console.error ({'error': {ссылка на self.name} + "ошибка с входным параметром"). Намного легче вырезать / вставлять сообщения об ошибках, которые повторяются в нескольких функциях, если вам не нужно каждый раз останавливать и изменять текст внутри них. В моем случае я возвращаю ошибки обещания от нескольких вызовов обещания - приятно знать, какое обещание / функция породила ошибку.
Скотт
7

Вы можете использовать это для браузеров, которые поддерживают Error.stack (возможно, не почти все)

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

конечно, это для текущей функции, но вы поняли идею.

счастливый пускать слюни, пока вы код

no_ripcord
источник
4

Вы можете использовать nameсвойство, чтобы получить имя функции, если вы не используете анонимную функцию

Например:

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

Теперь давайте попробуем с именованной функцией

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

теперь вы можете использовать

// will return "someMethod"
p.getSomeMethodName()
Сэнди Халим
источник
4

Вы можете использовать имя конструктора как:

{your_function}.prototype.constructor.name

этот код просто возвращает имя метода.

Мухамад Золфагари
источник
3

как часть, как ECMAScript 6вы можете использовать метод Function.name

function doSomething() {}

alert(doSomething.name); // alerts "doSomething"
pleerock
источник
В ReactJS вам нужно добавить это: console.log (this.doSomething.name);
Биткло
2
это вне функции
Скотт
3

Вы можете использоватьFunction.name :

В большинстве реализаций JavaScript, когда у вас есть ссылка на конструктор в области видимости, вы можете получить его строковое имя из свойства name (например, Function.name или Object.constructor.name

Вы можете использоватьFunction.callee :

Нативный arguments.callerметод устарел, но большинство браузеров поддерживают его Function.caller, что будет возвращать фактический вызывающий объект (его тело кода): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ Функция / абонент? redirectlocale = EN-US и redirectslug = JavaScript% 2FReference% 2FGlobal_Objects% 2FFunction% 2Fcaller

Вы можете создать исходную карту :

Если вам нужна буквальная сигнатура функции («имя»), а не сам объект, вам, возможно, придется прибегнуть к чему-то более индивидуальному, например, создать ссылку на массив значений строки API, которые вам понадобятся доступ часто. Вы можете отобразить их вместе, используя Object.keys()и ваш массив строк, или заглянуть в библиотеку исходных карт Mozilla на GitHub, для более крупных проектов: https://github.com/mozilla/source-map.

Бенни
источник
2

Я знаю, что это старый вопрос, но в последнее время я столкнулся с некоторой подобной проблемой, пытаясь украсить некоторые методы компонента React для целей отладки. Как уже говорили люди, arguments.callerи arguments.calleeони запрещены в строгом режиме, который, вероятно, включен по умолчанию в вашем транспаранте React. Вы можете либо отключить его, либо я смог придумать еще один хак, потому что в React все функции класса названы, вы можете сделать это:

Component.prototype.componentWillMount = function componentWillMount() {
    console.log('Callee name: ', this.__proto__.constructor.toString().substr(0,30));
...
}
Frankies
источник
1

Это сработало для меня.

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

Тестовый код:

var obj = new AbstractDomainClass();
expect(obj.className()).toBe('AbstractDomainClass');
user1379687
источник
1

У меня была похожая проблема, и я решил ее следующим образом:

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

Этот код более удобным образом реализует один ответ, который я уже прочитал здесь, в начале этого обсуждения. Теперь у меня есть функция-член, получающая имя любого объекта функции. Вот полный сценарий ...

<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>
Сандро Роза
источник
1

Если я понял, что вы хотите сделать, это то, что я делаю внутри конструктора функции.

if (!(this instanceof arguments.callee)) {
    throw "ReferenceError: " + arguments.callee.name + " is not defined";
}
Zibri
источник
2
Примечание: устарело - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Helcio R. Macedo Pandelo
0

Это будет работать в ES5, ES6, во всех браузерах и в строгом режиме.

Вот как это выглядит с именованной функцией.

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

Вот как это выглядит с анонимной функцией.

(() => {
  console.log(new Error().stack.split(/\r\n|\r|\n/g)[1].trim());
})();
at <anonymous>:2:15
Эрик
источник
Это дает разные результаты для каждого браузера. 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

смотрите здесь: http://www.tek-tips.com/viewthread.cfm?qid=1209619

arguments.callee.toString();

кажется, подходит для ваших нужд.

Мануэль Битто
источник
3
arguments.callee.toString () просто возвращает источник функции.
Скотт Кларенбах
2
Не допускается: свойства 'caller', 'callee' и 'arguments' не могут быть доступны для функций строгого режима или объектов аргументов для их вызова
Эрик Ходонски
-2

Вы можете использовать Error.stack для отслеживания имени функции и точного положения, где вы находитесь в ней.

Смотрите stacktrace.js

kofifus
источник
-3

Простой способ получить имя функции из функционирующей вами функции.

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

Дэйв Браун
источник
1
дает мне
путеводитель
1
знать о содержании thisэтого в принципе может быть все что угодно. попробуй(function(){console.log(this.name);}).bind({name: "put basically anything here even an object maybe a 1TB string maybe a class definition"})()
Вален