Alcance inconsistente de "uso estricto" en diferentes navegadores web (en relación con los argumentos.llamado y llamante)
Situación:
He encontrado algo extraño con respecto a modo estricto en Javascript.
- Estoy usando una biblioteca Javascript externa de terceros que
- fue minificado,
- tiene más de 4000 líneas de código,
- es no usando
use strict
en absoluto, y - está usando
arguments.callee
.
- Estoy usando
use strict
en mi propio código, dentro del ámbito de una función.
Cuando llamo a una de las funciones proporcionadas por el biblioteca, lanza un error. Sin embargo,
- el error se genera solo si estoy usando
use strict
- el error se lanza en todos los navegadores excepto Chrome
Código:
He eliminado todas las cosas no relacionadas y reducido el código a esta (demo en línea en jsFiddle):
// This comes from the minified external JS library.
// It creates a global object "foo".
(function () {
foo = {};
foo.bar = function (e) {
return function () {
var a5 = arguments.callee;
while (a5) {
a5 = a5.caller // Error on this line in all browsers except Chrome
}
}
}("any value here");
})();
// Here's my code.
(function() {
"use strict"; // I enable strict mode in my own function only.
foo.bar();
alert("done");
})();
Resultado de la prueba:
+-----------------------+-----+--------------------------------------------------------------+
| Browser | OS | Error |
+-----------------------+-----+--------------------------------------------------------------+
| Chrome 27.0.1453.94 m | Win | <<NO ERROR!>> |
| Opera 12.15 | Win | Unhandled Error: Illegal property access |
| Firefox 21.0 | Win | TypeError: access to strict mode caller function is censored |
| Safari 5.1.7 | Win | TypeError: Type error |
| IE 10 | Win | SCRIPT5043: Accessing the 'caller' property of a function or |
| | | arguments object is not allowed in strict mode |
| Chrome 27.0.1543.93 | Mac | <<NO ERROR!>> |
| Opera 12.15 | Mac | Unhandled Error: Illegal property access |
| Firefox 21.0 | Mac | TypeError: access to strict mode caller function is censored |
| Safari 6.0.4 | Mac | TypeError: Function.caller used to retrieve strict caller |
+-----------------------+-----+--------------------------------------------------------------+
Nota: para OS
, Win
= Windows 7, Mac
= Mac OS 10.7.5
Mi comprensión:
- Todos los navegadores de escritorio modernos soportan
use strict
(ver Puedo usar). - El
use strict
está dentro del ámbito de mi función, por lo que todo lo definido fuera de su ámbito no se ve afectado (ver esta pregunta de desbordamiento de pila).
Pregunta:
Por lo tanto, son todos los navegadores excepto Chrome mal? O es al revés? ¿O es este comportamiento indefinido por lo que los navegadores pueden optar por implementarlo de cualquier manera?
1 answers
Prefacio
Un par de puntos rápidos antes de entrar en la carne de esto:{[45]]}
- Todos los navegadores de escritorio modernos soportan
use strict
...
No, para nada. IE8 es un navegador bastante moderno (ya no, en 2015), y IE9 es un bastante navegador bastante moderno. Ninguno de ellos soporta el modo estricto (IE9 soporta partes del mismo). IE8 va a estar con nosotros mucho tiempo, porque es tan alto como se puede ir en Windows XP. A pesar de que XP ahora está completamente al final de su vida útil (bueno, puede comprar un plan especial de "Soporte personalizado" de MS), la gente continuará usándolo por un tiempo.
- El
use strict
está dentro de mi función, por lo que todo lo definido fuera de su ámbito no se ve afectado{[49]]}
No del todo. La especificación impone restricciones sobre cómo incluso el código no estricto usa funciones creadas en modo estricto. El modo tan estricto puede alcanzar fuera de su caja. Y de hecho, eso es parte de lo que seguir con el código que estás usando.
Descripción general
Por lo tanto, son todos los navegadores excepto Chrome mal? O es al revés? ¿O es este comportamiento indefinido por lo que los navegadores pueden optar por implementarlo de cualquier manera?
Mirando un poco, parece: {[45]]}
Chrome lo está haciendo bien de una manera,
Firefox lo está haciendo bien de una manera diferente,
...y IE10 lo está consiguiendo muy ligeramente equivocado. :- ) (IE9 definitivamente se equivoca, aunque no de una manera particularmente dañina.)
No miré a los demás, pensé que habíamos cubierto el suelo.
El código que causa fundamentalmente el problema es este bucle
var a5 = arguments.callee;
while (a5) {
a5 = a5.caller // Error on this line in all browsers except Chrome
}
...que se basa en la propiedad caller
de los objetos de función. Así que empecemos por ahí.
Function#caller
La propiedad Function#caller
nunca fue definida en la especificación de la 3a edición. Algunas implementaciones proporcionadas es una idea sorprendentemente mala (lo siento, eso fue subjetivo, ¿no?) un problema de implementación (incluso más de uno que arguments.caller
), particularmente en entornos multihilo (y hay motores JavaScript multihilo), así como con código recursivo, como señaló Bergi en los comentarios sobre la pregunta.
Así que en la 5a edición se deshicieron explícitamente de ella, especificando que hacer referencia a la propiedad caller
en una función estricta arrojaría error. (Esto es en §13.2, Creando Objetos de Función , Paso 19.)
Eso está en una función estricta. Sin embargo, en una función no estricta, el comportamiento no está especificado y depende de la implementación. Es por eso que hay tantas maneras diferentes de hacer esto bien.
Código instrumentado
Es más fácil hacer referencia a código instrumentado que a una sesión de depuración, así que usemos esto :
console.log("1. Getting a5 from arguments.callee");
var a5 = arguments.callee;
console.log("2. What did we get? " +
Object.prototype.toString.call(a5));
while (a5) {
console.log("3. Getting a5.caller");
a5 = a5.caller; // Error on this line in all browsers except Chrome
console.log("4. What is a5 now? " +
Object.prototype.toString.call(a5));
}
Cómo Chrome Lo Consigue Derecha
En V8 (motor JavaScript de Chrome), el código anterior nos da esto:
1. Getting a5 from arguments.callee 2. What did we get? [object Function] 3. Getting a5.caller 4. What is a5 now? [object Null]
Así que obtuvimos una referencia a la función foo.bar
de arguments.callee
, pero luego acceder a caller
en esa función no estricta nos dio null
. El bucle termina y no obtenemos ningún error.
Dado que Function#caller
no está especificado para funciones no estrictas, a V8 se le permite hacer lo que quiera para ese acceso a caller
en foo.bar
. Regresar null
es perfectamente razonable (aunque me sorprendió ver null
en lugar de undefined
). (Volveremos a eso null
en las conclusiones a continuación...)
Cómo Firefox Lo Hace Bien
SpiderMonkey (motor JavaScript de Firefox) hace esto:
1. Getting a5 from arguments.callee 2. What did we get? [object Function] 3. Getting a5.caller TypeError: access to strict mode caller function is censored
Empezamos obteniendo foo.bar
desde arguments.callee
, pero luego accediendo a caller
en esa función no estricta falla con un error.
Dado que, de nuevo, el acceso a caller
en una función no estricta es un comportamiento no especificado, la gente de SpiderMonkey puede hacer lo que quiera. Decidieron lanzar un error si la función que se devolvería es una función estricta. Una línea fina, pero como no se especifica, se les permite caminar por ella.
Cómo IE10 Se Equivoca Un Poco
JScript (motor JavaScript de IE10) hace esto:
1. Getting a5 from arguments.callee 2. What did we get? [object Function] 3. Getting a5.caller SCRIPT5043: Accessing the 'caller' property of a function or arguments object is not allowed in strict mode
Al igual que con los demás, obtenemos la función foo.bar
de arguments.callee
. Entonces tratar de acceder a esa función no estricta caller
nos da un error diciendo que no podemos hacer eso en modo estricto.
Yo llamo a esto " incorrecto "(pero con un muy "w" minúscula) porque dice que no podemos hacer lo que estamos haciendo en modo estricto, pero no estamos en modo estricto.
Pero se podría argumentar que esto no es más malo que lo que Chrome y Firefox hacen, porque (de nuevo) el acceso caller
es un comportamiento no especificado. Así que la gente de IE10 decidió que su implementación de este comportamiento no especificado arrojaría un error de modo estricto. Creo que es engañoso, pero de nuevo, si es "incorrecto", ciertamente no es muy{[56]]} Equivocada.
POR cierto, IE9 definitivamente se equivoca:
1. Getting a5 from arguments.callee 2. What did we get? [object Function] 3. Getting a5.caller 4. What is a5 now? [object Function] 3. Getting a5.caller 4. What is a5 now? [object Null]
Permite Function#caller
en la función no estricta, y luego lo permite en una función estricta, devolviendo null
. La especificación es clara de que ese segundo acceso debería haber arrojado un error, ya que estaba accediendo a caller
en una función estricta.
Conclusiones y observaciones
Lo interesante de todo lo anterior es que además del comportamiento claramente especificado de lanzar un error si intenta acceder a caller
en funciones estrictas, Chrome, Firefox e IE10 (de varias maneras) evitan que uses caller
para obtener una referencia a una función estricta, incluso cuando accedes a caller
en una función no estricta. Firefox hace esto lanzando un error. Chrome e IE10 lo hacen devolviendo null
. Todos ellos admiten obtener una referencia a una función non-strict a través de caller
(en una función no estricta), solo que no es una función estricta.
No puedo encontrar ese comportamiento especificado en ninguna parte (pero entonces, caller
en las funciones no estrictas no están especificadas en absoluto...). Probablemente sea lo Correcto(tm), simplemente no lo veo especificado.
Este código también es divertido para jugar: Live Copy | Fuente en vivo
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Strict and Loose Function#caller</title>
<style>
p {
font-family: sans-serif;
margin: 0.1em;
}
.err {
color: #d00;
}
</style>
</head>
<body>
<script>
function display(msg, cls) {
var p = document.createElement('p');
if (cls) {
p.className = cls;
}
p.innerHTML = String(msg);
document.body.appendChild(p);
}
// The loose functions
(function () {
function loose1() {
display("loose1 calling loose2");
loose2();
}
loose1.id = "loose1"; // Since name isn't standard yet
function loose2() {
var c;
try {
display("loose2: looping through callers:");
c = loose2;
while (c) {
display("loose2: getting " + c.id + ".caller");
c = c.caller;
display("loose2: got " +
((c && c.id) || Object.prototype.toString.call(c)));
}
display("loose2: done");
}
catch (e) {
display("loose2: exception: " +
(e.message || String(e)),
"err");
}
}
loose2.id = "loose2";
window.loose1 = loose1;
window.loose2 = loose2;
})();
// The strict ones
(function() {
"use strict";
function strict1() {
display("strict1: calling strict2");
strict2();
}
strict1.id = "strict1";
function strict2() {
display("strict2: calling loose1");
loose1();
}
strict2.id = "strict2";
function strict3() {
display("strict3: calling strict4");
strict4();
}
strict3.id = "strict3";
function strict4() {
var c;
try {
display("strict4: getting strict4.caller");
c = strict4.caller;
}
catch (e) {
display("strict4: exception: " +
(e.message || String(e)),
"err");
}
}
strict4.id = "strict4";
strict1();
strict3();
})();
</script>
</body>
</html>
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-13 08:34:22