Ответ 1
Потому что весь блок кода фактически один большой main
. В JavaScript глобальный код может иметь все код функции конструкций, который может иметь, и имеет ступенчатое выполнение, как и функции. Фактически, когда JS-движок обрабатывает блок кода в целом, он выполняет почти то же самое, что и при обработке вызова функции. См. Разделы спецификации 10.4.1 ( "Ввод глобального кода" ) и 10.4.3 ( "Ввод кода функции" ) и обратите внимание на то, насколько они похожи.
C не позволяет выполнять пошаговый код на глобальном уровне (у вас могут быть всевозможные инициализаторы, и они могут выглядеть как поэтапно, но это другая тема). И поэтому C требуется явная точка входа (main
). В JavaScript точкой входа является начало кода.
Что касается вашего вопроса ниже о том, является ли глобальный код последовательным. Ответ - да, это точно так же, как код в функции. Итак:
var x, y, z;
x = 1;
y = 2;
z = x + y;
alert("z is " + z);
... будет предупреждать "z is 3"
. Код выполняется последовательно, сверху вниз.
Есть несколько вещей, которые происходят до выполнения поэтапного кода, хотя это полезно знать. Наиболее значительным является то, что любые объявления в исходном тексте вводимой области обрабатываются до начала поэтапного кода. JavaScript имеет два основных типа объявлений: объявления переменных и декларации функций:
-
Имя любой переменной, объявленной с помощью
var
, добавляется в область (со значениемundefined
) перед выполнением любого поэтапного кода. (Подробнее: Плохо, неправильно понятоvar
) -
Обработки функций обрабатываются, а имена функций добавляются в область перед выполнением любого поэтапного кода. (JavaScript также имеет что-то еще, называемое выражением функции, которое является поэтапным кодом. Подробнее об этом ниже.)
Так, например, в этом исходном тексте:
var x;
x = 1;
foo();
function foo() {
}
объявления
var x;
function foo() {
}
а пошаговый код
x = 1;
foo();
Сначала обрабатываются декларации. Вот почему работает вызов foo
. (Эти же правила применяются к исходному тексту внутри функций.) Эта обработка объявлений перед чем-либо еще иногда называется "подъемом", потому что декларации в некотором смысле отрываются от их местоположения в исходном тексте и перемещаются в самом начале. Я предпочитаю думать об этом как о двух проходах через источник: первый проход делает объявления, второй выполняет поэтапный код.
(Боковое примечание: объявление переменной более одного раза в той же области вполне законно [хотя и бессмысленно]. Объявление двух функций с тем же именем также является законным, последнее объявление переопределяет предыдущее.)
(В примечании 2: ES2015 [ES6] введены объявления let
и const
переменных, которые ведут себя несколько иначе, чем var
. Вы не можете объявить переменную дважды с ними, у них есть область блока, и вы не могут использовать переменную до утверждения, где она была объявлена. Поэтому они в основном не подняты [есть что-то вроде подъема в том, что они препятствуют доступу к теневой переменной в области сложения еще до let x
или любой другой строки ].)
Подробнее и, возможно, немного технически:
var
Если var
происходит до запуска поэтапного кода, вам может быть интересно:
var x = 1;
Это происходит до поэтапного кода или как часть его? Ответ заключается в том, что на самом деле это просто сокращение двух совершенно разных вещей:
var x;
x = 1;
Часть var x;
выполняется перед поэтапным кодом, часть x = 1;
является ступенчатым кодом и выполняется, когда мы достигаем ее в последовательности. Итак:
alert(x); // "undefined" -- there **is** a variable `x`; it has the value `undefined`
var x = 1;
alert(x); // "1" -- now `x` has the value `1`
Объявление функций
JavaScript имеет две разные, но очень похожие вещи: объявления функций и выражения функций. Вы можете указать, какая из них связана с тем, используете ли вы результирующую функцию как часть выражения, в котором она определена.
Здесь объявление функции:
function foo() {
}
Это все выражения функций (мы используем полученное значение функции как часть выражения, в терминологии информатики функция используется как правое значение):
// 1: Assigning the result to something
var x = function() {
};
// 2: Passing the result into a function
bar(function() {
});
// 3: Calling the function immediately
(function(){
})();
// 4: Also calling the function immediately (parens at end are different)
(function(){
}());
// 5: Also calling the function immediately
!function(){
}();
// 6: Syntax error, the parser needs *something* (parens, an operator like ! or
// + or -, whatever) to know that the `function` keyword is starting an *expression*,
// because otherwise it starts a *declaration* and the parens at the end don't make
// any sense (and function declarations are required to have names).
function(){
}();
Правило состоит в том, что объявления функций обрабатываются до начала поэтапного кода. Выражения функции, как и все другие выражения, обрабатываются там, где они встречаются.
Одно заключительное примечание: это выражение функции с именем:
var f = function foo() {
};
Мы используем его как правое значение, поэтому мы знаем это выражение; но у него есть имя, подобное объявлениям функций. Это совершенно правильный и законный JavaScript, и то, что он хотел сделать, - создать функцию с правильным именем (foo
) как часть поэтапного кода. Имя функции не добавляется в область (как это было бы, если бы это было объявление функции).
Однако вы не увидите названные функциональные выражения в очень многих местах, потому что JScript (движок Microsoft JavaScript) получает их ужасно и совершенно неправильно, создавая две отдельные функции в два разных раза.