¿Por qué la propiedad argumentos.callee.caller está en desuso en JavaScript?


214

¿Por qué la arguments.callee.callerpropiedad quedó en desuso en JavaScript?

Fue agregado y luego desaprobado en JavaScript, pero fue omitido por completo por ECMAScript. Algunos navegadores (Mozilla, IE) siempre lo han admitido y no tienen planes en el mapa para eliminar el soporte. Otros (Safari, Opera) han adoptado soporte para él, pero el soporte en navegadores antiguos no es confiable.

¿Hay una buena razón para poner esta valiosa funcionalidad en el limbo?

(O, alternativamente, ¿hay una mejor manera de controlar la función de llamada?)


2
Es compatible con otros navegadores porque cualquier característica que obtenga un mínimo de uso generalizado se convertirá en un error de compatibilidad para otro navegador. Si un sitio usa una función que solo existe en un navegador, el sitio está roto en todos los demás, y generalmente los usuarios piensan que es el navegador el que está roto.
olliej

44
(Casi todos los navegadores han hecho esto en un momento u otro, por ejemplo, esta característica (y JS en sí) proviene de Netscape, XHR originado en IE, Canvas en Safari, etc. Algunos de estos son útiles y son recogidos por los otros navegadores con el tiempo (js, canvas, xhr son todos ejemplos), algunos (.callee) no lo son.
olliej

@olliej ¡Su comentario sobre apoyarlo porque se usa y no porque es un estándar (o incluso a pesar de que está en desuso en el estándar) es muy cierto! Es por eso que comencé a ignorar los estándares cuando siento que no me están ayudando. Nosotros, como desarrolladores, podemos dar forma a la dirección de los estándares utilizando lo que funciona y no lo que la especificación dice que debemos hacer. Así es como llegamos <b>y <i>regresamos (sí, esos fueron desaprobados en un punto).
Stijn de Witt

Respuestas:


252

Las primeras versiones de JavaScript no permitían expresiones de funciones con nombre, y por eso no pudimos hacer una expresión de función 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 evitar esto, arguments.calleese agregó para que pudiéramos hacer:

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

Sin embargo, esta fue realmente una solución realmente mala, ya que esto (junto con otros argumentos, llamadas y problemas de llamadas) hace que la reclinación de cola y la cola sean imposibles en el caso general (puede lograrlo en casos seleccionados a través del rastreo, etc., pero incluso el mejor código es subóptimo debido a controles que de otra forma no serían necesarios). El otro problema importante es que la llamada recursiva tendrá un thisvalor diferente , por ejemplo:

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 todos modos, EcmaScript 3 resolvió estos problemas al permitir expresiones de funciones con nombre, por ejemplo:

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

Esto tiene numerosos beneficios:

  • La función se puede llamar como cualquier otra dentro de su código.

  • No contamina el espacio de nombres.

  • El valor de thisno cambia.

  • Es más eficiente (acceder al objeto de argumentos es costoso).

Whoops

Me acabo de dar cuenta de que, además de todo lo demás, la pregunta se refería arguments.callee.caller, o más específicamente Function.caller.

En cualquier momento puede encontrar la llamada más profunda de cualquier función en la pila, y como dije anteriormente, mirar la pila de llamadas tiene un efecto importante: hace que una gran cantidad de optimizaciones sea imposible o mucho más difícil.

P.ej. Si no podemos garantizar que una función fno llame a una función desconocida, entonces no es posible en línea f. Básicamente significa que cualquier sitio de llamadas que puede haber sido trivialmente inlinable acumula una gran cantidad de guardias, tome:

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

Si el intérprete js no puede garantizar que todos los argumentos proporcionados sean números en el momento en que se realiza la llamada, debe insertar verificaciones para todos los argumentos antes del código en línea, o no puede en línea la función.

Ahora, en este caso particular, un intérprete inteligente debería ser capaz de reorganizar las comprobaciones para que sean más óptimas y no verificar ningún valor que no se utilizaría. Sin embargo, en muchos casos eso simplemente no es posible y, por lo tanto, es imposible alinearse.


12
¿Estás diciendo que está depurada solo porque es difícil de optimizar? Eso es un poco tonto.
Thomas Eding

11
No, enumeré una serie de razones, además de que hace que sea difícil de optimizar (aunque en general la historia ha demostrado que las cosas que son difíciles de optimizar también tienen una semántica que las personas tienen dificultades para seguir)
olliej

17
El este argumento es un poco falsa, su valor puede ser establecido por la llamada si es importante. Por lo general, no se usa (al menos, nunca he tenido un problema con él en las funciones recursivas). Llamar a la función por su nombre tiene los mismos problemas, por thislo que creo que es irrelevante con respecto a si la persona que llama es buena o mala. Además, la persona que llama y la persona que llama solo están "en desuso" en modo estricto (ECMAscript ed 5, diciembre de 2009), pero supongo que eso no se sabía en 2008 cuando olliej publicó.
RobG

8
) Todavía no veo la lógica. En cualquier lenguaje con funciones de primera clase, hay un claro valor en ser capaz de definir un cuerpo de la función que puede referirse a sí mismo sin tener que saber i
Mark Reed

