Чому властивість argument.callee.caller застаріло в JavaScript?


214

Чому arguments.callee.callerвластивість застаріло в JavaScript?

Він був доданий та згодом застарілий у JavaScript, але його взагалі було пропущено ECMAScript. Деякі веб-переглядачі (Mozilla, IE) завжди підтримували його і не мають на карті жодних планів щодо видалення підтримки. Інші (Safari, Opera) прийняли підтримку, але підтримка у старих браузерах ненадійна.

Чи є вагомі причини підставити цю цінну функціональність на кінцівку?

(Або по черзі, чи є кращий спосіб схопити ручку на функцію виклику?)


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

4
(Майже всі браузери робили це в той чи інший час, наприклад, ця функція (і сам JS) походить від Netscape, XHR, що виник у IE, Canvas в Safari тощо. Деякі з них корисні і підхоплені іншими браузерами. з часом (js, canvas, xhr - всі приклади), деякі (.callee) - ні.
olliej

@olliej Ваш коментар щодо його підтримки, тому що він використовується, а не тому, що він є стандартом (або навіть незважаючи на те, що він є застарілим у стандарті), дуже правдивий! Ось чому я почав переважно ігнорувати стандарти, коли відчуваю, що вони мені не допомагають. Ми, як розробники, можемо формувати напрямки стандартів, використовуючи те, що працює, а не те, що специфікація каже, що ми повинні робити. Ось так ми повернулися <b>і <i>назад (так, вони були застаріли в один момент).
Штійн де Віт

Відповіді:


252

Ранні версії 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 не може гарантувати, що всі надані аргументи є числами в момент здійснення виклику, йому потрібно або вставити перевірки всіх аргументів перед вкладеним кодом, або він не може вбудувати функцію.

Тепер у цьому конкретному випадку розумний перекладач повинен мати можливість переставляти перевірки на більш оптимальні та не перевіряти будь-які значення, які не використовувались. Однак у багатьох випадках це просто неможливо, і тому неможливо вставити рядки.


12
Ви хочете сказати, що це оприлюднене лише тому, що важко оптимізувати? Це якось нерозумно.
Томас Едінг

11
Ні, я перерахував низку причин, на додаток до того, що важко оптимізувати (хоча загалом історія показала, що речі, які важко оптимізувати, також мають семантику, до якої люди мають труднощі слідувати)
olliej

17
Цей аргумент трохи підроблений, його значення може бути встановлено викликом , якщо це важливо. Зазвичай він не використовується (принаймні, у мене ніколи не виникало проблем з рекурсивними функціями). Виклик функції по імені має ті ж проблеми з , thisтак що я думаю , що це не має значення щодо того , що викликається є хорошим або поганим. Крім того, виклики та абонент "лише" застаріли у суворому режимі (ECMAscript ed 5, грудень 2009 р.), Але я думаю, що цього не було відомо в 2008 році, коли olliej розмістив повідомлення.
RobG

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

8
RobG зазначив це, але я не думаю, що все було так зрозуміло: повторне використання названої функції збереже значення лише, thisякщо thisце глобальна область. У всіх інших випадках значення this vol зміниться після першого рекурсивного дзвінка, тому я вважаю, що частини вашої відповіді, що натякають на збереження this, насправді не дійсні.
JLRishe

89

arguments.callee.callerце НЕ рекомендується, хоча це використання зробити з майна. ( просто дасть вам посилання на поточну функцію)Function.callerarguments.callee

  • Function.caller, хоч і нестандартна згідно ECMA3, реалізована у всіх поточних основних браузерах .
  • arguments.caller є застарілим на користь , а не реалізована в деяких поточних основних браузерах (наприклад , Firefox 3).Function.caller

Таким чином, ситуація є не надто ідеальною, але якщо ви хочете отримати доступ до функції виклику в Javascript у всіх основних браузерах, ви можете використовувати властивість, або отримувати доступ безпосередньо за вказаною посиланням на функцію, або з анонімної функції через властивість.Function.callerarguments.callee


5
Це найкраще пояснення того, що є, а не застаріло, дуже корисно. Хороший приклад того, що Function.caller не може зробити (отримати слід стеку рекурсивних функцій), див. Developer.mozilla.org/uk/JavaScript/Reference/Global_Objects/…
Juan Mendes

1
Хоча, arguments.calleeзаборонено в суворому режимі. Мене це теж засмутило, але краще більше не користуватися ним.
Подвійний гра

1
Arguments.callee гіперпосилання ви повинні MDN каже , що він віддаляється в строгому режимі. Хіба це не те саме, що застаріло?
стайфл

1
Зауважте, що arguments.callee.callerв строгому режимі ES5 застаріле: "Іншою функцією, яка була застаріла, було аргументи.callee.caller, а точніше Function.caller." ( Джерело )
thoan

29

Краще використовувати іменовані функції, ніж аргументи.callee:

 function foo () {
     ... foo() ...
 }

краще, ніж

 function () {
     ... arguments.callee() ...
 }

Названа функція матиме доступ до свого абонента через властивість абонента :

 function foo () {
     alert(foo.caller);
 }

що краще, ніж

 function foo () {
     alert(arguments.callee.caller);
 }

Депресія обумовлена ​​чинними принципами проектування ECMAScript .


2
Чи можете ви описати, чому краще використовувати названу функцію. Чи ніколи не потрібно використовувати callee в анонімній функції?
AnthonyWJones

27
Якщо ви використовуєте callee в анонімній функції, то у вас є функція, яка не повинна бути анонімною.
Prestaul

3
іноді найпростіший спосіб налагодження - за допомогою .caller (). У таких випадках названі функції не допоможуть - ви намагаєтеся визначити, яку функцію виконує виклик.
SamGoody

6
Визначте краще. Наприклад, IE6-8 назвали функціональні примхи, коли ARG.callee працює.
cmc

1
Крім IE6-8 примх, він також робить код щільно сполученим. Якщо назви об'єктів та / або функцій жорстко кодуються, то, як згадуються ardsasd та rsk82, існують основні небезпеки рефакторингу, які збільшуються лише у міру збільшення розміру бази коду. Тестові одиниці - це захисна лінія, і я їх використовую, але вони все ще не є відповіддю, що насправді мене насичує щодо проблеми жорсткого кодування.
Жасмін Гегман

0

Просто розширення. Значення "цього" змінюється під час рекурсії. У наступному (модифікованому) прикладі факториал отримує об’єкт {foo: true}.

[1,2,3,4,5].map(function factorial(n) {
  console.log(this);
  return (!(n>1))? 1 : factorial(n-1)*n;
},     {foo:true}     );

Факториал, який називається вперше, отримує об'єкт, але це не відповідає дійсності рекурсивних викликів.


1
Тому що ти робиш це неправильно. Якщо thisпотрібно підтримувати, напишіть, що factorial.call(this, n-1)я фактично виявив у написанні рекурсивного коду, що зазвичай його немає this, або thisпосилається на якийсь вузол у дереві, і насправді добре, що він змінюється.
Штійн де Віт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.