Як дізнатися функцію абонента в JavaScript?


865
function main()
{
   Hello();
}

function Hello()
{
  // How do you find out the caller function is 'main'?
}

Чи є спосіб дізнатися стек викликів?


63
Я сподіваюся, що це просто допоможе вам у налагодженні. Варіантна поведінка на основі абонента - погана ідея.
ОВ.

Коли це стане в нагоді для налагодження?
Андерсон Грін

33
@AndersonGreen, наприклад, якщо у вас є метод візуалізації шаблонів за замовчуванням і бачите, що його викликають двічі. Замість того, щоб прочесати через 1000s LoC або важко переступати з налагоджувачем, ви можете просто побачити, який був стек у той час.
tkone

28
щоб побачити слід стека, використовуйте console.trace () для chrome. не знаю про інших, хоча
lukas.pukenis

5
Чому це погана ідея?
Яків Шнайдер

Відповіді:


994
function Hello()
{
    alert("caller is " + Hello.caller);
}

Зверніть увагу , що ця функція є нестандартною , з Function.caller:

Нестандартна
Ця функція є нестандартною та не відповідає стандартним стандартам. Не використовуйте його на виробничих сайтах, що стоять перед Інтернетом: це працюватиме не для кожного користувача. Також можуть бути великі несумісності між реалізаціями, і поведінка може змінитися в майбутньому.


Далі наведена стара відповідь 2008 року, яка більше не підтримується в сучасному Javascript:

function Hello()
{
    alert("caller is " + arguments.callee.caller.toString());
}

254
arguments.callee.caller.nameотримає ім’я функції.
Ракета Хазмат

137
Властивості "caller", "callee" та "argument" можуть не бути доступними для функцій суворого режиму або об'єктів аргументів для викликів до них "- вони застаріли в ES5 та видаляються у суворому режимі.
ThatGuy

12
Він буде працювати лише в тому випадку, якщо ви не використовуєте суворий режим. Тож видалення 'use strict';може допомогти.
pvorb

23
argumentsМОЖЛИ отримати доступ до якоїсь функції в суворому режимі, було б дурним це зневажити. тільки не з функції.аргументи ззовні. Крім того, якщо у вас є іменний аргумент, форма його аргументів [i] не буде відслідковувати зміни, які ви вносите в іменовану версію всередині функції.
rvr_jon

41
Цей метод застарів після того, як ця публікація була внесена до списку в 2011 році. Кращим методом тепер є Function.caller (станом на 2015 рік).
Грег

152

StackTrace

Ви можете знайти весь слід стека за допомогою конкретного коду браузера. Добре, що хтось це вже зробив ; ось код проекту на GitHub .