8
RobG señaló esto, pero no creo que fuera tan claro: recurrir usando una función con nombre solo preservará el valor de thisif thises el alcance global. En todos los demás casos, el valor de this va a cambiar después de la primera llamada recursiva, así que creo que las partes de su respuesta aludiendo a la preservación de la thisrealidad no son válidos.
JLRishe

89

arguments.callee.callerno está en desuso, aunque hace uso de la propiedad. ( solo le dará una referencia a la función actual)Function.callerarguments.callee

  • Function.caller, aunque no es estándar según ECMA3, se implementa en todos los principales navegadores actuales .
  • arguments.caller está en desuso a favor y no se implementa en algunos de los principales navegadores actuales (por ejemplo, Firefox 3).Function.caller

Por lo tanto, la situación es menos que ideal, pero si desea acceder a la función de llamada en Javascript en todos los principales navegadores, puede usar la propiedad, ya sea accediendo directamente en una referencia de función con nombre o desde una función anónima a través de la propiedad.Function.callerarguments.callee


55
Esta es la mejor explicación de lo que está y no está en desuso, muy útil. Para un buen ejemplo de lo que Function.caller no puede hacer (obtener el seguimiento de la pila de funciones recursivas), vea developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Juan Mendes

1
Sin embargo, arguments.calleeestá prohibido en modo estricto. También me puso triste, pero es mejor no usarlo más.
Gras Double

1
El hipervínculo argumentos.callee que tiene a MDN dice que se elimina en modo estricto. ¿No es lo mismo que obsoleto?
styfle

1
Tenga en cuenta que arguments.callee.callerestá en desuso en el modo estricto de ES5: "Otra característica que estaba en desuso fue argumentos.callee.caller, o más específicamente Function.caller". ( Fuente )
thdoan

29

Es mejor usar funciones nombradas que argumentos.callee:

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

es mejor que

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

La función nombrada tendrá acceso a su llamador a través de la propiedad llamante :

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

que es mejor que

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

La desaprobación se debe a los principios de diseño actuales de ECMAScript .


2
¿Puede describir por qué es mejor usar la función nombrada? ¿Nunca hay necesidad de usar el llamado en una función anónima?
AnthonyWJones

27
Si usa callee en una función anónima, entonces tiene una función que no debe ser anónima.
Prestaul

3
a veces la forma más fácil de depurar es con .caller (). En tales casos, las funciones con nombre no ayudarán: está tratando de determinar qué función está llamando.
SamGoody

66
Define mejor. Por ejemplo, IE6-8 ha nombrado peculiaridades de funciones mientras argumentos.callee funciona.
cmc

1
Además de las peculiaridades de IE6-8, también hace que el código esté estrechamente acoplado. Si los nombres de los objetos y / o funciones están codificados, entonces, como mencionan ardsasd y rsk82, existen grandes peligros de refactorización, que solo aumentan a medida que aumenta el tamaño de la base del código. Las pruebas unitarias son una línea de defensa, y las uso, pero todavía no son una respuesta que realmente me sacie personalmente con respecto a este problema de hardcoding.
Jasmine Hegman

0

Solo una extensión. El valor de "esto" cambia durante la recursión. En el siguiente ejemplo (modificado), factorial obtiene el 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}     );

El factorial llamado primera vez obtiene el objeto, pero esto no es cierto para las llamadas recursivas.


1
Porque lo estás haciendo mal. Si thisnecesita mantenimiento, escriba factorial.call(this, n-1)que realmente he encontrado al escribir código recursivo, que generalmente no existe this, o se thisrefiere a algún nodo en un árbol y en realidad es bueno que cambie.
Stijn de Witt
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.