“严格使用”的范围不一致在不同的Web浏览器上(关于arguments.callee和调用者) [英] Inconsistent scope of "use strict" on different web browsers (concerning arguments.callee and caller)

查看:104
本文介绍了“严格使用”的范围不一致在不同的Web浏览器上(关于arguments.callee和调用者)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

情况:



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




  • 我使用的是外部的第三方Javascript库,其中


    • 被缩小,

    • 拥有超过4000行代码,

    • 使用使用严格,以及

    • 正在使用 arguments.callee


  • 我在我自己的代码中使用 use strict ,在函数范围内。



当我调用库提供的其中一个函数时,它会抛出一个错误。但是,只有当我使用 use strict

  • 除Chrome以外的所有浏览器都会引发错误






  • < h3>代码:

    我删除了所有不相关的东西并将代码缩减为此(关于jsFiddle的在线演示):



      //这来自缩小的外部JS库。它创建了一个全局对象foo。(function(){foo = {}; foo.bar = function(e){return function(){var a5 = arguments.callee; while(a5){a5 = a5.caller //除Chrome之外的所有浏览器中此行的错误}}}(此处有任何值);})(); //这是我的代码。(功能ion(){use strict; //我只在自己的函数中启用严格模式。 foo.bar(); alert(done);})();  






    测试结果:



      + ------------ ----------- + ----- + -------------------------------- ------------------------------ + 
    |浏览器| OS |错误|
    + ----------------------- + ----- + --------------- ----------------------------------------------- +
    | Chrome 27.0.1453.94 m |赢| << NO ERROR!>> |
    |歌剧12.15 |赢|未处理的错误:非法属性访问|
    | Firefox 21.0 |赢| TypeError:访问严格模式调用函数被删除|
    | Safari 5.1.7 |赢| TypeError:输入错误|
    | IE 10 |赢| SCRIPT5043:访问函数的调用者属性或|
    | | |在严格模式下不允许使用arguments对象
    | Chrome 27.0.1543.93 | Mac | << NO ERROR!>> |
    |歌剧12.15 | Mac |未处理的错误:非法属性访问|
    | Firefox 21.0 | Mac | TypeError:访问严格模式调用函数被删除|
    | Safari 6.0.4 | Mac | TypeError:Function.caller用于检索严格的调用者|
    + ----------------------- + ----- + --------------- ----------------------------------------------- +

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






    我的理解:




    • 所有现代桌面浏览器都支持使用严格(参见我可以使用)。

    • use strict 在我的函数范围内,因此在其范围之外定义的所有内容都不会受到影响(请参阅此Stack Overflow问题)。






    问题:



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

    解决方案

    前言



    在我们讨论这个问题之前的几个快速点:



    • 所有现代桌面浏览器都支持使用严格 ...


    不,完全没有。 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 //除Chrome之外的所有浏览器中此行的错误
    }

    ...它依赖于函数对象的调用者属性。所以让我们从那里开始。



    函数#calller



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



    所以在第5版中,他们通过指定在严格函数上引用调用者属性会抛出错误来明确地消除它。 (这是在§13.2,创建函数对象,第19步。)



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



    检验代码



    更容易推荐回到检测代码而不是调试会话,所以让我们使用这个

      console.log(1。从arguments.callee获取a5); 
    var a5 = arguments.callee;
    console.log(2.我们得到了什么?+
    Object.prototype.toString.call(a5));
    while(a5){
    console.log(3。获取a5.caller);
    a5 = a5.caller; //除了Chrome
    console.log之外的所有浏览器中的这行错误(4.现在是什么a5?+
    Object.prototype.toString.call(a5));
    }



    Chrome如何正确使用



    在V8(Chrome的JavaScript引擎)上,上面的代码为我们提供了这样的信息:

     1。从arguments.callee获取a5 
    2.我们得到了什么? [对象功能]
    3.获取a5.caller
    4.现在是什么a5? [object Null]

    所以我们从<$ c获得了对 foo.bar 函数的引用$ c> arguments.callee ,但是在非严格函数上访问调用者给了我们 null 。循环终止,我们不会收到任何错误。



    由于函数#calller 未指定非严格函数,V8允许在 foo.bar 上执行任何想要访问调用者的操作。返回 null 是完全合理的(虽然我很惊讶地看到 null 而不是 undefined )。 (我们将在下面的结论中回到 null ...)



    Firefox如何正确



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

     1。从arguments.callee获取a5 
    2.我们得到了什么? [对象功能]
    3.获取a5.caller
    TypeError:访问严格模式调用函数被审查

    我们开始获取 foo.bar 来自 arguments.callee ,但随后访问来电者关于非严格函数失败并出现错误。



    因为再次访问调用者非严格函数是未指定的行为,SpiderMonkey人员可以做他们想要的。如果要返回的函数是严格函数,他们决定抛出错误。一个细线,但由于这是未指定的,他们被允许走它。



    IE10如何获得它非常错误



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

     1.从arguments.callee获取a5 
    2.我们得到了什么? [object Function]
    3.获取a5.caller
    SCRIPT5043:在严格模式下不允许访问函数或arguments对象的'caller'属性

    与其他人一样,我们从 arguments.callee 获得 foo.bar 函数。然后尝试访问非严格函数的调用者给我们一个错误,说我们不能在严格模式下这样做。



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



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



    BTW,IE9肯定会得到这个错误:

     1。从arguments.callee获取a5 
    2.我们得到了什么? [对象功能]
    3.获取a5.caller
    4.现在是什么a5? [对象功能]
    3.获取a5.caller
    4.现在是什么a5? [object Null]

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



    < h2>结论和观察

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



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



    这段代码也很有趣:< a href =http://jsbin.com/iliguq/1>实时复制 | 直播资源

      <!DOCTYPE html> 
    < html>
    < head>
    < meta charset = utf-8 />
    < title>严格和宽松的功能#calller< / title>
    < style>
    p {
    font-family:sans-serif;
    保证金:0.1em;
    }
    .err {
    color:#d00;
    }
    < / style>
    < / head>
    < body>
    < script>
    函数显示(msg,cls){
    var p = document.createElement('p');
    if(cls){
    p.className = cls;
    }
    p.innerHTML = String(msg);
    document.body.appendChild(p);
    }

    //松散函数
    (function(){
    函数loose1(){
    显示(loose1调用loose2);
    loose2();
    }
    loose1.id =loose1; //由于名称不标准,因此

    函数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){
    显示(loose2:exception:+
    (e.message || String(e)),
    err);
    }
    }
    loose2.id =loose2;

    window.loose1 = loose1;

    window.loose2 = loose2;
    })();

    //严格的
    (函数(){
    使用严格;

    函数strict1(){
    display( strict1:调用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>


    Situation:

    I found something strange concerning strict mode in Javascript.

    • I am using an external, third-party Javascript library which
      • was minified,
      • has over 4000 lines of code,
      • is not using use strict at all, and
      • is using arguments.callee.
    • I am using use strict in my own code, scoped within a function.

    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

    Code:

    I've removed all the unrelated stuff and reduced the code into this (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");
    })();


    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


    My understanding:

    • All modern desktop browsers support use strict (see Can I use).
    • The use strict is scoped within my function, so everything defined outside its scope is not affected (see this Stack Overflow question).

    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?

    解决方案

    Preface

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

    • All modern desktop browsers support use strict...

    No, not at all. IE8 is a fairly modern browser (not anymore, in 2015), and IE9 is a quite fairly 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.

    • The use strict is scoped within my function, so everything defined outside its scope is not affected

    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?

    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 slightly wrong. :-) (IE9 definitely gets it wrong, although not in a particularly harmful way.)

    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 caller property of function objects. So let's start there.

    Function#caller

    The Function#caller property 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.

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

    That's on a strict function. 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

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

    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.bar function from arguments.callee, but then accessing caller on that non-strict function gave us null. The loop terminates and we don't get any error.

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

    How Firefox Gets It Right

    SpiderMonkey (Firefox's JavaScript engine) does this:

    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.bar from arguments.callee, but then accessing caller on that non-strict function fails with an error.

    Since, again, the access to caller on 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.

    How IE10 Gets It Very Slightly Wrong

    JScript (IE10's JavaScript engine) does this:

     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.bar function from arguments.callee. Then trying to access that non-strict function's caller gives us an error saying we can't do that in strict mode.

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

    But you could argue this is no more wrong that what Chrome and Firefox do, because (again) the caller access 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 very wrong.

    BTW, IE9 definitely gets this wrong:

    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#caller on 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 caller on a strict function.

    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 caller on strict functions, Chrome, Firefox, and IE10 all (in various ways) prevent your using caller to get a reference to a strict function, even when accessing caller on 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.

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

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

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

    这篇关于“严格使用”的范围不一致在不同的Web浏览器上(关于arguments.callee和调用者)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