Але не всі новини хороші:

  1. Дістати слід стека дуже повільно, тому будьте обережні (читайте це докладніше).

  2. Вам потрібно буде визначити назви функцій, щоб стежка стека була розбірливою. Тому що якщо у вас є такий код:

    var Klass = function kls() {
       this.Hello = function() { alert(printStackTrace().join('\n\n')); };
    }
    new Klass().Hello();

    Google Chrome попередить, ... kls.Hello ( ...але більшість веб-переглядачів очікують назви функції одразу після ключового слова functionта вважатимуть це анонімною функцією. Навіть Chrome не зможе використовувати це Klassім'я, якщо ви не дасте ім'я klsфункції.

    І до речі, ви можете перейти до функції printStackTrace опцію, {guess: true}але я не знайшов реального покращення, зробивши це.

  3. Не всі браузери дають вам однакову інформацію. Тобто параметри, стовпчик коду тощо.


Назва функції виклику

До речі, якщо ви хочете лише ім'я функції абонента (у більшості браузерів, але не в IE), ви можете використовувати:

arguments.callee.caller.name

Але зауважте, що це ім’я буде одним із functionключових слів. Я не знайшов способу (навіть на Google Chrome) отримати більше, ніж отримати код усієї функції.


Код функції виклику

І підсумовуючи решту найкращих відповідей (Пабло Кабрера, Нурдін та Грег Х'югілл). Єдиний крос-браузер і дійсно безпечна річ, яку ви можете використовувати:

arguments.callee.caller.toString();

Який покаже код функції виклику. На жаль, цього мені недостатньо, і саме тому я даю вам поради щодо StackTrace та функції виклику (ім'я для виклику) (хоча вони не є крос-браузером).


1
можливо, вам слід додати Function.callerвідповідь від @ Грега
Зах Лисобей

Function.callerОднак робота в суворому режимі не буде.
Рікард Елімяа

54

Я знаю, що ви згадали "в Javascript", але якщо мета налагодження, я думаю, що простіше просто скористатися інструментами для розробників вашого браузера. Ось як це виглядає в Chrome: введіть тут опис зображення Просто опустіть налагоджувач там, де ви хочете дослідити стек.


3
Це давнє питання ... але це, безумовно, сучасний найважливіший спосіб зробити це сьогодні.
markstewie

53

Щоб скласти резюме (і зробити його зрозумілішим) ...

цей код:

function Hello() {
    alert("caller is " + arguments.callee.caller.toString());
}

еквівалентно цьому:

function Hello() {
    alert("caller is " + Hello.caller.toString());
}

Ясно, що перший біт є більш портативним, оскільки ви можете змінити назву функції, скажімо, з "Привіт" на "Ciao", і все-таки змусити цю роботу працювати.

В останньому, у випадку, якщо ви вирішите змінити назву викликаної функції (Привіт), вам доведеться змінити всі її виникнення :(


7
argument.callee.caller завжди недійсний у Chrome 25.0.1364.5 dev
Kokizzu

53

Ви можете отримати повний стек-трек:

arguments.callee.caller
arguments.callee.caller.caller
arguments.callee.caller.caller.caller

Поки не телефонує null.

Примітка: вона викликає нескінченний цикл на рекурсивних функціях.


2
Вибачте за пізню відповідь, але я раніше не бачив вашого коментаря; лише у випадку рекурсії він не працює, в інших випадках він повинен працювати.
ale5000

45

Я зазвичай використовую (new Error()).stackв Chrome. Приємно, що це також дає вам номери рядків, де абонент викликав функцію. Мінус у тому, що він обмежує довжину стека до 10, саме тому я зайшов на цю сторінку в першу чергу.

(Я використовую це для збору викликів викликів у конструкторі низького рівня під час виконання, для перегляду та налагодження пізніше, тому встановлення точки розриву не корисне, оскільки воно буде вдарене тисячі разів)


Не могли б ви додати трохи більше опису щодо наданого пояснення?
абарізон

6
Це єдине, що я міг би взяти на роботу, коли 'use strict';на місці. Дайте мені потрібну інформацію - дякую!
Джеремі Гарріс

4
Щодо межі довжини стека ... ви можете змінити це за допомогою "Error.stackTraceLimit = Нескінченність".
Том

(нова помилка ("StackLog")). stack.split ("\ n") робить приємніше читати.
Теоман шипахі

36

Якщо ви не збираєтеся запускати його в IE <11, тоді console.trace () підійде.

function main() {
    Hello();
}

function Hello() {
    console.trace()
}

main()
// Hello @ VM261:9
// main @ VM261:4

Це працює! Слід додати більше голосів
Krunal Panchal

22

Ви можете використовувати Function.Caller, щоб отримати функцію дзвінка. Старий метод використання argument.caller вважається застарілим.

Наступний код ілюструє його використання:

function Hello() { return Hello.caller;}

Hello2 = function NamedFunc() { return NamedFunc.caller; };

function main()
{
   Hello();  //both return main()
   Hello2();
}

Примітки про застарілий argument.caller: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/caller

Будьте в курсі, що Function.caller є нестандартним: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller


1
Це правильна відповідь у ці дні. Ви більше не можете робити аргументи argument.caller.callee. Хочеться, щоб ми могли перенести це на початок, оскільки всі інші речі застаріли.
coblr

4
Здається, це неможливо в суворому режимі? Cannot access caller property of a strict mode function
Зах Лісобей

Function.caller також не працював для мене в суворому режимі. Крім того, згідно MDN , function.caller є нестандартним і не повинен використовуватися у виробництві. Однак це може працювати для налагодження.
jkdev

У мене не було проблем з нестандартним, якщо він працював у Node, але його просто не дозволено в суворому режимі (я протестував на вузлі 6.10). Те саме стосується "аргументів". Я отримую повідомлення про помилку: '' 'абонент' та 'аргументи' є обмеженими властивостями функцій і не можна отримати доступ у цьому контексті. "
Том

21

Я би зробив це:

function Hello() {
  console.trace();
}

Це чудово працює! слід прийняти як правильну відповідь, оскільки інші способи старі \ більше не працюють
Yuval

19
function Hello() {
    alert(Hello.caller);
}

1
І тільки для назви функції використовуйте Hello.caller.name
vanval

те саме, щоarguments.callee.caller.toString()
user2720864

Це має бути правильна відповідь, принаймні для 2016 року
Даніель,

Це не на стандартному
етапі

1

18

Це безпечніше використовувати , *arguments.callee.callerтак arguments.callerяк НЕ рекомендується ...


36
arguments.calleeтакож застаріла в ES5 і видаляється в суворому режимі.
nyuszika7h

2
Чи є альтернатива? Редагувати: arguments.calleeбуло поганим рішенням проблеми, яка тепер була краще вирішена developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Меттью

16

Схоже, це досить вирішене запитання, але нещодавно я з’ясував, що callee заборонено в «суворому режимі», тому для власного використання я написав клас, який отримає шлях звідки його викликають. Це частина невеликої вкладки-помічника, і якщо ви хочете використовувати код окремо, змініть зміщення, яке використовується для повернення сліду стека абонента (використовуйте 1 замість 2)

function ScriptPath() {
  var scriptPath = '';
  try {
    //Throw an error to generate a stack trace
    throw new Error();
  }
  catch(e) {
    //Split the stack trace into each line
    var stackLines = e.stack.split('\n');
    var callerIndex = 0;
    //Now walk though each line until we find a path reference
    for(var i in stackLines){
      if(!stackLines[i].match(/http[s]?:\/\//)) continue;
      //We skipped all the lines with out an http so we now have a script reference
      //This one is the class constructor, the next is the getScriptPath() call
      //The one after that is the user code requesting the path info (so offset by 2)
      callerIndex = Number(i) + 2;
      break;
    }
    //Now parse the string for each section we want to return
    pathParts = stackLines[callerIndex].match(/((http[s]?:\/\/.+\/)([^\/]+\.js)):/);
  }

  this.fullPath = function() {
    return pathParts[1];
  };

  this.path = function() {
    return pathParts[2];
  };

  this.file = function() {
    return pathParts[3];
  };

  this.fileNoExt = function() {
    var parts = this.file().split('.');
    parts.length = parts.length != 1 ? parts.length - 1 : 1;
    return parts.join('.');
  };
}

Не працює для мене function a(){ function b(){ function c(){ return ScriptPath(); } return c(); } return b(); } a()в консолі (не пробував у файлі), але, здається, має розумне уявлення. У будь-якому випадку слід звертати увагу на видимість.
ninjagecko

Ідея чудова. Я розбираю по-різному, але в додатках nw.js це справді єдина ідея, яка дає те, що я шукаю.
Ендрю Гроте

Наведіть приклад, як викликати цю функцію.
pd_au


11

Просто консоліруйте свій стек помилок. Потім ви можете знати, як вас дзвонять

const hello = () => {
  console.log(new Error('I was called').stack)
}

const sello = () => {
  hello()
}

sello()


10

Оновлення 2018 року

callerзаборонено в суворому режимі . Ось альтернатива з використанням (нестандартного) Errorстека .

Наступна функція, схоже, виконує роботу в Firefox 52 та Chrome 61-71, хоча її реалізація робить багато припущень щодо формату журналу двох браузерів і повинна використовуватися з обережністю, враховуючи, що вона кидає виняток і, можливо, виконує два регулярні виразки відповідність, перш ніж зробити.

'use strict';
const fnNameMatcher = /([^(]+)@|at ([^(]+) \(/;

function fnName(str) {
  const regexResult = fnNameMatcher.exec(str);
  return regexResult[1] || regexResult[2];
}

function log(...messages) {
  const logLines = (new Error().stack).split('\n');
  const callerName = fnName(logLines[1]);

  if (callerName !== null) {
    if (callerName !== 'log') {
      console.log(callerName, 'called log with:', ...messages);
    } else {
      console.log(fnName(logLines[2]), 'called log with:', ...messages);
    }
  } else {
    console.log(...messages);
  }
}

function foo() {
  log('hi', 'there');
}

(function main() {
  foo();
}());


4
Це неймовірно, а також жахливо.
Ян

Я отримав "foo за допомогою: привіт, там", але foo не називався "привіт там", журнал викликався з "привіт там"
AndrewR

Правильно, у граматиці повідомлення про помилку був "неправильно змінений модифікатор". Це означало сказати, що "журнал викликався з функції f, він хотів, щоб повідомлення X було надруковано", але якомога коротшим способом.
Рованіон

9

І в режимі ES6, і в строгому режимі скористайтеся наступним, щоб отримати функцію Caller

console.log((new Error()).stack.split("\n")[2].trim().split(" ")[1])

Зауважте, що вищенаведений рядок буде винятком, якщо немає абонента або попереднього стека. Використовуйте відповідно.


Щоб отримати виклик (назва поточної функції), використовуйте: console.log((new Error()).stack.split("\n")[1].trim().split(" ")[1])
VanagaS

7

Я хотів додати тут свою загадку:

http://jsfiddle.net/bladnman/EhUm3/

Я перевірив це хром, сафарі та IE (10 та 8). Добре працює. Є лише 1 функція, яка має значення, тому якщо вас лякає велика загадка, читайте нижче.

Примітка. У цій скрипці є досить велика кількість моєї власної "котлової панелі". Ви можете вилучити все це і використовувати спліт, якщо хочете. Це просто ультрабезпечний набір функцій, на які я став покладатися.

Там також є шаблон "JSFiddle", який я використовую для багатьох загадок, щоб просто швидко перебирати.


Цікаво, чи можна додати «помічників» як розширення для прототипу в деяких випадках, наприклад:String.prototype.trim = trim;
аутизм

6

Якщо ви просто хочете назву функції, а не код, і хочете рішення, незалежне від браузера, використовуйте наступне:

var callerFunction = arguments.callee.caller.toString().match(/function ([^\(]+)/)[1];

Зауважте, що вищесказане поверне помилку, якщо немає функції виклику, оскільки в масиві немає елемента [1]. Щоб обійти, скористайтеся наведеним нижче способом:

var callerFunction = (arguments.callee.caller.toString().match(/function ([^\(]+)/) === null) ? 'Document Object Model': arguments.callee.caller.toString().match(/function ([^\(]+)/)[1], arguments.callee.toString().match(/function ([^\(]+)/)[1]);

1
Це вже давно застаріло .
Дан Даскалеску

5

Просто хочу , щоб ви знали , що на PhoneGap / Androidname , здається, працює оленяча шкіра. Але arguments.callee.caller.toString()зробимо трюк.


4

Тут все, крім цього functionname, знято caller.toString()з RegExp.

<!DOCTYPE html>
<meta charset="UTF-8">
<title>Show the callers name</title><!-- This validates as html5! -->
<script>
main();
function main() { Hello(); }
function Hello(){
  var name = Hello.caller.toString().replace(/\s\([^#]+$|^[^\s]+\s/g,'');
  name = name.replace(/\s/g,'');
  if ( typeof window[name] !== 'function' )
    alert ("sorry, the type of "+name+" is "+ typeof window[name]);
  else
    alert ("The name of the "+typeof window[name]+" that called is "+name);
}
</script>

це все ще повертає всю заяву про метод
Маслоу,


3

Відповідь heystewart в і відповідь JiarongWu в обох згадується , що Errorоб'єкт має доступ до stack.

Ось приклад:

function main() {
  Hello();
}

function Hello() {
  var stack;
  try {
    throw new Error();
  } catch (e) {
    stack = e.stack;
  }
  // N.B. stack === "Error\n  at Hello ...\n  at main ... \n...."
  var m = stack.match(/.*?Hello.*?\n(.*?)\n/);
  if (m) {
    var caller_name = m[1];
    console.log("Caller is:", caller_name)
  }
}

main();

Різні браузери показують стек у різних строкових форматах:

Safari : Caller is: main@https://stacksnippets.net/js:14:8 Firefox : Caller is: main@https://stacksnippets.net/js:14:3 Chrome : Caller is: at main (https://stacksnippets.net/js:14:3) IE Edge : Caller is: at main (https://stacksnippets.net/js:14:3) IE : Caller is: at main (https://stacksnippets.net/js:14:3)

Більшість браузерів встановить стек за допомогою var stack = (new Error()).stack. В Internet Explorer стек буде невизначений - вам потрібно скинути реальний виняток, щоб отримати стек.

Висновок: Можна визначити "головним" викликає "Привіт", використовуючи stack" Errorоб'єкт". Насправді він працюватиме у випадках, коли callee/ callerпідхід не працює. Він також покаже вам контекст, тобто вихідний файл та номер рядка. Однак необхідні зусилля, щоб зробити рішення крос-платформою.


2

Зауважте, що ви не можете використовувати Function.caller в Node.js, замість цього використовуйте пакет call-id . Наприклад:

var callerId = require('caller-id');

function foo() {
    bar();
}
function bar() {
    var caller = callerId.getData();
    /*
    caller = {
        typeName: 'Object',
        functionName: 'foo',
        filePath: '/path/of/this/file.js',
        lineNumber: 5,
        topLevelFlag: true,
        nativeFlag: false,
        evalFlag: false
    }
    */
}

1

Спробуйте наступний код:

function getStackTrace(){
  var f = arguments.callee;
  var ret = [];
  var item = {};
  var iter = 0;

  while ( f = f.caller ){
      // Initialize
    item = {
      name: f.name || null,
      args: [], // Empty array = no arguments passed
      callback: f
    };

      // Function arguments
    if ( f.arguments ){
      for ( iter = 0; iter<f.arguments.length; iter++ ){
        item.args[iter] = f.arguments[iter];
      }
    } else {
      item.args = null; // null = argument listing not supported
    }

    ret.push( item );
  }
  return ret;
}

Працював для мене у Firefox-21 та Chromium-25.


Спробуйте це для рекурсивних функцій.
daniel1426


1

Ще один спосіб вирішити цю проблему - просто передати ім'я функції виклику як параметр.

Наприклад:

function reformatString(string, callerName) {

    if (callerName === "uid") {
        string = string.toUpperCase();
    }

    return string;
}

Тепер ви можете назвати функцію так:

function uid(){
    var myString = "apples";

    reformatString(myString, function.name);
}

У моєму прикладі використовується чітко кодована перевірка імені функції, але ви можете легко використовувати оператор перемикання або якусь іншу логіку, щоб робити все, що вам потрібно.


Я вважаю, що це також вирішує проблеми сумісності веб-переглядачів здебільшого. Але будь ласка, протестуйте це, перш ніж вважати, що це правда! ( починає потіти )
GrayedFox

1

Наскільки я знаю, у нас є два шляхи для цього з таких джерел, як

  1. argument.caller

    function whoCalled()
    {
        if (arguments.caller == null)
           console.log('I was called from the global scope.');
        else
           console.log(arguments.caller + ' called me!');
    }
  2. Function.caller

    function myFunc()
    {
       if (myFunc.caller == null) {
          return 'The function was called from the top!';
       }
       else
       {
          return 'This function\'s caller was ' + myFunc.caller;
        }
    }

Подумайте, у вас є відповідь :).


Це припиняється протягом багатьох років , а Function.caller не працює в суворому режимі.
Дан Даскалеску

1

Чому всі рішення, наведені вище, виглядають як ракетна наука. Тим часом він не повинен бути складнішим за цей фрагмент. Усі кредити цьому хлопцеві

Як дізнатися функцію абонента в JavaScript?

var stackTrace = function() {

    var calls = [];
    var caller = arguments.callee.caller;

    for (var k = 0; k < 10; k++) {
        if (caller) {
            calls.push(caller);
            caller = caller.caller;
        }
    }

    return calls;
};

// when I call this inside specific method I see list of references to source method, obviously, I can add toString() to each call to see only function's content
// [function(), function(data), function(res), function(l), function(a, c), x(a, b, c, d), function(c, e)]

3
Це те, що я отримую за допомогою цього: TypeError: 'caller', 'callee' та 'argument' властивості не можуть бути доступні у функціях суворого режиму або в об'єктах аргументів для дзвінків до них. Будь-які ідеї, як це працювати в суворому режимі?
hard_working_ant

1

Я намагаюся вирішити як питання, так і поточну щедрість цим питанням.

Баунті вимагає, щоб абонент був отриманий у суворому режимі, і єдиний спосіб я бачу це - звертаючись до функції, оголошеної поза суворим режимом.

Наприклад, наведене нижче є нестандартним, проте перевірено з попередніми (29.03.2016) та поточними (1 серпня 2018) версіями Chrome, Edge та Firefox.

function caller()
{
   return caller.caller.caller;
}

'use strict';
function main()
{
   // Original question:
   Hello();
   // Bounty question:
   (function() { console.log('Anonymous function called by ' + caller().name); })();
}

function Hello()
{
   // How do you find out the caller function is 'main'?
   console.log('Hello called by ' + caller().name);
}

main();



0

Якщо вам дійсно потрібна функціональність з якихось причин і ви хочете, щоб вона була сумісною для веб-браузерів, і не переживаєте за суворі речі та бути сумісними вперед, тоді передайте цю посилання:

function main()
{
   Hello(this);
}

function Hello(caller)
{
    // caller will be the object that called Hello. boom like that... 
    // you can add an undefined check code if the function Hello 
    // will be called without parameters from somewhere else
}

0

Я думаю, що наступний фрагмент коду може бути корисним:

window.fnPureLog = function(sStatement, anyVariable) {
    if (arguments.length < 1) { 
        throw new Error('Arguments sStatement and anyVariable are expected'); 
    }
    if (typeof sStatement !== 'string') { 
        throw new Error('The type of sStatement is not match, please use string');
    }
    var oCallStackTrack = new Error();
    console.log(oCallStackTrack.stack.replace('Error', 'Call Stack:'), '\n' + sStatement + ':', anyVariable);
}

Виконайте код:

window.fnPureLog = function(sStatement, anyVariable) {
    if (arguments.length < 1) { 
        throw new Error('Arguments sStatement and anyVariable are expected'); 
    }
    if (typeof sStatement !== 'string') { 
        throw new Error('The type of sStatement is not match, please use string');
    }
    var oCallStackTrack = new Error();
    console.log(oCallStackTrack.stack.replace('Error', 'Call Stack:'), '\n' + sStatement + ':', anyVariable);
}

function fnBsnCallStack1() {
    fnPureLog('Stock Count', 100)
}

function fnBsnCallStack2() {
    fnBsnCallStack1()
}

fnBsnCallStack2();

Журнал виглядає так:

Call Stack:
    at window.fnPureLog (<anonymous>:8:27)
    at fnBsnCallStack1 (<anonymous>:13:5)
    at fnBsnCallStack2 (<anonymous>:17:5)
    at <anonymous>:20:1 
Stock Count: 100
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.