javascript 不同网络浏览器上“严格使用”的范围不一致(关于arguments.callee 和caller)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16871050/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 06:17:52  来源:igfitidea点击:

Inconsistent scope of "use strict" on different web browsers (concerning arguments.callee and caller)

javascriptecmascript-5

提问by Pang

Situation:

情况:

I found something strange concerning strict modein Javascript.

我在 Javascript 中发现了一些关于严格模式的奇怪之处。

  • I am using an external, third-party Javascript library which
    • was minified,
    • has over 4000 lines of code,
    • is notusing use strictat all, and
    • is using arguments.callee.
  • I am using use strictin my own code, scoped within a function.
  • 我正在使用外部第三方 Javascript 库
    • 被缩小,
    • 有超过 4000 行代码,
    • 根本没有使用use strict,并且
    • 正在使用arguments.callee.
  • use strict在我自己的代码中使用,作用域在一个函数内。

When I call one of the functions provided by the library, it throws an error. However,

当我调用库提供的函数之一时,它会引发错误。然而,

  • the error is thrown only if I am using use strict
  • the error is thrown in all browsers except Chrome
  • 只有在我使用时才会抛出错误 use strict
  • 错误在除 Chrome 之外的所有浏览器中抛出


Code:

代码:

I've removed all the unrelated stuff and reduced the code into this (online demo on jsFiddle):

我已经删除了所有不相关的东西并将代码简化为这个(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");
})();



Test result:

测试结果:

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

Note: for OS, Win= Windows 7, Mac= Mac OS 10.7.5

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



My understanding:

我的理解:

  • All modern desktop browsers support use strict(see Can I use).
  • The use strictis scoped within my function, so everything defined outside its scope is not affected (see this Stack Overflow question).
  • 所有现代桌面浏览器都支持use strict(请参阅我可以使用)。
  • use strict是我的职责内范围的,所以它的范围之外定义的一切都没有影响(见这个堆栈溢出问题)。


Question:

问题:

So, are all browsers except Chrome wrong? Or is it the other way round? Or is this undefined behaviour so the browsers may choose to implement it in either way?

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

回答by T.J. Crowder

Preface

前言

A couple of quick points before we get into the meat of this:

在我们深入讨论这个问题之前,有几个要点:

  • All modern desktop browsers support use strict...
  • 所有现代桌面浏览器都支持use strict...

No, not at all. IE8 is a fairly modern browser(not anymore, in 2015), and IE9 is a quitefairly modern browser. Neither of them supports strict mode (IE9 supports parts of it). IE8 is going to be with us a long time, because it's as high as you can go on Windows XP. Even though XP is now flatly end-of-lifed (well, you can buy a special "Custom Support" plan from MS), people will continue to use it for a while.

一点都不。IE8是一个相当现代的浏览器(不下去了,在2015年),而IE9是一个相当相当现代的浏览器。它们都不支持严格模式(IE9 支持它的一部分)。IE8 将伴随我们很长时间,因为它与您在 Windows XP 上的性能一样高。尽管 XP 现在已完全停止使用(好吧,您可以从 MS 购买特殊的“自定义支持”计划),但人们仍会继续使用它一段时间。

  • The use strictis scoped within my function, so everything defined outside its scope is not affected
  • use strict是我的职责内范围的,所以一切规定以外的范围不受影响

Not quite. The specification imposes restrictions on how even non-strict code uses functions created in strict mode. So strict mode can reach outside its box. And in fact, that's part of what's going on with the code you're using.

不完全的。该规范对非严格代码如何使用在严格模式下创建的函数施加了限制。所以严格模式可以超出它的范围。事实上,这是您正在使用的代码的一部分。

Overview

概述

So, are all browsers except Chrome wrong? Or is it the other way round? Or is this undefined behaviour so the browsers may choose to implement it in either way?

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

Looking into it a bit, it looks like:

稍微看一下,它看起来像:

  1. Chrome is getting it right one way,

  2. Firefox is getting it right a different way,

  3. ...and IE10 is getting it very slightlywrong. :-) (IE9 definitely gets it wrong, although not in a particularly harmful way.)

  1. Chrome 正以一种方式做到这一点,

  2. Firefox 正在以不同的方式正确处理,

  3. ...和 ​​IE10 得到它非常轻微的错误。:-)(IE9 肯定会出错,虽然不是特别有害。)

I didn't look at the others, I figured we'd covered the ground.

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

The code fundamentally causing the trouble is this loop

从根本上引起问题的代码是这个循环

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

...which relies on the callerproperty of function objects. So let's start there.

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

Function#caller

Function#caller

The Function#callerproperty was never defined in the 3rd edition specification. Some implementations provided it, others didn't. It's a shockingly bad idea(sorry, that was subjective, wasn't it?)an implementation issue (even more of one than arguments.caller), particularly in multi-threaded environments (and there are multi-threaded JavaScript engines), as well as with recursive code, as Bergi pointed out in the comments on the question.

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

So in the 5th edition they explicitly got rid of it, by specifying that referencing the callerproperty on a strict function would throw an error. (This is in §13.2, Creating Function Objects, Step 19.)

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

That's on a strictfunction. On a non-strict function, though, the behavior is unspecified and implementation-dependent. Which is why there are so many different ways to get this right.

这是一个严格的函数。但是,在非严格函数上,行为是未指定的并且依赖于实现。这就是为什么有这么多不同的方法可以做到这一点。

Instrumented Code

插装代码

It's easier to refer back to instrumented code than a debugging session, so let's use this:

与调试会话相比,回溯检测的代码更容易,所以让我们使用它

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

How Chrome Gets It Right

Chrome 如何做到正确

On V8 (Chrome's JavaScript engine), the code above gives us this:

在 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]

So we got a reference to the foo.barfunction from arguments.callee, but then accessing calleron that non-strict function gave us null. The loop terminates and we don't get any error.

因此,我们从 获得了对该foo.bar函数的引用arguments.callee,但随后访问caller该非严格函数给了我们null. 循环终止,我们没有收到任何错误。

Since Function#calleris unspecified for non-strict functions, V8 is allowed to do anything it wants for that access to calleron foo.bar. Returning nullis perfectly reasonable (although I was surprised to see nullrather than undefined). (We'll come back to that nullin the conclusions below...)

由于Function#caller对于非严格函数未指定,因此 V8 可以为访问calleron做任何它想做的事情foo.bar。返回null是完全合理的(尽管我很惊讶地看到null而不是undefined)。(我们将null在下面的结论中回到这一点......)

How Firefox Gets It Right

Firefox 如何做到正确

SpiderMonkey (Firefox's JavaScript engine) does this:

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

We start out getting foo.barfrom arguments.callee, but then accessing calleron that non-strict function fails with an error.

我们开始了越来越foo.bararguments.callee,但随后访问caller在非严格的功能因错误而失败。

Since, again, the access to calleron a non-strict function is unspecified behavior, the SpiderMonkey folks can do what they want. They decided to throw an error if the function that would be returned is a strict function. A fine line, but as this is unspecified, they're allowed to walk it.

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

How IE10 Gets It Very Slightly Wrong

IE10 如何得到它非常轻微的错误

JScript (IE10's JavaScript engine) does this:

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

As with the others, we get the foo.barfunction from arguments.callee. Then trying to access that non-strict function's callergives us an error saying we can't do that in strict mode.

与其他foo.bar函数一样,我们从arguments.callee. 然后尝试访问那个非严格函数caller会给我们一个错误,说我们不能在严格模式下这样做。

I call this "wrong" (but with a verylower-case "w") because it says that we can't do what we're doing in strict mode, but we're not instrict mode.

我把这种“错误”(但有一个非常小写字母“W”),因为它说,我们不能做我们在严格模式做什么,但我们不是严格模式。

But you could argue this is no more wrong that what Chrome and Firefox do, because (again) the calleraccess is unspecified behavior. So the IE10 folks decided that their implementation of this unspecified behavior would throw a strict-mode error. I think it's misleading, but again, if it's "wrong," it certainly isn't verywrong.

但是您可以争辩说,这与 Chrome 和 Firefox 所做的一样没有错,因为(再次)caller访问是未指定的行为。所以 IE10 的人决定他们对这种未指定行为的实现会引发严格模式错误。我认为它具有误导性,但同样,如果它是“错误的”,那肯定不是错误。

BTW, IE9 definitely gets this wrong:

顺便说一句,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]

It allows Function#calleron the non-strict function, and then allows it on a strict function, returning null. The spec is clear that that second access should have thrown an error, as it was accessing calleron a strict function.

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

Conclusions and Observations

结论和观察

What's interesting about all of the above is that in addition to the clearly-specified behavior of throwing an error if you try to access calleron strict functions, Chrome, Firefox, and IE10 all (in various ways) prevent your using callerto get a reference to a strict function, even when accessing calleron a non-strict function. Firefox does this by throwing an error. Chrome and IE10 do it by returning null. They all support getting a reference to a non-strict function via caller(on a non-strict function), just not a strict function.

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

I can't find that behavior specified anywhere (but then, calleron non-strict functions is entirely unspecified...). It's probably the Right Thing(tm), I just don't see it specified.

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

This code is also fun to play with: Live Copy| Live Source

使用此代码也很有趣: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>

回答by scradam

I have to use an older Telerik JS library that I cant't readily update and encountered this error this morning. One possible workaround for some people might be to use the 'setTimeout' JS function to leave strict mode before calling the loose mode function.

我必须使用无法更新的旧版 Telerik JS 库,今天早上遇到了这个错误。对于某些人来说,一种可能的解决方法可能是使用“setTimeout”JS 函数在调用松散模式函数之前离开严格模式。

e.g. Change this:

例如改变这个:

function functionInStrictMode(){
  looseModeFunction();
}

To something like this:

对于这样的事情:

function functionInStrictMode(){
    setTimeout(looseModeFunction);      
}

I'm guessing this probably works because setTimeout reverts the context to the global namespace and/or leaves the scope of functionInStrictMode. I don't fully understand all the particulars. There might be better ways; I did not research this thoroughly, but I thought I'd post it here for discussion.

我猜这可能有效,因为 setTimeout 将上下文恢复到全局命名空间和/或离开 functionInStrictMode 的范围。我不完全了解所有细节。可能有更好的方法;我没有彻底研究这个,但我想我会在这里发布以供讨论。