JavaScript: баг с замыканием в FireFox

Какой alert должен показать следующий JavaScript в браузере?
<html>
<head>
<script>
/* When are functions defined? */
function really() { alert("Original"); }
if (0) {
    alert("No");
    function really() { alert("Yes, really"); }
}
really();
</script>
</head>
<body>Really</body>
</html>
Поведение тут зависит от браузера, FF выведет «Original», а остальные браузеры — «Yes, really».
4 декабря 2008 09:34

ELV1S (elv1s.ru)
4 декабря 2008, 11:02

«Yes, really» будет везде, кроме Firefox-а. И в Опере, и в Сафари, и в IE.

http://dmitry.baranovskiy.com/post/36156571

ELV1S (elv1s.ru)
4 декабря 2008, 11:04

И замыкания я здесь не вижу.

Alisey (alisey.myopenid.com)
4 декабря 2008, 11:42

Так даже нагляднее:
really();
function really() { alert(1) }
if (1) function really() { alert(2) }

If не создаёт новой области видимости, значит второе определение должно перетирать первое. И Firefox ведёт себя неправильно.

Привязка переменных должна делаться только в момент входа в контекст выполнения:
lol = 1; (function () { alert(lol); var lol; })();

В PHP, конечно, царит шизофрения. Например, функции внутри if создаются по мере выполнения кода, а функции после безусловного exit - в момент инициализации.

bolk (bolknote.ru)
4 декабря 2008, 11:51, ответ предназначен ELV1S (elv1s.ru):

OMG! Я всегда считал, что function really() {} полный эквивалент var really = function () {}

Замыкание — это функция внутри функции со своим окружением. То, что здесь окружение внутри функции не используется, мне кажется частным случаем замыкания.

bolk (bolknote.ru)
4 декабря 2008, 11:51, ответ предназначен Alisey (alisey.myopenid.com):

В PHP до версии 5.3 нет лямбд, так что о чём речь?

bolk (bolknote.ru)
4 декабря 2008, 11:52, ответ предназначен ELV1S (elv1s.ru):

Кстати, ты не заглядывал в стандарт? FF *точно* себя неправильно ведёт?

Alisey (alisey.myopenid.com)
4 декабря 2008, 12:00

Нашёл, ECMA-262. В момент входа в контекст выполнения (Global, Function, Eval), создаётся объект с переменными для этого контекста. Сначала туда заносятся формальные параметры, потом определения функций, в том порядке, как они появляются в коде, их значением становится функция, созданная на основе определения в коде. Если имя занято, то значение перетирается. Потом назначаются переменные (var), со значением undefined. И если имя уже занято, то привязка не меняется.

Alisey (alisey.myopenid.com)
4 декабря 2008, 12:20, ответ предназначен bolk (bolknote.ru):

В PHP до версии 5.3 нет лямбд, так что о чём речь?
Причём здесь лямбды? Речь о порядке инициализации и области видимости. И замыкание - это не "функция внутри функции". Замыкание - это функция, с привязкой переменных на основе лексического контекста. Она может быть совсем даже снаружи, а не внутри.
Некоторые вообще предпочитают называть замыканием не саму функцию, а только эту привязку.

i = 1;
function really() { alert(i) };
function lol(){ var i = 2; really();} // единица, замыкание есть, "функций внутри функции" нет
lol();

bolk (bolknote.ru)
4 декабря 2008, 13:27, ответ предназначен Alisey (alisey.myopenid.com):

И замыкание - это не "функция внутри функции".
Я упрощённо говорю, ага.
Причём здесь лямбды?
Я подумал, что речь идёт о create_function, только сейчас догадался, что речь идти может и о function вложенном в function.
Нашёл, ECMA-262.
Меня волнует моё невольно заблуждение. Я всегда считал, что два упоминаемых описания эквиваленты в языке. По стандарту это так или нет?

Alisey (alisey.myopenid.com)
4 декабря 2008, 14:56, ответ предназначен bolk (bolknote.ru):

По стандарту это не так, что является хорошим дизайнерским решением.
<script>
a();
function a(){ alert(1) }
a = function(){ alert(2) }
a();
</script>

Как это работает: попадаем в новую область видимости, в этом конкретном случае Global. [...много предварительных действий...], ищем декларации функций. function a()... - это декларация. Запоминаем привязку имя -> тело функции. Больше объявлений функций нет, теперь ищем переменные. Есть объявление переменной a. Если бы это имя уже не ссылалось на функцию, мы бы инициализировали его undefined, а так - пропускаем.
Теперь, когда все имена привязаны, начинаем выполнять код. a() выдаёт единицу, что подтверждает идею о том, что декларации функций обрабатываются до начала выполнения кода. Доходим до строки с присвоением: a = ..., в результате присвоения "a" начинает ссылаться на анонимную функцию, и следующая строка выдаёт 2.

