Названные Выражения function в IE, части 2

Я задал этот вопрос некоторое время назад и был доволен принятым ответом. Я сейчас понял, однако, что следующая техника:

var testaroo = 0;
(function executeOnLoad() {
    if (testaroo++ < 5) {
        setTimeout(executeOnLoad, 25);
        return;
    }
    alert(testaroo); // alerts "6"
})();

возвращает результат, который я ожидаю. Если ответ T.J.Crowder от моего первого вопроса корректен, то разве, эта техника не должна не работать?

7
задан Community 23 May 2017 в 11:43
поделиться

2 ответа

очень хороший вопрос. : -)

Разница:

Разница между этой ситуацией и ситуацией detachEvent в том, что здесь вам все равно, что ссылка на функцию внутри и снаружи «функции» одинакова, просто чтобы код был таким же. В ситуации detachEvent имело значение, что вы видите одну и ту же ссылку на функцию внутри и вне «функции», потому что так работает detachEvent , отсоединив конкретную функцию, которую вы ему передали.

Две функции?

Да. CMS указала, что IE (JScript) создает две функции, когда видит выражение именованной функции, подобное тому, которое содержится в вашем коде. (Мы еще вернемся к этому.) Интересно то, что вы звоните обоим из них. Да, действительно. :-) Первоначальный вызов вызывает функцию, возвращаемую выражением, а затем все вызовы, использующие это имя, вызывают другой.

Незначительное изменение кода может прояснить ситуацию:

var testaroo = 0;
var f = function executeOnLoad() {
    if (testaroo++ < 5) {
        setTimeout(executeOnLoad, 25);
        return;
    }
    alert(testaroo); // alerts "6"
};
f();

f (); в конце вызывает функцию, которая была возвращена выражением функции, но что интересно, эта функция вызывается только один раз . Во всех остальных случаях, когда он вызывается через ссылку executeOnLoad , вызывается другая функция .Но поскольку обе функции закрываются по одним и тем же данным (включая переменную testaroo ) и имеют один и тот же код, эффект очень похож на наличие только одной функции. Тем не менее, мы можем продемонстрировать, что есть две функции, во многом так же, как это сделала CMS:

var testaroo = 0;
var f = function executeOnLoad() {
    if (testaroo++ < 5) {
        setTimeout(executeOnLoad, 0);
        return;
    }
    alert(testaroo); // alerts "6"

    // Alerts "Same function? false"
    alert("Same function? " + (f === executeOnLoad));
};
f();

Это не просто какой-то артефакт имен, это действительно две функции, создаваемые JScript.

Что происходит?

По сути, люди, реализующие JScript, по-видимому, решили обрабатывать именованные функциональные выражения как как объявления функции , так и как выражения функции , создание двух функциональных объектов в процессе (один из «объявления», один из «выражения») и почти наверняка делает это в разное время. Это совершенно неправильно, но они так поступили. Удивительно, но даже новый JScript в IE8 продолжает такое поведение.

Вот почему ваш код видит (и использует) две разные функции. Это также причина названия «утечка», упомянутого в CMS. Переход к слегка измененной копии его примера:

function outer() {
    var myFunc = function inner() {};

    alert(typeof inner); // "undefined" on most browsers, "function" on IE

    if (typeof inner !== "undefined") { // avoid TypeError on other browsers
        // IE actually creates two function objects: Two proofs:
        alert(inner === myFunc); // false!
        inner.foo = "foo";
        alert(inner.foo);        // "foo"
        alert(myFunc.foo);       // undefined
    }
}

Как он упоминал, внутренний определен в IE (JScript), но не в других браузерах. Почему нет? Для случайного наблюдателя, помимо двух функций, поведение JScript в отношении имени функции кажется правильным. В конце концов, только функции вводят новую область видимости в Javascript, верно? А функция внутренняя четко определена в внешнем .Но спецификация на самом деле приложила все усилия, чтобы сказать «нет», этот символ не определен в external (даже до того, что углубился в детали о том, как реализации избегают этого, не нарушая других правил) . Это описано в Разделе 13 (как в 3-м, так и в 5-м издании спецификации ). Вот соответствующая высокоуровневая цитата:

На Идентификатор в FunctionExpression можно ссылаться изнутри FunctionBody FunctionExpression, чтобы функция могла вызывать себя рекурсивно. Однако, в отличие от FunctionDeclaration, на Идентификатор в FunctionExpression нельзя ссылаться, и он не влияет на область, содержащую FunctionExpression.

