Ранні версії JavaScript не дозволяли називати вирази функцій, і тому ми не могли зробити рекурсивне вираження функції:
// This snippet will work:
function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
}
[1,2,3,4,5].map(factorial);
// But this snippet will not:
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : /* what goes here? */ (n-1)*n;
});
Щоб обійти це, arguments.callee
було додано, щоб ми могли зробити:
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : arguments.callee(n-1)*n;
});
Однак це насправді було дуже поганим рішенням, оскільки це (в поєднанні з іншими аргументами, питаннями виклику та виклику) унеможливлює рекруцію вкладок та хвостів у загальному випадку (ви можете досягти цього у вибраних випадках шляхом відстеження тощо, але навіть найкращий код є недостатньо оптимальним через перевірки, які інакше не були б необхідними). Інша основна проблема полягає в тому, що рекурсивний виклик отримає інше this
значення, наприклад:
var global = this;
var sillyFunction = function (recursed) {
if (!recursed)
return arguments.callee(true);
if (this !== global)
alert("This is: " + this);
else
alert("This is the global");
}
sillyFunction();
Так чи інакше, EcmaScript 3 вирішив ці проблеми, дозволивши названі вирази функцій, наприклад:
[1,2,3,4,5].map(function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
});
Це має численні переваги:
Функцію можна викликати, як і будь-яку іншу, всередині вашого коду.
Це не забруднює простір імен.
Значення this
не змінюється.
Він більш ефективний (доступ до об'єкта аргументів дорогий).
Уопс,
Щойно зрозумів, що крім усього іншого, питання стосувалося arguments.callee.caller
, або конкретнішеFunction.caller
.
У будь-який момент часу ви можете знайти найглибшого абонента будь-якої функції на стеці, і, як я вже говорив вище, перегляд стека викликів має один єдиний головний ефект: Це робить велику кількість оптимізацій неможливими або набагато набагато складнішими.
Напр. якщо ми не можемо гарантувати, що функція f
не викличе невідому функцію, то вбудувати її неможливо f
. В основному це означає, що будь-який сайт виклику, який, можливо, був тривільно нездійсненним, накопичує велику кількість охоронців:
function f(a, b, c, d, e) { return a ? b * c : d * e; }
Якщо інтерпретатор js не може гарантувати, що всі надані аргументи є числами в момент здійснення виклику, йому потрібно або вставити перевірки всіх аргументів перед вкладеним кодом, або він не може вбудувати функцію.
Тепер у цьому конкретному випадку розумний перекладач повинен мати можливість переставляти перевірки на більш оптимальні та не перевіряти будь-які значення, які не використовувались. Однак у багатьох випадках це просто неможливо, і тому неможливо вставити рядки.