Это вроде бы очевидно - присвоение происходит в момент выполнения кода.
Но может быть неочевидно, что вот это тоже присвоение: var a = function(){ }
И это: a = true ? alert : 9; a(5);
Функции - такие же значения как числа и строки.
Можно в массив запихнуть: [function() { alert(1) }, function() { alert(2) }][1]();

А вот у деклараций (и переменных, и функций) есть та особенность, что они инициализируются в момент вхождения в новую область видимости. В вашем первом примере if не создаёт новой области видимости, это можно проверить задав в нём какой-нибудь var, контекст останется тот же. Значит декларация должна обрабатываться ещё до этапа выполнения. Но Firefox почему-то так не считает.

bolk (bolknote.ru)
4 декабря 2008, 15:15, ответ предназначен Alisey (alisey.myopenid.com):

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

P.S. заметку поправил.

bolk (bolknote.ru)
4 декабря 2008, 15:17, ответ предназначен Alisey (alisey.myopenid.com):

Рассказывать что есть такой тип «функция» не нужно. Из традиционного, я давно пишу на JS, писал на Pascal, Perl, где есть такой тип и недавно — на Python.

Alisey (alisey.myopenid.com)
4 декабря 2008, 15:27, ответ предназначен bolk (bolknote.ru):

В Пайтоне и некоторых версиях Javascript ещё есть yield, вроде базовые вещи, не понимаю как большинство "прекрасно обходится и без них".

bolk (bolknote.ru)
4 декабря 2008, 15:44, ответ предназначен Alisey (alisey.myopenid.com):

В Python/JS 2.0 есть yield, да. Это удобно.

jahson.livejournal.com (jahson.livejournal.com)
4 декабря 2008, 18:45, ответ предназначен bolk (bolknote.ru):

Ну вот, всё уже объяснили ) Положу свою копейку:

В JavaScript, по-сути, три способа объявлять функции - function statement, function expression и function constructor, не буду заморачиваться переводом. Первый - это пресловутый function name( [params] ) { body }, второй - function [name] ( [params] ) { body }, третий - new Function('args', 'body'). Первый используется для объявления "нормальных" (не знаю, как назвать иначе) функций, а второй - используется внутри выражений (expressions). Про третий забудем, единственное, что нужно отметить - у функций созданных этим способом нет имени и они наследуют только глобальную область видимости %)

Function statement хорош тем, что может быть использован до появления - почему уже описали. Т.е. даже function a() { return b(); function b() { return 1; }; }; отработает и вернёт 1.

Function expression создаёт анонимную функцию, но наша анонимная функция может иметь имя: var x = function y() { alert(x); alert(y); };, притом о настоящем имени функции никто, кроме неё не знает.

Firefox вообще говоря, прав, исходя из написанного на mdc определения function expression:
A function declaration is very easily (and often unintentionally) turned into a function expression. A function declaration ceases to be one when it either:

    * becomes part of an expression
    * is no longer a "source element" of a function or the script itself. A "source element" is a non-nested statement in the script or a function body:

      var x = 0; // source element
      if (x == 0) { // source element
         x = 10; // not a source element
         function boo() {} // not a source element
      }
      function foo() { // source element
         var y = 20; // source element
         function bar() {} // source element
         while (y == 10) { // source element
            function blah() {} // not a source element
            y++; // not a source element
         }
      }
https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Functions

Alisey (alisey.myopenid.com)
4 декабря 2008, 18:47, ответ предназначен bolk (bolknote.ru):

Даже в JS 1.7, их можно вывернуть наизнанку и сделать что-то вроде:

var event = yield waitForMouseClick();
alert(event.x);

Alisey (alisey.myopenid.com)
4 декабря 2008, 20:00, ответ предназначен jahson.livejournal.com:

Взял яваскриптовый движок, написанный на яваскрипте автором яваскрипта.
http://mxr.mozilla.org/mozilla/source/js/narcissus/

console.log(evaluate('function a() { return 1 }; if (0) function a() { return 2 }; a();'));

Действительно возвращает единицу. Не могу найти в коде, как это происходит.

Alisey (alisey.myopenid.com)
4 декабря 2008, 20:01, ответ предназначен bolk (bolknote.ru):

Впору переименовывать заметку :)

jahson.livejournal.com (jahson.livejournal.com)
4 декабря 2008, 21:03, ответ предназначен Alisey (alisey.myopenid.com):

