不同Web浏览器上“use strict”的范围不一致(关于arguments.callee和caller)

时间:2013-06-01 09:07:17

标签: javascript ecmascript-5

场合:

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

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

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

  • 仅当我使用use strict
  • 时才会引发错误
  • 除Chrome以外的所有浏览器都会引发错误

代码:

我删除了所有不相关的内容并将代码缩减为此内容(online demo on 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");
})();


测试结果:

+-----------------------+-----+--------------------------------------------------------------+
| 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    |
+-----------------------+-----+--------------------------------------------------------------+

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


我的理解:

  • 所有现代桌面浏览器均支持use strict(请参阅Can I use)。
  • use strict在我的函数范围内,因此在其范围之外定义的所有内容都不会受到影响(请参阅this Stack Overflow question)。

问题:

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

2 个答案:

答案 0 :(得分:41)

前言

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

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

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

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

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

概述

  

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

稍微调查一下,看起来像是:

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

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

  3. ...而且IE10的非常轻微错误。 :-)(IE9肯定是错的,虽然没有特别有害的方式。)

  4. 我没有看到其他人,我认为我们已经覆盖了地面。

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

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

    ...它依赖于函数对象的caller属性。所以,让我们从那里开始吧。

    Function#caller

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

    所以在第5版中,他们通过指定在strict函数上引用arguments.caller属性会引发错误来明确地消除它。 (这是§13.2, Creating Function Objects, Step 19。)

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

    仪表代码

    回调检测代码比调试会话更容易,所以让我们use this

    caller

    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]

    因此,我们从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)); } 获得了对foo.bar函数的引用,但随后访问该非严格函数的arguments.callee给了我们caller。循环终止,我们不会收到任何错误。

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

    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

    我们开始从null获取foo.bar,但是在非严格函数上访问arguments.callee失败并显示错误。

    由于非严格函数对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

    与其他人一样,我们从caller获取foo.bar函数。然后尝试访问非严格函数arguments.callee会给我们一个错误,说我们无法在严格模式下执行此操作。

    我称之为&#34;错误&#34; (但是非常小写&#34; w&#34;)因为它说我们不能做我们在严格模式下做的事情,但我们&#39 ; 严格模式。

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

    BTW,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]

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

    结论和观察

    以上所有内容的有趣之处在于,如果您尝试在严格的功能,Chrome,Firefox和IE10上访问caller,那么除了明确指定的抛出错误行为之外(以各种方式)阻止您使用caller获取对严格函数的引用,即使在非严格函数上访问caller时也是如此。 Firefox通过抛出错误来做到这一点。 Chrome和IE10通过返回caller来完成此操作。它们都支持通过null(在非严格函数上)引用 -strict函数,而不是严格的函数。

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

    此代码也很有趣:Live Copy | Live Source

    caller

答案 1 :(得分:0)

我必须使用一个较旧的Telerik JS库,该库无法轻松更新,并且今天早晨遇到了此错误。对于某些人来说,一种可行的解决方法可能是在调用松散模式函数之前,使用“ setTimeout” JS函数退出严格模式。

例如更改此:

function functionInStrictMode(){
  looseModeFunction();
}

对于这样的事情:

function functionInStrictMode(){
    setTimeout(looseModeFunction);      
}

我猜想这可能可行,因为setTimeout将上下文还原为全局名称空间和/或离开了functionInStrictMode的范围。我不完全了解所有细节。可能会有更好的方法;我没有对此进行彻底的研究,但我想将其发布在这里进行讨论。