Ответ 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>