Мне вот больше интересно, как это так:
function a() { return 1 }; if (1) { function a() { return 2 }; } a(); => 2

Alisey (alisey.myopenid.com)
4 декабря 2008, 22:08, ответ предназначен jahson.livejournal.com:

Это в глобальной области видимости.
А теперь что-то вообще чудесное (FF):

(function() {
  function a() { return 1 };
  if (1) function a() { return 2 };
  return a();
})(); // 1

zeroglif.myopenid.com (zeroglif.myopenid.com)
4 декабря 2008, 22:47

В эпоху NN функция внутри if() легко и непринуждённо объявлялась (в стиле IE). В дальнейшем от этого отказались, но чтобы не ломать дурацкий код добавили что-то вроде экстеншна к движку. Подаётся это обычно или под соусом 16-ого раздела экмы, где разрешено отступать от буквы стандарта, или под соусом, который описал выше jahson (про source elements)...

jahson.livejournal.com (jahson.livejournal.com)
5 декабря 2008, 05:28, ответ предназначен Alisey (alisey.myopenid.com):

Так понятней выйдет:
(function() {
if (1) function a() { return 2 };
return a();
function a() { return 1 };
})();

Почему так выходит - скорее всего из-за от этого

// function statement
function a() {
   // function statement
   function b() {}
   if (0) {
      // function expression
      function c() {}
   }
}

jahson.livejournal.com (jahson.livejournal.com)
5 декабря 2008, 05:29, ответ предназначен zeroglif.myopenid.com:

Ох уж эта экма )

bolk (bolknote.ru)
5 декабря 2008, 09:49, ответ предназначен jahson.livejournal.com:

Синтаксис JS я знаю. Я уже объяснил что для меня было непонятным.

Alisey (alisey.myopenid.com)
5 декабря 2008, 09:53, ответ предназначен zeroglif.myopenid.com:

но чтобы не ломать дурацкий код...
Так какой же код официально "дурацкий"? IE-style, Mozilla-style?

(function() {
function a() { return 1 };
if (1) function a() { return 2 };
return a();
})(); // 1 в FF3

Если считать, что вторая функция a() - это Declaration, то она переопределяет первую на этапе инициализации, результат 2. Если считать её Expression - она переопределяет первую на этапе выполнения. (Обратите внимание, условие выполняется). Опять результат 2.
Движок Брендана Айка выдаёт 2, зато SpiderMonkey - единицу. Непонятно что это за "экстеншен" такой.

Кстати :) По вашим комментариям на dklab понял как работают прототипы и конструкторы. Спасибо.

jahson.livejournal.com (jahson.livejournal.com)
5 декабря 2008, 10:54, ответ предназначен bolk (bolknote.ru):

Именно.

jahson.livejournal.com (jahson.livejournal.com)
5 декабря 2008, 10:56, ответ предназначен Alisey (alisey.myopenid.com):

А если считать, что первая - declaration, а вторая - expression? Я там выше привёл пример.

Alisey (alisey.myopenid.com)
5 декабря 2008, 11:53, ответ предназначен jahson.livejournal.com:

Да, рассмотрим её как Exression, то есть из-за if констукция будет обрабатываться как
if (1) var a = function () { return 2 };

Окажется, что если перевести в такую форму, то результат станет 2, вполне ожидаемый результат, выражение обработалось, значение присвоилось. Почему тогда в первом случае не так?
Вы можете сказать, что это не совсем правильная аналогия, а правильно будет
if (1) function /*a*/ () { return 2 };

Вроде бы тогда всё логично, функция есть, но она не используется. Результат 1.
Но есть одна вещь, которая ломает эту теорию:
(function() {
//function a() { return 1 };
if (1) function a() { return 2 };
return a();
})(); // 2!!!

Ещё один эксперимент, убираем обёрточую функцию:
function a() { return 1 };
if (1) function a() { return 2 };
a(); // 2

Результат снова два. Я не вижу логики.

zeroglif.myopenid.com (zeroglif.myopenid.com)
5 декабря 2008, 12:10

Объявленная функция (FD) должна быть или внутри функции, или выше всех (в Program), внутри statement-a типа if(1){} функцию объявить нельзя. Одновременно с этим функция-выражение (FE) может быть до-фига-где, но только вот нельзя ей быть в качестве ExpressionStatement, там не должно быть слова function в начале. Отсюда выходит, что внутри if(1){} и не FD, и не FE, а... отложенное объявление, это расширение, добавленное в Mz лет 9-10 назад, с тех пор им никто и не пользуется... ;)

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)