Почему они пошли на эту беду? Я не знаю, но подозреваю, что это связано с тем фактом, что объявления функции оцениваются до выполнения любого кода оператора (пошаговый код), тогда как выражения функции - как и все выражения - оцениваются как часть кода оператора, когда они достигаются в потоке управления. Рассмотрим:

function foo() {

    bar();

    function bar() {
        alert("Hi!");
    }
}

Когда поток управления входит в функцию foo , первое, что происходит, это то, что создается экземпляр функции bar и связывается с символом bar ]; только после этого интерпретатор начинает обработку операторов в теле функции foo . Поэтому вызов на бар вверху работает.

Но здесь:

function foo() {

    var f;

    f = function() {
        alert("Hi!");
    };

    f();
}

Функция выражение вычисляется при достижении (ну, вероятно, мы не можем быть уверены, что некоторые реализации не делают этого раньше).Одна веская причина, по которой выражение не оценивается (или не должно оцениваться) раньше:

function foo() {

    var f;

    if (some_condition) {
        f = function() {
            alert("Hi (1)!");
        };
    }
    else {
        f = function() {
            alert("Hi! (2)");
        };
    }

    f();
}

... выполнение этого раньше приводит к двусмысленности и / или потере усилий. Это приводит к вопросу, что здесь должно произойти:

function foo() {

    var f;

    bar();

    if (some_condition) {
        f = function bar() {
            alert("Hi (1)!");
        };
    }
    else {
        f = function bar() {
            alert("Hi! (2)");
        };
    }

    f();
}

Какой бар вызывается в начале?Авторы спецификации решили разрешить эту ситуацию, сказав, что bar вообще не определен в foo , таким образом, полностью обойдя проблему. (Это не единственный способ, которым они могли это решить, но, похоже, именно так они и решили.)

Так как же IE (JScript) это обрабатывает? Бар , вызванный в начале, предупреждает "Привет (2)!". Это, в сочетании с тем фактом, что мы знаем, что два функциональных объекта созданы на основе других наших тестов, является самым четким признаком того, что JScript обрабатывает именованные функциональные выражения как объявления функций и функциональных выражений, потому что это именно то, что Предполагается, что здесь произойдет :

function outer() {

    bar();

    function bar() {
        alert("Hi (1)!");
    }

    function bar() {
        alert("Hi (2)!");
    }
}

У нас есть два объявления функций с одинаковым именем. Ошибка синтаксиса? Вы так думаете, но это не так. Спецификация явно допускает это и говорит, что второе объявление в порядке исходного кода «выигрывает». Из Раздела 10.1.3 спецификации 3-го издания:

Для каждого объявления функции в коде в порядке исходного текста создайте свойство объекта переменной, имя которого является идентификатором в объявлении функции ... Если объект переменной уже имеет свойство с этим именем, замените его значение и атрибуты ...

(«Переменный объект» - это то, как разрешаются символы; это уже другая тема.) Это так же однозначно в 5-м издании (Раздел 10.5), но, ммм, намного менее цитируемо.

Значит, это просто IE?

Чтобы прояснить, IE - не единственный браузер, который имеет (или имел) необычную обработку NFE, хотя им становится довольно одиноко (довольно большая проблема с Safari было исправлено, например). Просто у JScript действительно большая особенность в этом отношении. Но если подойти к этому, я думаю , что на самом деле это единственная текущая крупная реализация с какой-либо действительно серьезной проблемой - мне интересно узнать о любых других, если кто-то знает о них.

Наша позиция

Учитывая все вышесказанное, на данный момент я держусь подальше от NFE, потому что я (как и большинство людей) должен поддерживать JScript. В конце концов, достаточно просто использовать объявление функции, а затем обратиться к нему позже (или даже раньше) с помощью переменной:

function foo() { }
var f = foo;

... и это надежно работает во всех браузерах, избегая таких проблем, как ваше detachEvent проблема. Другие разумные люди решают проблему по-другому , просто принимая, что будут созданы две функции, и пытаются минимизировать влияние, но мне совсем не нравится этот ответ, потому что именно то, что случилось с вами с detachEvent .

6
ответ дан 6 December 2019 в 21:11
поделиться

Что ж, это будет работать, проблема с JScript (IE) в том, что идентификатор выражения функции ( executeOnLoad ) будет утечка в его охватывающую область, и фактически создавая два функциональных объекта ..

(function () {
  var myFunc = function foo () {};
  alert(typeof foo); // "undefined" on all browsers, "function" on IE

  if (typeof foo !== "undefined") { // avoid TypeError on other browsers
    alert( foo === myFunc ); // false!, IE actually creates two function objects
  }
})();
6
ответ дан 6 December 2019 в 21:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: