Function Expression и ебучий осёл

В JavaScript, как известно, есть два способа объявить функцию.

/* Function Declaration. Функция сразу определяется в локальном контексте под именем func */
function func() {
 
}

и

/* Function Expression.  */
var func; // func просто переменная. Изначально в ней, как обычно, undefined.
 
func = (function () {}); // В определённый момент в качестве значения ей присваивается объект функции.

FE ещё называют анонимной функцией.

И есть ещё один подвид Function Expression. Именованное (Named) Function Expression:

var func;
 
func = (function fufunc() { /* ... */ });

Анонимная неанонимная функция :).

В нашем локальном контексте по прежнему есть переменная func.
Но в контексте вызываемой функции также появляется её «имя» fufunc.

var func;
 
func = (function fufunc() {
    return func === fufunc; // func и fufunc ссылаются на один и тот же объект функции
})();
 
func === fufunc; // ошибка, в этом контексте нет fufunc

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

(function iam(x) {
    if (x > 0) {
        return iam(x - 1);
    } else {
        return 1;
    }
})(5);

Ну и ещё, это полезно для отладки, чтобы в консоли и профайлере отображалась не просто стена из «(anonym function)», а что-то осмысленное.

И, как всегда, нашу идилию разрушает ёбаный осёл!
IE 9 уже работает с этим по человечески, но более старые, как всегда жгут.

var func;
 
func = (function fufunc() {
    return func === fufunc; // false
})();
 
func === fufunc; // нет ошибки, но false

Он создаёт в нашем контексте и func и fufunc.

Так не просто создаёт две ссылки на один объект функции. Это можно ещё понять и простить. Нет, это две совершенно различные функции. Да ещё и с различными прототипами.

Например, создаём функцию-конструктор и проверяем в ней, как её вызвали, с new или без:

function getConstruct(x) {
    return function C() {
        if (!(this instanceof C)) { // вызвали без new
            throw new Error("Example: instance = new C()");
        }
        this.x = x;
    }
}
 
var Construct = getConstruct();
 
var x = new Construct();

Хуя с два. В IE8 this будет не instanceof C. Так как C и то, что вернула getConstruct(), это две совершенно разные функции.

Или вот:

var Func = function iam() {
    iam.counter -= 1;
    if (iam.counter === 0) {
        alert("BANG");
    }
};
Func.counter = 5;

Хрена. Func и iam два разных объекта и у них два разных свойства counter.

Или собираем функции в namespace, чтобы не загадить глобальный контекст:

var MyNS = {
    'my_super_func': function my_super_func() { /* ... */ },
    'my_puper_func': function my_puper_func() { /* ... */ }
};

IE8 всё равно загадит глобальный контекст копиями всех этих функций.

Так и живём.

2 комментария »

  • Прикольно, спасибо за статью, пригодится!

    adw0rd, 13.03.2013, 8:24

  • Я знаю для решения этой проблемы отличный хак.
    Не поддерживать IE < 9.

    Алексей, 13.03.2013, 16:12

Leave a comment