Подтвердить что ты не робот

Несогласованный объем "строгого использования" в разных веб-браузерах (в отношении arguments.callee и caller)

Ситуация:

Я нашел что-то странное в строгом режиме в 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 my code.
(function() {
    "use strict";   // I enable strict mode in my own function only.

    foo.bar();
    alert("done");
})();
4b9b3361

Ответ 1

Введение

Несколько быстрых очков, прежде чем мы попадаем в это мясо:

  • Все современные настольные браузеры поддерживают use strict...

Нет, совсем нет. IE8 - довольно современный браузер (не более, в 2015 году), а IE9 - довольно современный довольно современный браузер. Ни один из них не поддерживает строгий режим (IE9 поддерживает его части). IE8 будет с нами долгое время, потому что он настолько высок, насколько вы можете идти в Windows XP. Несмотря на то, что XP теперь полностью оторван от жизни (ну, вы можете купить специальный план "Индивидуальная поддержка" от MS), люди будут продолжать использовать его некоторое время.

  • use strict находится в пределах моей функции, поэтому все, что определено вне его области действия, не затрагивается

Не совсем. Спецификация налагает ограничения на то, как даже нестрогий код использует функции, созданные в строгом режиме. Таким образом, строгий режим может выходить за пределы его окна. И на самом деле, эта часть того, что происходит с кодом, который вы используете.

Обзор

Итак, все ли браузеры, кроме Chrome, ошибаются? Или это наоборот? Или это поведение undefined, чтобы браузеры могли реализовать его в любом случае?

Вникая в это немного, это выглядит так:

  • Chrome делает все правильно,

  • Firefox делает все по-другому,

  • ... и IE10 очень сильно ошибается.:-) (IE9 определенно ошибается, хотя и не особенно опасным образом.)

Я не смотрел на других, я полагал, что мы покрыли землю.

Код, вызывающий основную проблему, представляет собой этот цикл

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

..., который опирается на свойство caller объектов функции. Так что начните там.

Function#caller

Свойство Function#caller никогда не было определено в спецификации третьего издания. Некоторые реализации предоставили его, другие - нет. Это ужасно плохая идея (извините, это было субъективно, не так ли?) Проблема с реализацией (даже больше, чем arguments.caller), особенно в многопоточных средах (и есть несколько -работанные JavaScript-движки), а также с рекурсивным кодом, как указал Берги в комментариях по этому вопросу.

Итак, в пятом издании они явно избавились от него, указав, что ссылка на свойство 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));
}

Как Chrome получает это право

В V8 (движок JavaScript Chrome) приведенный выше код дает нам следующее:

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 (движок JavaScript 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 (движок JavaScript IE10) делает следующее:

 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. Все они поддерживают получение ссылки на не -страничную функцию через caller (на нестрогую функцию), а не строгую функцию.

Я не могу найти, что такое поведение указано где угодно (но тогда caller для нестрогих функций полностью не указано...). Вероятно, это Right Thing (tm) я просто не вижу его.

Этот код также интересен: 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>