Por que a propriedade argumentos.callee.caller foi reprovada em JavaScript?

214

Por que a arguments.callee.callerpropriedade foi descontinuada em JavaScript?

Foi adicionado e descontinuado no JavaScript, mas foi totalmente omitido pelo ECMAScript. Alguns navegadores (Mozilla, IE) sempre o suportaram e não têm planos no mapa para remover o suporte. Outros (Safari, Opera) adotaram suporte para ele, mas o suporte em navegadores antigos não é confiável.

Existe uma boa razão para colocar essa funcionalidade valiosa no limbo?

(Ou, alternativamente, existe uma maneira melhor de controlar a função de chamada?)

pcorcoran
fonte
2
É suportado por outros navegadores, pois qualquer recurso que receba pouco uso se tornará um bug de compatibilidade para outro navegador. Se um site usa um recurso que existe apenas em um navegador, o site está quebrado em todos os outros e, normalmente, os usuários pensam que é o navegador que está quebrado.
olliej
4
(Quase todos os navegadores fizeram isso uma vez ou outra, por exemplo, esse recurso (e o próprio JS) vem do Netscape, XHR, originário do IE, Canvas no Safari, etc. Alguns deles são úteis e são escolhidos pelos outros navegadores. ao longo do tempo (js, canvas, xhr são exemplos), alguns (.callee) não são
#
@olliej Seu comentário sobre apoiá-lo porque é usado e não porque é um padrão (ou mesmo apesar de ter sido descontinuado no padrão) é muito verdadeiro! É por isso que comecei a ignorar os padrões sempre que sinto que eles não estão me ajudando. Nós, como desenvolvedores, podemos moldar a direção dos padrões usando o que funciona e não o que a especificação diz que devemos fazer. É assim que chegamos <b>e <i>voltamos (sim, esses foram preteridos em um ponto).
Stijn de Witt

Respostas:

252

As versões anteriores do JavaScript não permitiam expressões de função nomeadas e, por isso, não conseguimos criar uma expressão de função recursiva:

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

Para contornar isso, arguments.calleefoi adicionado o que poderíamos fazer:

 [1,2,3,4,5].map(function(n) {
     return (!(n>1))? 1 : arguments.callee(n-1)*n;
 });

No entanto, essa foi realmente uma solução muito ruim, pois isso (em conjunto com outros argumentos, problemas de chamada e chamador) impossibilita a recursão de alinhamento e cauda no caso geral (você pode obtê-lo em casos selecionados através de rastreamento etc.), mas mesmo o melhor código está abaixo do ideal devido a verificações que de outra forma não seriam necessárias). A outra questão importante é que a chamada recursiva terá um thisvalor diferente , por exemplo:

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

De qualquer forma, o EcmaScript 3 resolveu esses problemas, permitindo expressões de funções nomeadas, por exemplo:

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

Isso tem vários benefícios:

  • A função pode ser chamada como qualquer outra de dentro do seu código.

  • Não polui o espaço para nome.

  • O valor de thisnão muda.

  • É mais eficiente (acessar o objeto de argumentos é caro).

Opa,

Apenas percebi que, além de tudo o mais, a questão era sobre arguments.callee.caller, ou mais especificamente Function.caller.

A qualquer momento, você pode encontrar o chamador mais profundo de qualquer função na pilha e, como eu disse acima, observar a pilha de chamadas tem um único efeito principal: torna impossível um grande número de otimizações ou muito mais difícil.

Por exemplo. se não podemos garantir que uma função fnão chamará uma função desconhecida, não será possível incorporar f. Basicamente, significa que qualquer site de chamada que possa ter sido trivialmente inlinável acumula um grande número de guardas, tome:

 function f(a, b, c, d, e) { return a ? b * c : d * e; }

Se o intérprete js não puder garantir que todos os argumentos fornecidos sejam números no momento em que a chamada é feita, ele precisará inserir verificações para todos os argumentos antes do código embutido ou não poderá incorporar a função.

Agora, nesse caso em particular, um intérprete inteligente deve ser capaz de reorganizar as verificações para que sejam mais ideais e não verificar valores que não seriam usados. No entanto, em muitos casos, isso simplesmente não é possível e, portanto, torna-se impossível alinhar.

olliej
fonte
12
Você está dizendo que ele é privado apenas porque é difícil otimizar? Isso é meio bobo.
Thomas Eding
11
Não, eu listei uma série de razões, além de que o que torna difícil optimize (embora na história geral tem mostrado que as coisas que são difíceis de otimizar também têm uma semântica que as pessoas têm dificuldade em seguir)
olliej
17
O argumento this é um pouco falso, seu valor pode ser definido pela chamada se for importante. Geralmente não é usado (pelo menos, nunca tive problemas com ele em funções recursivas). Chamar a função pelo nome tem os mesmos problemas, por thisisso acho irrelevante se o chamado é bom ou ruim. Além disso, callee e caller são apenas "descontinuados" no modo estrito (ECMAscript ed 5, dez 2009), mas acho que isso não era conhecido em 2008 quando olliej postou.
RobG 27/05
8
) Ainda não vejo a lógica. Em qualquer língua com funções de primeira classe, há um claro valor em ser capaz de definir um corpo função que pode referir-se a si mesmo, sem ter que saber i
Mark Reed
8
RobG apontou isso, mas não acho que ficou tão claro: a recorrência usando uma função nomeada preservará apenas o valor de thisif thisé o escopo global. Em todos os outros casos, o valor de this vai mudar após a primeira chamada recursiva, então eu acho que as partes de sua resposta alusivas à preservação do thisnão são realmente válido.
JLRishe
89

arguments.callee.calleré não obsoleto, apesar de não fazer uso da propriedade. ( apenas fornecerá uma referência à função atual)Function.callerarguments.callee

  • Function.caller, embora não padrão de acordo com o ECMA3, é implementado em todos os principais navegadores atuais .
  • arguments.caller foi descontinuado em favor de e não foi implementado em alguns dos principais navegadores atuais (por exemplo, Firefox 3).Function.caller

Portanto, a situação é menos do que ideal, mas se você quiser acessar a função de chamada em Javascript em todos os principais navegadores, poderá usar a propriedade, acessada diretamente em uma referência de função nomeada ou a partir de uma função anônima através da propriedadeFunction.callerarguments.callee

James Wheare
fonte
5
Esta é a melhor explicação para o que é e o que não é obsoleto, muito útil. Para um bom exemplo do que Function.caller não pode fazer (obter o rastreamento de pilha de funções recursivas), consulte developer.mozilla.org/en/JavaScript/Reference/Global_Objects/...
Juan Mendes
1
Embora, arguments.calleeé proibido no modo estrito. Isso também me deixou triste, mas é melhor não usá-lo.
Gras Double
1
O hiperlink argument.callee que você precisa para o MDN diz que ele foi removido no modo estrito. Isso não é o mesmo que reprovado?
Styfle
1
Observe que arguments.callee.callerestá obsoleto no modo estrito do ES5: "Outro recurso que foi descontinuado foi argumentos.callee.caller ou, mais especificamente, Function.caller." ( Source )
thdoan
29

É melhor usar funções nomeadas do que argumentos.callee:

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

é melhor que

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

A função nomeada terá acesso ao seu chamador através da propriedade do chamador :

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

o que é melhor que

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

A descontinuação ocorre devido aos princípios atuais de design do ECMAScript .

Zach
fonte
2
Você pode descrever por que usar a função nomeada é melhor. Nunca é necessário usar o chamado em uma função anônima?
AnthonyWJones
27
Se você estiver usando o chamado em uma função anônima, terá uma função que não deve ser anônima.
Prestaul 19/09/08
3
Às vezes, a maneira mais fácil de depurar é com .caller (). Nesses casos, as funções nomeadas não ajudarão - você está tentando descobrir qual função está fazendo a chamada.
SamGoody
6
Defina melhor. Por exemplo, o IE6-8 nomeou quirks da função enquanto argumentos.callee funciona.
Cmc #
1
Além das peculiaridades do IE6-8, ele também torna o código fortemente acoplado. Se os nomes de objetos e / ou funções forem codificados, então como ardsasd e rsk82 mencionam, existem grandes perigos de refatoração, que aumentam apenas à medida que o tamanho da base de código aumenta. Os testes de unidade são uma linha de defesa, e eu os uso, mas ainda não são uma resposta que realmente me sacie pessoalmente em relação a esse problema codificado.
Jasmine Hegman
0

Apenas uma extensão. O valor "this" muda durante a recursão. No exemplo a seguir (modificado), o fatorial obtém o objeto {foo: true}.

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

O fatorial chamado primeira vez obtém o objeto, mas isso não é verdadeiro para chamadas recursivas.

FERcsI
fonte
1
Porque você está fazendo errado. Se thisprecisar ser mantido, escreva o factorial.call(this, n-1)que encontrei ao escrever código recursivo, que geralmente não existe thisou thisse refere a algum nó em uma árvore e, na verdade, é bom que isso mude.
Stijn de Witt