在不同的Web浏览器上使用"use strict"的范围不一致(关于arguments.callee和caller)

Pan*_*ang 28 javascript ecmascript-5

情况:

我发现Javascript中的严格模式有些奇怪.

  • 我正在使用外部的第三方Javascript库
    • 被缩小了,
    • 拥有超过4000行代码,
    • 不是use strict在所有,
    • 正在使用arguments.callee.
  • use strict在我自己的代码中使用,在一个函数范围内.

当我调用库提供的其中一个函数时,它会抛出一个错误.然而,

  • 只有在我使用时才会抛出错误 use strict
  • 除Chrome之外的所有浏览器都会引发错误

码:

我删除了所有不相关的东西并将代码缩减为此(在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");
})();
Run Code Online (Sandbox Code Playgroud)


测试结果:

+-----------------------+-----+--------------------------------------------------------------+
| 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    |
+-----------------------+-----+--------------------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

注意:对于OS,Win= Windows 7,Mac= Mac OS 10.7.5


我的理解:

  • 所有现代桌面浏览器都支持use strict(请参阅我可以使用).
  • use strict的范围在我的函数中,因此在其范围之外定义的所有内容都不会受到影响(请参阅此Stack Overflow问题).

题:

那么,Chrome以外的所有浏览器都是错误的吗 或者反过来呢?或者是这种未定义的行为,以便浏览器可以选择以任何一种方式实现它?

T.J*_*der 41

前言

在我们开始讨论这个问题之前,有几个快速的要点:

  • 所有现代桌面浏览器支持use strict...

一点都不.IE8是一个相当现代的浏览器 (不下去了,在2015年),而IE9是一个相当相当现代的浏览器.它们都不支持严格模式(IE9支持部分模式).IE8将与我们在一起很长一段时间,因为它与Windows XP一样高.尽管XP现在已经过时了(嗯,你可以从MS购买一个特殊的"自定义支持"计划),人们会继续使用它一段时间.

  • use strict的范围在我的函数中,因此在其范围之外定义的所有内容都不会受到影响

不完全的.该规范对非严格代码使用在严格模式下创建的函数的方式施加了限制.所以严格的模式可以到达它的框外.事实上,这是你正在使用的代码正在发生的事情的一部分.

概观

那么,Chrome以外的所有浏览器都是错误的吗 或者反过来呢?或者是这种未定义的行为,以便浏览器可以选择以任何一种方式实现它?

看一下,它看起来像:

  1. Chrome正在以某种方式正确行事,

  2. Firefox以不同的方式正确行事,

  3. ...而且IE10的版本有点错误.:-)(IE9肯定是错的,虽然没有特别有害的方式.)

我没看其他人,我想我们已经覆盖了地面.

从根本上造成麻烦的代码是这个循环

var a5 = arguments.callee;
while (a5) {
    a5 = a5.caller      // Error on this line in all browsers except Chrome
}
Run Code Online (Sandbox Code Playgroud)

...它依赖于caller函数对象的属性.那么让我们从那里开始.

Function#caller

Function#caller属性从未在第3版规范中定义.一些实现提供了它,其他实现没有.这是一个令人震惊的坏主意 (抱歉,这是主观的,不是吗?)一个实现问题(甚至更多arguments.caller),特别是在多线程环境中(并且有多线程JavaScript引擎),以及递归代码,正如Bergi在关于这个问题的评论中指出的那样.

所以在第5版中,他们通过指定caller在严格函数上引用属性会引发错误来明确地摆脱它.(这是在§13.2,创建函数对象,第19步.)

这是一个严格的功能.但是,在非严格函数上,行为未指定且与实现有关.这就是为什么有这么多不同的方法来做到这一点.

仪表代码

返回检测代码比调试会话更容易,所以让我们使用它:

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));
}
Run Code Online (Sandbox Code Playgroud)

Chrome如何正确使用它

在V8(Chrome的JavaScript引擎)上,上面的代码为我们提供了以下代码:

1. Getting a5 from arguments.callee
2. What did we get? [object Function]
3. Getting a5.caller
4. What is a5 now? [object Null]

所以我们得到了foo.bar函数的引用arguments.callee,但是然后访问caller那个非严格的函数给了我们null.循环终止,我们不会收到任何错误.

由于Function#caller未指定非严格函数,因此允许V8执行其访问所需的任何caller操作foo.bar.回归null是完全合理的(虽然我很惊讶地看到null而不是undefined).(我们将null在下面的结论中再回过头来看......)

Firefox如何做对

SpiderMonkey(Firefox的JavaScript引擎)执行此操作:

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

我们开始foo.bar从中获取arguments.callee,但是然后访问caller该非严格函数失败并出现错误.

由于caller对非严格函数的访问权限是未指定的行为,因此SpiderMonkey人员可以做他们想要的事情.如果要返回的函数是严格函数,他们决定抛出错误.一个细线,但由于这是未指定的,他们被允许走它.

IE10如何获得它非常错误

JScript(IE10的JavaScript引擎)执行此操作:

 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

和其他人一样,我们从中获得foo.bar功能arguments.callee.然后尝试访问非严格函数caller会给我们一个错误,说我们不能在严格模式下执行此操作.

我称之为"错误"(但是使用非常小写的"w"),因为它说我们不能做我们在严格模式下所做的事情,但我们并没有处于严格模式.

但你可以说,Chrome和Firefox的做法并没有错,因为(再次)caller访问是未指定的行为.所以IE10人员决定他们实现这种未指定的行为会引发严格模式错误.我认为这是误导,但再次,如果它是"错误的",它当然不是非常错误.

顺便说一句,IE9肯定是错的:

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]

它允许Function#caller在非严格函数上,然后允许它在严格函数上返回null.规范很清楚,第二次访问应该抛出错误,因为它是caller在严格的函数上访问.

结论和观察

以上所有内容的有趣之处在于,除了明确指定的抛出错误行为,如果您尝试访问caller严格的函数,Chrome,Firefox和IE10都会(以各种方式)阻止您使用caller以获取对一个严格的函数,即使访问caller非严格的函数.Firefox通过抛出错误来做到这一点.Chrome和IE10通过返回来做到这一点null.它们都支持得到一个参照经-strict功能caller(在非严格的功能),只是没有严格的功能.

我无法在任何地方找到指定的行为(但是,caller非严格的函数完全没有指定...).它可能是正确的东西(tm),我只是没有看到它指定.

这段代码也很有趣:Live Copy | 直播源

<!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>
Run Code Online (Sandbox Code Playgroud)

  • @TJCrowder几乎每次我在问题列表中看到TJCrowder的签名时我都知道答案会更长,而且我会学到比预期更多的知识.我的问题又被填满了:)谢谢. (4认同)