Чому властивість 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.